Add slides text autofit: shrink mode + GS-style toggle UI#305
Conversation
📝 WalkthroughWalkthroughThis PR implements a three-mode text autofit feature ( ChangesText Autofit Feature Implementation
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Verification: verify:selfResult: ✅ PASS in 226.1s
Verification: verify:integrationResult: ✅ PASS |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/slides/src/view/editor/editor.ts`:
- Around line 577-583: The autofit toggle updates store state but doesn't
request a repaint; after the this.options.store.updateElementData(slide.id,
elementId, { autofit: nextMode }) call inside onAutofitToggle, invoke the
editor's render trigger (e.g. call this.requestRender()) so the canvas/overlay
is re-rendered immediately; update onAutofitToggle to perform the state change
inside this.options.store.batch(...) and then call the editor's render-request
method (requestRender or the existing equivalent) to enqueue a repaint.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: e030c77a-ceb8-4f00-aa2c-603321e7c094
📒 Files selected for processing (29)
docs/design/README.mddocs/design/slides/slides-text-autofit.mddocs/tasks/active/20260525-slides-text-autofit-lessons.mddocs/tasks/active/20260525-slides-text-autofit-todo.mdpackages/docs/src/view/text-box-editor.tspackages/docs/test/view/text-box-editor.test.tspackages/frontend/src/app/slides/yorkie-slides-store.tspackages/frontend/src/types/slides-document.tspackages/frontend/tests/app/slides/yorkie-slides-equivalence.test.tspackages/slides/src/import/pptx/shape.tspackages/slides/src/import/pptx/text.tspackages/slides/src/index.tspackages/slides/src/model/autofit.tspackages/slides/src/model/element.tspackages/slides/src/model/layout.tspackages/slides/src/store/memory.tspackages/slides/src/view/canvas/text-renderer.tspackages/slides/src/view/editor/editor.tspackages/slides/src/view/editor/interactions/insert.tspackages/slides/src/view/editor/overlay.tspackages/slides/src/view/editor/text-box-editor.tspackages/slides/test/import/pptx/text.test.tspackages/slides/test/model/autofit.test.tspackages/slides/test/model/layout-apply.test.tspackages/slides/test/model/layout.test.tspackages/slides/test/store/memory.test.tspackages/slides/test/view/editor/autofit-toggle.test.tspackages/slides/test/view/editor/interactions/insert-defaults.test.tspackages/slides/test/view/text-box-autofit-gating.test.ts
(cherry picked from commit 51b5030d903ef7324492ed4149b9ed787c3446bc)
Pure functions: scaleBlocks (deep-clone with proportional font/margin), computeAutofitScale (binary-search shrink to fit), computeAutofitHeight (grow-to-fit height). Covered by 7 Vitest unit tests with a fake measurer. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> (cherry picked from commit 09a5b40a85c5a873af6a28908464fbd2f1d5a14d)
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> (cherry picked from commit 6c5dfa908085b16ae64d6925853fbe063b4b70c9)
(cherry picked from commit cb423761a505dbc1dc20184154ae3d07800c4f81)
Adds detectAutofitMode(txBody) to text.ts: reads <a:normAutofit/>, <a:spAutoFit/>, or <a:noAutofit/> and returns 'shrink', 'grow', or 'none'. buildTextElement in shape.ts now sets data.autofit via this function. The existing normAutofit fontScale baking in parseTextBody is unchanged. Five new tests cover all mapping cases. (cherry picked from commit b3292f67a44bce67fb6ee6eabb43c3a3810a5095)
The production YorkieSlidesStore rebuilt text-element data as
{ blocks } in three places — addElement, addSlide placeholder seeding,
and undo/redo snapshot restore — dropping the seeded autofit mode. So
inserted boxes and placeholders fell back to 'none' in production and
undo/redo wiped the mode deck-wide, even on imported decks. All branch
tests passed only because they exercise MemSlidesStore.
Carry autofit (and, in the restore path, the previously-dropped
placeholderRef) through all three sites, add autofit to the Yorkie text
schema, export AutofitMode from the slides barrel, and add a Mem-vs-
Yorkie equivalence test covering insert + add-slide + undo/redo.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
(cherry picked from commit 78d59c06112d8bd9e5b0acfa1abc7020e1ceddaf)
Layout placeholders default to 'shrink' (font auto-scales down to fit the fixed box). User-inserted text boxes default to 'grow' (box height tracks content). Google Slides / OOXML parity. (cherry picked from commit 30704200960693d1e79b5f4a0e2ae9c19fc09b6c)
addSlide and applyLayoutToSlide re-seed text placeholder data.blocks with master typography, but reassigned the whole data object and dropped the seeded autofit mode. Spread the existing data so 'shrink' survives, and add an end-to-end addSlide test that asserts stamped text placeholders keep autofit 'shrink'. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> (cherry picked from commit 5ae3aea97b80d8e8c1858092ce887f0cf508c0e6)
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> (cherry picked from commit f649f0f4ee1ca0e871602abedc4a916b9f95e6db)
Slides autofit 'shrink' needs the editor to lay out scaled-down fonts so the editing surface matches the committed slide canvas. Add an optional layout-only block transform (the grow side already exists via onContentHeightChange). Absent => identity; docs/sheets unaffected. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Reconcile the autofit feature with the auto-grow that landed on main. The wrapper now gates main's onContentHeightChange grow path to 'grow' (and absent, the pre-autofit default) and wires transformLayoutBlocks for 'shrink' (fixed box, fonts scale). 'none' is fixed. editor.ts passes element.data.autofit through; its commit-time height persist is auto- gated because the wrapper only fires onContentHeightChange for grow. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Address reconciliation review: add a wrapper test proving shrink wires only transformLayoutBlocks, grow/absent only onContentHeightChange, none neither. Remove computeAutofitHeight (grow now uses the docs editor's layout height directly, no caller). Wrap the shrink transform in try/catch so a canvas-less env falls back to unscaled instead of failing the mount. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Google-Slides parity in-context affordance: a small button appears at the frame's bottom-left when a single text element is selected. Click flips between 'grow' (auto-resize box) and 'shrink' (fixed box, fonts scale). Wires through OverlayOptions.onAutofitToggle into a single store.updateElementData patch; renderer and editor react on the next repaint, no remount. Hidden during in-place editing because the editing element is already filtered out of the overlay's selection list. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Two fixes to the bottom-left autofit mode toggle:
1. The toggle button sits below the frame, so the editor's overlay-level
pointerdown handler treated the click as a click on empty space and
cleared the selection (which then hid the toggle, masking that the
mode had actually flipped). Stop pointerdown + mousedown + click
propagation on the button so the editor's hit-test never sees it.
Add a regression test that spies on the overlay's pointer listeners.
2. Replace the placeholder text glyphs ('A↕' / '↕') with crisp inline
SVG icons — a rectangle with a vertical two-headed arrow for 'grow',
and big-A + little-a for 'shrink'. Add hover/focus styling, a soft
box shadow, and an aria-label so the affordance is keyboard- and
screen-reader-friendly.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
62fa0fc to
b01fcd1
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/tasks/active/20260525-slides-text-autofit-todo.md`:
- Around line 85-87: Update the doc so the default autofit semantics are
consistent: change the two occurrences that read “absent ⇒ 'none'” (the "Autofit
behavior" paragraph and the paragraph at line ~803) to match the reconciliation
note and implemented behavior by using “absent ⇒ grow”; ensure the
reconciliation note (the top-of-file note referenced on Line 10) remains
accurate or is adjusted to the same “absent ⇒ grow” wording so all three places
(the "Autofit behavior" section, the later paragraph around 803, and the
reconciliation note) state the same default semantics.
- Line 13: The markdown has a blockquote with an extra blank line after the '>'
that triggers MD028; edit the blockquote in
docs/tasks/active/20260525-slides-text-autofit-todo.md to remove the empty line
immediately after the opening '>' so the quote's content starts on the next line
without a blank line (ensure any nested content remains correctly indented and
that no other leading/trailing blank lines remain inside that blockquote).
In `@packages/slides/src/view/editor/overlay.ts`:
- Around line 775-779: The aria-label currently only distinguishes 'shrink' vs
resize and misreports when current === 'none'; update the conditional that sets
the 'aria-label' (the expression using current === 'shrink' in overlay.ts) to
explicitly handle 'none' (e.g., three-way conditional or switch) and return an
accurate label for 'none' such as "Autofit mode: none. Click to switch to resize
shape." so screen readers get the correct state.
- Around line 758-760: The toggle's anchor is computed from unrotated frame
coordinates (using element.frame.x/y/h), so rotated text boxes place the toggle
incorrectly; update the calculation in overlay.ts to account for
element.frame.rotation by computing the visual bottom-left corner after rotation
(e.g., transform the frame's bottom-left corner around the frame center using
element.frame.rotation and then apply the scale and AUTOFIT_TOGGLE_OFFSET) and
use that rotated, scaled point for x/y instead of the raw
frame.x/frame.y/frame.h values; reference the existing variables element,
options (scale) and AUTOFIT_TOGGLE_OFFSET when locating and replacing the
calculation.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 5540c8bd-8fcd-4e1d-a019-8ef313380cf0
📒 Files selected for processing (29)
docs/design/README.mddocs/design/slides/slides-text-autofit.mddocs/tasks/active/20260525-slides-text-autofit-lessons.mddocs/tasks/active/20260525-slides-text-autofit-todo.mdpackages/docs/src/view/text-box-editor.tspackages/docs/test/view/text-box-editor.test.tspackages/frontend/src/app/slides/yorkie-slides-store.tspackages/frontend/src/types/slides-document.tspackages/frontend/tests/app/slides/yorkie-slides-equivalence.test.tspackages/slides/src/import/pptx/shape.tspackages/slides/src/import/pptx/text.tspackages/slides/src/index.tspackages/slides/src/model/autofit.tspackages/slides/src/model/element.tspackages/slides/src/model/layout.tspackages/slides/src/store/memory.tspackages/slides/src/view/canvas/text-renderer.tspackages/slides/src/view/editor/editor.tspackages/slides/src/view/editor/interactions/insert.tspackages/slides/src/view/editor/overlay.tspackages/slides/src/view/editor/text-box-editor.tspackages/slides/test/import/pptx/text.test.tspackages/slides/test/model/autofit.test.tspackages/slides/test/model/layout-apply.test.tspackages/slides/test/model/layout.test.tspackages/slides/test/store/memory.test.tspackages/slides/test/view/editor/autofit-toggle.test.tspackages/slides/test/view/editor/interactions/insert-defaults.test.tspackages/slides/test/view/text-box-autofit-gating.test.ts
🚧 Files skipped from review as they are similar to previous changes (20)
- packages/slides/src/view/editor/interactions/insert.ts
- packages/slides/test/import/pptx/text.test.ts
- docs/design/README.md
- packages/slides/test/store/memory.test.ts
- packages/slides/src/index.ts
- packages/slides/test/model/layout-apply.test.ts
- packages/slides/test/view/editor/autofit-toggle.test.ts
- packages/slides/src/store/memory.ts
- packages/slides/test/model/autofit.test.ts
- packages/slides/src/model/layout.ts
- packages/frontend/src/app/slides/yorkie-slides-store.ts
- packages/slides/src/view/canvas/text-renderer.ts
- packages/slides/src/view/editor/editor.ts
- packages/slides/src/view/editor/text-box-editor.ts
- packages/frontend/tests/app/slides/yorkie-slides-equivalence.test.ts
- packages/slides/test/view/editor/interactions/insert-defaults.test.ts
- packages/slides/src/import/pptx/shape.ts
- packages/slides/test/model/layout.test.ts
- packages/slides/test/view/text-box-autofit-gating.test.ts
- packages/docs/src/view/text-box-editor.ts
| > `transformLayoutBlocks` hook for `shrink`. Absent `autofit` ⇒ `grow` | ||
| > (not `none`) to preserve the shipped auto-grow default. See the design | ||
| > doc and `*-lessons.md` (lesson 8). | ||
|
|
There was a problem hiding this comment.
Fix MD028 blockquote formatting warning.
There is a blank line inside the opening blockquote; markdownlint flags this and it can fail docs lint gates.
Proposed fix
> doc and `*-lessons.md` (lesson 8).
-
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| > doc and `*-lessons.md` (lesson 8). | |
| > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. |
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 13-13: Blank line inside blockquote
(MD028, no-blanks-blockquote)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/tasks/active/20260525-slides-text-autofit-todo.md` at line 13, The
markdown has a blockquote with an extra blank line after the '>' that triggers
MD028; edit the blockquote in
docs/tasks/active/20260525-slides-text-autofit-todo.md to remove the empty line
immediately after the opening '>' so the quote's content starts on the next line
without a blank line (ensure any nested content remains correctly indented and
that no other leading/trailing blank lines remain inside that blockquote).
| * Autofit behavior. Absent ⇒ 'none' so documents created before this | ||
| * field keep their current fixed-size rendering (no migration). | ||
| */ |
There was a problem hiding this comment.
Align default semantics in the plan with shipped behavior.
Line 85 and Line 803 state “absent ⇒ none”, which conflicts with the reconciliation note (Line 10) and the implemented behavior (“absent ⇒ grow”). Please update these sections to a single source of truth to avoid future regressions.
Also applies to: 803-803
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/tasks/active/20260525-slides-text-autofit-todo.md` around lines 85 - 87,
Update the doc so the default autofit semantics are consistent: change the two
occurrences that read “absent ⇒ 'none'” (the "Autofit behavior" paragraph and
the paragraph at line ~803) to match the reconciliation note and implemented
behavior by using “absent ⇒ grow”; ensure the reconciliation note (the
top-of-file note referenced on Line 10) remains accurate or is adjusted to the
same “absent ⇒ grow” wording so all three places (the "Autofit behavior"
section, the later paragraph around 803, and the reconciliation note) state the
same default semantics.
| const { scale } = options; | ||
| const x = element.frame.x * scale; | ||
| const y = (element.frame.y + element.frame.h) * scale + AUTOFIT_TOGGLE_OFFSET; |
There was a problem hiding this comment.
Position the toggle using rotated geometry for rotated text boxes.
At Line 758, the toggle anchor is computed from frame.x/frame.y/frame.h only. For frame.rotation !== 0, this is not the visual bottom-left corner, so the button can render in the wrong place.
Suggested patch
const { scale } = options;
- const x = element.frame.x * scale;
- const y = (element.frame.y + element.frame.h) * scale + AUTOFIT_TOGGLE_OFFSET;
+ const f = element.frame;
+ const cx = f.x + f.w / 2;
+ const cy = f.y + f.h / 2;
+ const cos = Math.cos(f.rotation);
+ const sin = Math.sin(f.rotation);
+ const localBottomLeft = { x: 0, y: f.h };
+ const dx = localBottomLeft.x - f.w / 2;
+ const dy = localBottomLeft.y - f.h / 2;
+ const worldBottomLeft = {
+ x: cx + dx * cos - dy * sin,
+ y: cy + dx * sin + dy * cos,
+ };
+ const x = worldBottomLeft.x * scale;
+ const y = worldBottomLeft.y * scale + AUTOFIT_TOGGLE_OFFSET;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const { scale } = options; | |
| const x = element.frame.x * scale; | |
| const y = (element.frame.y + element.frame.h) * scale + AUTOFIT_TOGGLE_OFFSET; | |
| const { scale } = options; | |
| const f = element.frame; | |
| const cx = f.x + f.w / 2; | |
| const cy = f.y + f.h / 2; | |
| const cos = Math.cos(f.rotation); | |
| const sin = Math.sin(f.rotation); | |
| const localBottomLeft = { x: 0, y: f.h }; | |
| const dx = localBottomLeft.x - f.w / 2; | |
| const dy = localBottomLeft.y - f.h / 2; | |
| const worldBottomLeft = { | |
| x: cx + dx * cos - dy * sin, | |
| y: cy + dx * sin + dy * cos, | |
| }; | |
| const x = worldBottomLeft.x * scale; | |
| const y = worldBottomLeft.y * scale + AUTOFIT_TOGGLE_OFFSET; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/slides/src/view/editor/overlay.ts` around lines 758 - 760, The
toggle's anchor is computed from unrotated frame coordinates (using
element.frame.x/y/h), so rotated text boxes place the toggle incorrectly; update
the calculation in overlay.ts to account for element.frame.rotation by computing
the visual bottom-left corner after rotation (e.g., transform the frame's
bottom-left corner around the frame center using element.frame.rotation and then
apply the scale and AUTOFIT_TOGGLE_OFFSET) and use that rotated, scaled point
for x/y instead of the raw frame.x/frame.y/frame.h values; reference the
existing variables element, options (scale) and AUTOFIT_TOGGLE_OFFSET when
locating and replacing the calculation.
| 'aria-label', | ||
| current === 'shrink' | ||
| ? 'Autofit mode: shrink text. Click to switch to resize shape.' | ||
| : 'Autofit mode: resize shape. Click to switch to shrink text.', | ||
| ); |
There was a problem hiding this comment.
Handle 'none' mode in the aria-label text.
At Line 775, aria-label has only shrink/grow branches, so when current === 'none' screen readers announce it as resize-shape mode, which is inaccurate.
Suggested patch
btn.setAttribute(
'aria-label',
current === 'shrink'
? 'Autofit mode: shrink text. Click to switch to resize shape.'
- : 'Autofit mode: resize shape. Click to switch to shrink text.',
+ : current === 'grow'
+ ? 'Autofit mode: resize shape. Click to switch to shrink text.'
+ : 'Autofit mode: off. Click to enable resize shape to fit text.',
);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| 'aria-label', | |
| current === 'shrink' | |
| ? 'Autofit mode: shrink text. Click to switch to resize shape.' | |
| : 'Autofit mode: resize shape. Click to switch to shrink text.', | |
| ); | |
| 'aria-label', | |
| current === 'shrink' | |
| ? 'Autofit mode: shrink text. Click to switch to resize shape.' | |
| : current === 'grow' | |
| ? 'Autofit mode: resize shape. Click to switch to shrink text.' | |
| : 'Autofit mode: off. Click to enable resize shape to fit text.', | |
| ); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/slides/src/view/editor/overlay.ts` around lines 775 - 779, The
aria-label currently only distinguishes 'shrink' vs resize and misreports when
current === 'none'; update the conditional that sets the 'aria-label' (the
expression using current === 'shrink' in overlay.ts) to explicitly handle 'none'
(e.g., three-way conditional or switch) and return an accurate label for 'none'
such as "Autofit mode: none. Click to switch to resize shape." so screen readers
get the correct state.
PRs #257/#301/#303/#304/#305/#306/#307/#309/#315/#316/#317/#321/#322 shipped between v0.4.2 and the v0.4.3 cut, leaving their paired todo files in active with the process checkboxes still open. Flip the remaining boxes for the work that landed, then move the paired todo/lessons into docs/tasks/archive/2026/05/ via pnpm tasks:archive. Active count drops from 17 to 6; archive count grows by 21 files. Patch release covering 29 commits since v0.4.2. Headline theme is Slides editing & UX polish toward Google Slides / PowerPoint parity: smart guides (equal-spacing / distance / size), Shift drag modifiers, text inside shapes, a Format options right panel, tier-1 universal toolbar controls, text autofit (shrink + grow), group-selection visuals, and several smaller text-box / selection fixes. PPTX import gained paragraph-level bullet styles, body anchor (vertical alignment), and stretched blipFill cover-crop. Docs added a pending-inline-style at collapsed caret, a caret that tracks the resolved text color, bullet indent on Tab / Shift+Tab, font / size / line-spacing / clear formatting toolbar controls, an inline-style-attribute clear that actually lands in the CRDT, and a polished toolbar trigger / color swatch pass shared across docs / sheets / slides. Sheets gained .xlsx import. Infrastructure: Docker images now build on native arm64 runners, fixing the release hang seen on v0.4.2. No Prisma migration, no new backend env vars — the devops bump is a routine image-tag change. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Summary
Layers the remaining two text-box autofit modes on top of the auto-grow
feature that recently shipped (
slides-textbox-autogrow), giving slidesGoogle-Slides/PowerPoint parity, including the in-context bottom-left
mode toggle users have on selected text elements in GS.
AutofitModefield ('none' | 'shrink' | 'grow') onTextElement.data.autofit. Absent ⇒growso existing decks keepthe shipped auto-grow behavior;
nonemust be set explicitly (API /PPTX import only).
shrinkmode — fonts auto-scale down to fit a fixed box (theoriginal request: "text size changes with the amount of content"), via
a deterministic binary-search engine (
model/autofit.ts) shared by thecommitted canvas renderer and the in-place editor (pixel-identical).
grow— the wrapper gates main'sonContentHeightChangeto grow/absent and wires a new docstransformLayoutBlockshook only for shrink;noneis fixed.text element flips between
growandshrinkon click (GS-parityaffordance). Wired through
OverlayOptions.onAutofitToggle→ a singlestore.updateElementDatapatch; renderer + editor react on the nextrepaint. Hidden during in-place editing.
shrink, inserted text boxes →grow. Preserved through Mem-store stamping and the productionYorkie store (addElement / addSlide / undo-redo restore; the restore
path also regains the previously-dropped
placeholderRef).<a:bodyPr>autofit child mapped on import (keeps the existingnormAutofitfontScale baking).Shrink scale is derived live and never stored — a pure function of
(blocks, frame, font metrics), so collaborators agree without syncing.Design:
docs/design/slides/slides-text-autofit.md.Test plan
Automated (all green via
pnpm verify:self— lint, all package tests,all builds, knip dead-code=0, doc-staleness=0):
TextMeasurer): scale binary search, identity preservation, floor, empty content.shrinkwires onlytransformLayoutBlocks;grow/absent onlyonContentHeightChange;noneneither.shrinkviaaddSlide+applyLayoutToSlide; inserted boxgrow.autofitsurvives insert + add-slide + undo/redo in the production store.detectAutofitModemapping (all five cases).transformLayoutBlockshook fires layout-only; docs editor unaffected when absent.Manual (pending before merge):
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
Tests