Skip to content

Add slides text autofit: shrink mode + GS-style toggle UI#305

Merged
hackerwins merged 16 commits into
mainfrom
slides-text-autofit
May 27, 2026
Merged

Add slides text autofit: shrink mode + GS-style toggle UI#305
hackerwins merged 16 commits into
mainfrom
slides-text-autofit

Conversation

@hackerwins

@hackerwins hackerwins commented May 25, 2026

Copy link
Copy Markdown
Collaborator

Summary

Layers the remaining two text-box autofit modes on top of the auto-grow
feature that recently shipped (slides-textbox-autogrow), giving slides
Google-Slides/PowerPoint parity, including the in-context bottom-left
mode toggle
users have on selected text elements in GS.

  • Adds a persisted AutofitMode field ('none' | 'shrink' | 'grow') on
    TextElement.data.autofit. Absent ⇒ grow so existing decks keep
    the shipped auto-grow behavior; none must be set explicitly (API /
    PPTX import only).
  • New shrink mode — fonts auto-scale down to fit a fixed box (the
    original request: "text size changes with the amount of content"), via
    a deterministic binary-search engine (model/autofit.ts) shared by the
    committed canvas renderer and the in-place editor (pixel-identical).
  • Reuses the existing auto-grow for grow — the wrapper gates main's
    onContentHeightChange to grow/absent and wires a new docs
    transformLayoutBlocks hook only for shrink; none is fixed.
  • Mode toggle UI — a small button at the bottom-left of a selected
    text element flips between grow and shrink on click (GS-parity
    affordance). Wired through OverlayOptions.onAutofitToggle → a single
    store.updateElementData patch; renderer + editor react on the next
    repaint. Hidden during in-place editing.
  • Parity defaults: layout placeholders → shrink, inserted text boxes →
    grow. Preserved through Mem-store stamping and the production
    Yorkie store (addElement / addSlide / undo-redo restore; the restore
    path also regains the previously-dropped placeholderRef).
  • PPTX <a:bodyPr> autofit child mapped on import (keeps the existing
    normAutofit fontScale 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):

  • Engine unit tests (fake TextMeasurer): scale binary search, identity preservation, floor, empty content.
  • Mode-gating test: shrink wires only transformLayoutBlocks; grow/absent only onContentHeightChange; none neither.
  • Defaults end-to-end: placeholders shrink via addSlide + applyLayoutToSlide; inserted box grow.
  • Mem-vs-Yorkie equivalence: autofit survives insert + add-slide + undo/redo in the production store.
  • PPTX detectAutofitMode mapping (all five cases).
  • docs transformLayoutBlocks hook fires layout-only; docs editor unaffected when absent.
  • Autofit-toggle button: renders only for single text element + callback, click cycles grow ⇄ shrink, absent ⇒ grow, none → grow, hidden on shapes / multi-select.

Manual (pending before merge):

  • `pnpm dev` smoke: insert box → auto-grows while typing; click bottom-left toggle → switches to shrink, fonts scale; click again → back to grow; imported PPTX re-engages on edit.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Text autofit modes added: shrink, grow, none — placeholders default to shrink; new inserts default to grow; grow writes final height, shrink scales text to fit.
    • In-editor autofit toggle to switch modes while editing; PPTX import now maps PowerPoint autofit to these modes.
  • Documentation

    • New design, plan, and lessons docs describing autofit behavior, defaults, and verification.
  • Tests

    • Expanded tests covering autofit model, editor UX, import mapping, store persistence, and equivalence across stores.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 25, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR implements a three-mode text autofit feature (none, shrink, grow) for slide text elements. It adds type definitions, a binary-search shrink-scale engine, default seeding for placeholders and inserted text, Yorkie persistence, PPTX import mapping, canvas rendering support, in-place editor live-scaling via a new docs hook, and a toggle UI for mode switching.

Changes

Text Autofit Feature Implementation

Layer / File(s) Summary
Design specification and AutofitMode type
docs/design/slides/slides-text-autofit.md, docs/design/README.md, packages/slides/src/model/element.ts, packages/slides/src/index.ts
Design doc and README entry introduce three-mode autofit and add export type AutofitMode = 'none' | 'shrink' | 'grow' on TextElement.data with documented defaulting semantics.
Autofit scale computation engine
packages/slides/src/model/autofit.ts, packages/slides/test/model/autofit.test.ts
Implements scaleBlocks (scales vertical margins and inline font sizes) and computeAutofitScale (fixed-iteration binary search to find largest fitting shrink scale), with tests for scaling math and bounds.
PPTX autofit import mapping
packages/slides/src/import/pptx/text.ts, packages/slides/src/import/pptx/shape.ts, packages/slides/test/import/pptx/text.test.ts
Adds detectAutofitMode to map PPTX <a:bodyPr> autofit children to AutofitMode (normAutofit → shrink, spAutoFit → grow, absent/noAutofit → none) and applies it during text-element import.
Placeholder defaults and text seeding
packages/slides/src/model/layout.ts, packages/slides/src/store/memory.ts, packages/slides/src/view/editor/interactions/insert.ts, packages/slides/test/model/layout.test.ts, packages/slides/test/store/memory.test.ts, packages/slides/test/view/editor/interactions/insert-defaults.test.ts, packages/slides/test/model/layout-apply.test.ts
Seeds placeholders with autofit: 'shrink', preserves existing data fields when seeding blocks, and sets inserted text elements to autofit: 'grow', with tests asserting preserved/expected defaults.
Yorkie schema and undo/redo persistence
packages/frontend/src/types/slides-document.ts, packages/frontend/src/app/slides/yorkie-slides-store.ts, packages/frontend/tests/app/slides/yorkie-slides-equivalence.test.ts
Adds autofit?: AutofitMode to YorkieTextElement types and preserves data.autofit and placeholderRef across add, replaceRoot (undo/redo), and slide creation; verifies equivalence with MemSlidesStore.
Canvas rendering with shrink scaling
packages/slides/src/view/canvas/text-renderer.ts
drawText computes a shrink scale when data.autofit === 'shrink', applies scaleBlocks to normalized blocks, and relays out scaled blocks before rendering.
Docs text-box editor layout transform hook
packages/docs/src/view/text-box-editor.ts, packages/docs/test/view/text-box-editor.test.ts
Adds transformLayoutBlocks?: (blocks: Block[]) => Block[] to TextBoxEditorOptions and uses it in initializeTextBox.recomputeLayout so editors can apply transient layout transforms (e.g., shrink) without persisting them.
Slides text-box editor mode-aware wiring
packages/slides/src/view/editor/text-box-editor.ts, packages/slides/test/view/text-box-autofit-gating.test.ts
Extends mountSlidesTextBox to accept autofit and conditionally wire transformLayoutBlocks for shrink or onContentHeightChange for grow, with a cached CanvasTextMeasurer for scale computation.
Editor callback and entry point
packages/slides/src/view/editor/editor.ts
Wires onAutofitToggle into overlay rendering to batch-update element data.autofit and passes the element's current autofit into the mounted text-box editor on edit entry.
Autofit toggle UI
packages/slides/src/view/editor/overlay.ts, packages/slides/test/view/editor/autofit-toggle.test.ts
Renders a bottom-left autofit toggle for a single selected text element (when callback provided) that cycles grow ↔ shrink on click, treats absent/none correctly, prevents event propagation, and invokes the callback with next mode.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • wafflebase/wafflebase#298: Adds onContentHeightChange/setContentHeight mechanism for grow-mode live editing and commit-time frame.h persistence; related to grow-mode wiring extended here.
  • wafflebase/wafflebase#197: Introduced placeholder pipeline and placeholderRef identity; this PR preserves placeholderRef and adds autofit persistence during seeding/restore.

Poem

🐰 A rabbit pens a tiny note,
Fonts shrink and tall frames gently float,
Placeholders tuck, new boxes grow,
Toggle twitches to and fro,
Yorkie keeps the seeds we sow.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely summarizes the main changes: adding a shrink mode to text autofit and introducing a toggle UI for mode selection, which are the primary features of this PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch slides-text-autofit

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented May 25, 2026

Copy link
Copy Markdown
Contributor

Verification: verify:self

Result: ✅ PASS in 226.1s

Lane Status Duration
tokens:build ✅ pass 2.1s
sheets:build ✅ pass 13.5s
docs:build ✅ pass 11.3s
slides:build ✅ pass 12.9s
verify:fast ✅ pass 145.4s
frontend:build ✅ pass 17.3s
verify:frontend:chunks ✅ pass 0.3s
backend:build ✅ pass 4.8s
cli:build ✅ pass 2.0s
verify:entropy ✅ pass 16.4s

Verification: verify:integration

Result: ✅ PASS

@codecov

codecov Bot commented May 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@hackerwins

Copy link
Copy Markdown
Collaborator Author

Code review

No 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 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 1781df0 and ea422bb.

📒 Files selected for processing (29)
  • docs/design/README.md
  • docs/design/slides/slides-text-autofit.md
  • docs/tasks/active/20260525-slides-text-autofit-lessons.md
  • docs/tasks/active/20260525-slides-text-autofit-todo.md
  • packages/docs/src/view/text-box-editor.ts
  • packages/docs/test/view/text-box-editor.test.ts
  • packages/frontend/src/app/slides/yorkie-slides-store.ts
  • packages/frontend/src/types/slides-document.ts
  • packages/frontend/tests/app/slides/yorkie-slides-equivalence.test.ts
  • packages/slides/src/import/pptx/shape.ts
  • packages/slides/src/import/pptx/text.ts
  • packages/slides/src/index.ts
  • packages/slides/src/model/autofit.ts
  • packages/slides/src/model/element.ts
  • packages/slides/src/model/layout.ts
  • packages/slides/src/store/memory.ts
  • packages/slides/src/view/canvas/text-renderer.ts
  • packages/slides/src/view/editor/editor.ts
  • packages/slides/src/view/editor/interactions/insert.ts
  • packages/slides/src/view/editor/overlay.ts
  • packages/slides/src/view/editor/text-box-editor.ts
  • packages/slides/test/import/pptx/text.test.ts
  • packages/slides/test/model/autofit.test.ts
  • packages/slides/test/model/layout-apply.test.ts
  • packages/slides/test/model/layout.test.ts
  • packages/slides/test/store/memory.test.ts
  • packages/slides/test/view/editor/autofit-toggle.test.ts
  • packages/slides/test/view/editor/interactions/insert-defaults.test.ts
  • packages/slides/test/view/text-box-autofit-gating.test.ts

Comment thread packages/slides/src/view/editor/editor.ts
hackerwins and others added 16 commits May 28, 2026 00:58
(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]>
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]>
@hackerwins
hackerwins force-pushed the slides-text-autofit branch from 62fa0fc to b01fcd1 Compare May 27, 2026 16:03

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between ea422bb and b01fcd1.

📒 Files selected for processing (29)
  • docs/design/README.md
  • docs/design/slides/slides-text-autofit.md
  • docs/tasks/active/20260525-slides-text-autofit-lessons.md
  • docs/tasks/active/20260525-slides-text-autofit-todo.md
  • packages/docs/src/view/text-box-editor.ts
  • packages/docs/test/view/text-box-editor.test.ts
  • packages/frontend/src/app/slides/yorkie-slides-store.ts
  • packages/frontend/src/types/slides-document.ts
  • packages/frontend/tests/app/slides/yorkie-slides-equivalence.test.ts
  • packages/slides/src/import/pptx/shape.ts
  • packages/slides/src/import/pptx/text.ts
  • packages/slides/src/index.ts
  • packages/slides/src/model/autofit.ts
  • packages/slides/src/model/element.ts
  • packages/slides/src/model/layout.ts
  • packages/slides/src/store/memory.ts
  • packages/slides/src/view/canvas/text-renderer.ts
  • packages/slides/src/view/editor/editor.ts
  • packages/slides/src/view/editor/interactions/insert.ts
  • packages/slides/src/view/editor/overlay.ts
  • packages/slides/src/view/editor/text-box-editor.ts
  • packages/slides/test/import/pptx/text.test.ts
  • packages/slides/test/model/autofit.test.ts
  • packages/slides/test/model/layout-apply.test.ts
  • packages/slides/test/model/layout.test.ts
  • packages/slides/test/store/memory.test.ts
  • packages/slides/test/view/editor/autofit-toggle.test.ts
  • packages/slides/test/view/editor/interactions/insert-defaults.test.ts
  • packages/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).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

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.

Suggested change
> 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).

Comment on lines +85 to +87
* Autofit behavior. Absent ⇒ 'none' so documents created before this
* field keep their current fixed-size rendering (no migration).
*/

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

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.

Comment on lines +758 to +760
const { scale } = options;
const x = element.frame.x * scale;
const y = (element.frame.y + element.frame.h) * scale + AUTOFIT_TOGGLE_OFFSET;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

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.

Suggested change
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.

Comment on lines +775 to +779
'aria-label',
current === 'shrink'
? 'Autofit mode: shrink text. Click to switch to resize shape.'
: 'Autofit mode: resize shape. Click to switch to shrink text.',
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

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.

Suggested change
'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.

@hackerwins hackerwins changed the title Slides text autofit: add shrink mode + 3-mode selector on auto-grow Add slides text autofit: shrink mode + GS-style toggle UI May 27, 2026
@hackerwins
hackerwins merged commit 3df5f16 into main May 27, 2026
3 checks passed
@hackerwins
hackerwins deleted the slides-text-autofit branch May 27, 2026 16:16
hackerwins added a commit that referenced this pull request May 31, 2026
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]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant