Skip to content

Import + render PPTX <a:bodyPr anchor> with menu control#307

Merged
hackerwins merged 20 commits into
mainfrom
slides-pptx-text-vertical-anchor
May 29, 2026
Merged

Import + render PPTX <a:bodyPr anchor> with menu control#307
hackerwins merged 20 commits into
mainfrom
slides-pptx-text-vertical-anchor

Conversation

@hackerwins

@hackerwins hackerwins commented May 29, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Parse OOXML <a:bodyPr anchor="t |ctr|b"> into a new sparse TextElement.data.verticalAnchor field (top / middle / bottom). Absent preserves the previous top-anchored behavior — no migration for existing decks.
  • Apply a paint-origin y offset in the slide canvas renderer (computeVerticalOriginY in text-renderer.ts) and in the docs in-place text-box editor so paint, caret, selection rectangles, and click hit-test all align with the configured anchor. Imported titles in Yorkie, 캐즘 뛰어넘기.pptx slide 1 (and similar PPTX decks that bottom-anchor a tall placeholder) now render at the bottom of the frame instead of floating ~2" above.
  • Expose verticalAnchor via three "Align text top / middle / bottom" items in the slides context menu when a single TextElement is selected — Stage 1 of the UI rollout. Items mark the current value with a check-mark column (via a new opt-in ContextMenuItem.selected field), short-circuit idempotent clicks, and write through store.batch so each change is a single undo step. Toolbar / side-panel surfaces remain explicit follow-ups.

Test plan

  • pnpm verify:fast — lint + 803 docs + 1420 slides unit tests, 1 documented skip (jsdom canvas-geometry limitation; algebra verified in JSDoc + Playwright handoff noted)
  • Importer (detectVerticalAnchor): covers t/ctr/b/just/dist, missing bodyPr, missing/empty anchor attr; integration spec round-trips through parseSpTree
  • Canvas renderer (drawText + computeVerticalOriginY): all three anchors land in the expected band on a 200 px frame, overflow clamps to top, default (absent) path unchanged
  • Docs in-place editor: currentOriginY eagerly initialized before TextEditor is constructed (no first-render race); setContentHeight resyncs; click hit-test math (currentOriginY - Theme.pageGap) * scale reduces to -pageGap * scale for the default case (proven by walk-through + existing tests)
  • Context menu: items shown only for single-TextElement selections; current anchor marked selected; multi-select / non-text selections hide the items; idempotent click guard verified
  • Backward compat: existing decks without verticalAnchor render identically; existing menus without ContextMenuItem.selected render identically (existing specs unchanged)
  • Manual smoke: import Yorkie, 캐즘 뛰어넘기.pptx via pnpm dev, confirm slide 1 title sits at the bottom of its placeholder, enter edit mode and verify the caret stays aligned
  • Manual smoke: right-click an imported anchored title, confirm the radio shows the active anchor; flip between top / middle / bottom and verify the committed canvas and the in-place editor move together

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for vertical text alignment (top/middle/bottom) when importing PPTX files; vertical anchoring is now preserved and rendered correctly across the slide canvas and in-place text editor.
    • Added vertical text alignment options to the editor context menu for quick access.
  • Tests

    • Added comprehensive test coverage for vertical text anchoring in import, rendering, and editor functionality.
  • Documentation

    • Added design and implementation plan documentation for vertical text anchoring feature.

Review Change Stack

hackerwins and others added 18 commits May 29, 2026 19:21
Add a todo plan for parsing PPTX <a:bodyPr anchor> into a slides
TextElement.verticalAnchor field and rendering the offset so
imported title placeholders sit at the bottom of their oversized
frames as the source decks intend.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Mirrors OOXML <a:bodyPr anchor>. Sparse field — absent preserves
existing top-anchored behavior, so old documents and tests remain
valid without migration.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Mirrors the AutofitMode pattern so the PPTX importer and canvas
renderer can reference a single type symbol instead of duplicating
the inline union.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Source decks anchor title placeholders at the bottom of an oversized
frame (anchor="b"). Without parsing this, imported titles render
~2" above where the source shows them.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Treat empty-string anchor="" as absent rather than silently
overriding inheritance with 'top'. Tighten the null guard to
match attr()'s actual return type. Cover 'dist' alongside 'just'
in the unsupported-fallback test, and drop a redundant import
alias in the test file.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
drawText now measures laid-out content height and translates the
paint origin so text sits at the top/middle/bottom of the frame.
Imported PPTX titles with anchor="b" finally render at the bottom
of their oversized placeholder boxes instead of floating ~2" above.

Clamps to >= 0 so overflow content stays visible at the top edge
rather than getting clipped above the frame.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Notes the shrink-path padding assumption that keeps originY non-
negative, and the deliberate decision to paint the empty-placeholder
hint at the top regardless of verticalAnchor. Tightens the overflow
test so the assertion catches a spurious offset rather than just
checking the line baseline is inside the frame.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Adds verticalAnchor to the model-support matrix and records the
in-place editor parity gap that Chunk 3 will close.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Mirrors the slides canvas-renderer offset so paintLayout, the
caret position, the selection rectangles, and the click hit-test
all align with the configured anchor. Without this, editing a
bottom-anchored title snaps the text to the top of the frame.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Closes a narrow click-race window: TextEditor wires its pointer
handler at construction, but currentOriginY was only set inside
renderNow's first rAF. A click before that frame would resolve
hit-test as top-anchored regardless of the actual verticalAnchor.
Initialise eagerly at construction and also after setContentHeight
so the closure read always reflects the current frame.

Also tightens the middle-anchor test bound and marks the deferred
pointer-event hit-test gap.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Closes the "text snaps to top while editing" gap left by the
prior commits. The slides wrapper forwards verticalAnchor to
initializeTextBox so the editing surface applies the same paint
and hit-test offsets as the committed slide canvas.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Chunk 3 of the verticalAnchor work is now in: docs editor honors
the offset and the slides wrapper threads element.data.verticalAnchor
through, so the read-only render and the in-place editor agree.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
The getCanvasOffsetTop callback was returning -(pageGap+originY)*scale,
which double-applies the anchor offset: a click on a bottom-anchored
title's visible text resolved to a layout-y far past the last line,
sending the cursor to the wrong block.

Correct formula is (originY-pageGap)*scale: a click at host-y =
originY*scale produces py = pageGap, so localY = 0 — the very start
of the layout. Reduces to -pageGap*scale when originY = 0, preserving
the default-path behavior for docs/sheets callers.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Stage 1 of the verticalAnchor UI exposure — expose the field via
three "Align text top/middle/bottom" items in the existing slides
context menu, reusing Store.updateElementData. Toolbar and side
panel surfaces remain follow-ups.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Prefixes the active choice in radio-group-style menus with a
check-mark glyph (and a matching spacer on non-selected items so
labels stay column-aligned). No effect on menus that don't set
the new field. Used by the upcoming "Align text" items.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Exposes TextElement.data.verticalAnchor for user editing. Items
appear only when a single TextElement is selected; multi-select
and non-text selections hide them so the action target is
unambiguous. The current value is marked with the new
ContextMenuItem.selected indicator — "Top" reads as active for
unset (default-top) text boxes.

Closes Stage 1 of the verticalAnchor UI rollout. Toolbar and side
panel surfaces are tracked separately.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Items that leave selected=undefined no longer get the 3-space
spacer when a sibling radio group is present — the column was
leaking onto Copy/Cut/Paste/etc. when a single text element was
selected.

Also short-circuits a no-op verticalAnchor write so clicking the
currently-selected anchor doesn't add a spurious undo entry.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Code-review polish: the skipped pointer-event spec's body never ran
and only inflated the file; collapse to a one-liner with the
rationale on top. Surface the drawHint placeholder-ghost-text
top-anchor carve-out in the design doc so the limitation is visible
without re-deriving it from source.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@hackerwins, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 44 minutes and 21 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4834dc10-0684-4b62-b102-aca4f8b9b4d3

📥 Commits

Reviewing files that changed from the base of the PR and between 893eae6 and 3012f6a.

📒 Files selected for processing (8)
  • docs/design/slides/slides-themes-layouts-import.md
  • docs/tasks/README.md
  • docs/tasks/archive/2026/05/20260529-slides-pptx-text-vertical-anchor-lessons.md
  • docs/tasks/archive/2026/05/20260529-slides-pptx-text-vertical-anchor-todo.md
  • docs/tasks/archive/2026/05/20260529-slides-text-vertical-align-menu-lessons.md
  • docs/tasks/archive/2026/05/20260529-slides-text-vertical-align-menu-todo.md
  • docs/tasks/archive/README.md
  • packages/slides/test/view/editor/editor.test.ts
📝 Walkthrough

Walkthrough

This PR implements end-to-end PPTX vertical text anchoring support: parsing <a:bodyPr anchor> during import, storing as optional VerticalAnchorMode field on TextElement, rendering with origin-offset calculation in both slide canvas and in-place editor, and exposing UI controls via context menu with radio-group selection indicator.

Changes

Vertical Text Anchor Feature

Layer / File(s) Summary
Model and Type Definitions
packages/slides/src/model/element.ts, packages/slides/src/index.ts
Introduces VerticalAnchorMode = 'top' | 'middle' | 'bottom' type and adds optional verticalAnchor field to TextElement.data; re-exports type from package entrypoint for consumers.
PPTX Import Parsing and Wiring
packages/slides/src/import/pptx/text.ts, packages/slides/src/import/pptx/shape.ts, packages/slides/test/import/pptx/text.test.ts
New detectVerticalAnchor(txBody) function reads <a:bodyPr anchor> OOXML attribute and maps values (bbottom, ctrmiddle, ttop, absent→undefined) to VerticalAnchorMode; integrated into buildTextElement to include parsed anchor in TextElement.data; unit and integration tests verify mapping and import wiring.
Slide Canvas Text Rendering
packages/slides/src/view/canvas/text-renderer.ts, packages/slides/test/view/canvas/text-renderer.test.ts
New computeVerticalOriginY() helper computes clamped vertical offset based on anchor, frame height, and content height; drawText now passes originY to paintLayout instead of fixed zero; placeholder ghost text remains top-anchored with clarifying comments; tests assert paint baselines near top/middle/bottom and verify clamping when content overflows.
Context Menu Radio-Group Support
packages/slides/src/view/editor/context-menu.ts, packages/slides/test/view/editor/context-menu.test.ts
ContextMenuItem gains optional selected boolean field; showContextMenu prefixes labels with , three spaces, or omits glyph based on selected state for radio-group styling; tests verify glyph rendering, indentation handling, and click behavior.
In-Place Editor Vertical Anchoring
packages/docs/src/view/text-box-editor.ts, packages/docs/test/view/text-box-editor.test.ts
TextBoxEditorOptions adds optional verticalAnchor field; computes and caches currentOriginY on init and during render after layout; passes origin to paintLayout for text/cursor/selection rendering; updates click hit-test shim to use (currentOriginY - Theme.pageGap) * scale; comprehensive test harness stubs canvas/RAF and verifies paint baselines for all anchor modes and overflow clamping.
Editor UI Integration
packages/slides/src/view/editor/text-box-editor.ts, packages/slides/src/view/editor/editor.ts, packages/slides/test/view/editor/editor.test.ts
Slides text-box wrapper forwards verticalAnchor to docs editor; elementContextItems adds "Align text top/middle/bottom" menu for single-selected text elements, marking current anchor as selected and updating via store.updateElementData in batch; text-box mount options include verticalAnchor mirrored from element; tests verify menu visibility, selection state, and undo behavior.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • wafflebase/wafflebase#256: Modifies the getCanvasOffsetTop click-hit-test y-offset formula in docs text editor; this PR updates the same formula to account for currentOriginY vertical anchor offset alongside scale.
  • wafflebase/wafflebase#305: Adds autofit='shrink' layout scaling to slides canvas drawText; both PRs modify the same text-renderer.ts render pipeline, so integration testing of overflow/clamping behavior together would be valuable.

Poem

🐰 Anchors aweigh through OOXML's sea,
Text floats now top, middle, or bottom—so free!
From import to canvas, the editor knows,
Where every letter vertically goes.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.67% 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.
Title check ✅ Passed The pull request title accurately describes the main feature: importing PPTX vertical text anchoring (a:bodyPr anchor) and exposing it via menu control, matching the core changes across model, importer, renderer, and editor components.
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.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch slides-pptx-text-vertical-anchor

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 29, 2026

Copy link
Copy Markdown
Contributor

Verification: verify:self

Result: ✅ PASS in 248.7s

Lane Status Duration
tokens:build ✅ pass 2.2s
sheets:build ✅ pass 14.1s
docs:build ✅ pass 12.7s
slides:build ✅ pass 14.6s
verify:fast ✅ pass 159.8s
frontend:build ✅ pass 19.1s
verify:frontend:chunks ✅ pass 0.3s
backend:build ✅ pass 5.2s
cli:build ✅ pass 2.2s
verify:entropy ✅ pass 18.4s

Verification: verify:integration

Result: ✅ PASS

@codecov

codecov Bot commented May 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Capture lessons from the import + render + editor parity work and
the Stage 1 menu rollout: click hit-test math is the real failure
point (not paint), paintLayout propagates originY internally,
first-render closure variables need eager init, and radio-group
indicators must be per-item opt-in.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

@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: 2

🧹 Nitpick comments (2)
packages/slides/test/view/editor/editor.test.ts (2)

2400-2403: ⚡ Quick win

Consider importing the actual ContextMenuItem type instead of duplicating it.

The local ContextItemLike type duplicates the interface definition and could diverge from the actual ContextMenuItem type if the implementation adds or changes fields. Importing and using the real type would catch interface changes at compile time.

♻️ Refactor to use the actual type
+import type { ContextMenuItem } from '../../../src/view/editor/context-menu';
+
-  type ContextItemLike = { label: string; selected?: boolean; run: () => void };
   type EditorWithContextItems = {
-    elementContextItems(slideId: string): ReadonlyArray<ContextItemLike>;
+    elementContextItems(slideId: string): ReadonlyArray<ContextMenuItem>;
   };
 
-  function getContextItems(ed: SlidesEditor, slideId: string): ReadonlyArray<ContextItemLike> {
+  function getContextItems(ed: SlidesEditor, slideId: string): ReadonlyArray<ContextMenuItem> {
     return (ed as unknown as EditorWithContextItems).elementContextItems(slideId);
   }
🤖 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/test/view/editor/editor.test.ts` around lines 2400 - 2403,
Replace the duplicated local type with the real exported type: import
ContextMenuItem and change the local declarations — remove ContextItemLike and
update EditorWithContextItems.elementContextItems to return
ReadonlyArray<ContextMenuItem> (and adjust any usage that referenced the local
run/selected fields to the actual ContextMenuItem shape). Ensure the import is
added at the top of the test file and update any mock objects to satisfy the
real ContextMenuItem interface.

2465-2465: ⚡ Quick win

Use .toBe(false) for consistency with other boolean assertions.

This assertion uses .toBeFalsy() while similar checks in this suite (lines 2438, 2464) use .toBe(true). For boolean flags, .toBe(false) is more precise and maintains consistency.

♻️ Suggested fix
-    expect(items.find((it) => it.label === 'Align text top')?.selected).toBeFalsy();
+    expect(items.find((it) => it.label === 'Align text top')?.selected).toBe(false);
🤖 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/test/view/editor/editor.test.ts` at line 2465, Replace the
loose falsy assertion with an explicit boolean check to match the rest of the
suite: change the assertion that checks items.find((it) => it.label === 'Align
text top')?.selected from using .toBeFalsy() to .toBe(false) so the boolean flag
is asserted precisely (target the test in editor.test.ts where the
items.find(... 'Align text top')?.selected is asserted).
🤖 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/design/slides/slides-themes-layouts-import.md`:
- Line 438: The table row for `<a:bodyPr anchor>` is missing a third cell (it's
a 2-cell entry in a 3-column table), which breaks Markdown table alignment;
update that row in docs/design/slides/slides-themes-layouts-import.md to include
the missing third column (either an appropriate support/status value or an
explicit empty placeholder like an em-dash) so the row has three pipe-separated
cells, keeping the existing text referencing TextElement.data.verticalAnchor,
detectVerticalAnchor, and computeVerticalOriginY intact.

In `@packages/slides/test/view/editor/editor.test.ts`:
- Around line 2590-2618: The test currently checks that elements don't change
after clicking the already-selected anchor but doesn't assert the undo stack
remained unchanged; capture the undo state before running the menu action (e.g.,
const beforeCanUndo = store.canUndo(), beforeCanRedo = store.canRedo()), run the
no-op via items.find(...).run(), then assert the undo/redo state is unchanged
(expect(store.canUndo()).toBe(beforeCanUndo) and
expect(store.canRedo()).toBe(beforeCanRedo)); add these checks around the
existing before/after element assertions in the same test where editor, store,
getContextItems, and items.find(...).run() are used.

---

Nitpick comments:
In `@packages/slides/test/view/editor/editor.test.ts`:
- Around line 2400-2403: Replace the duplicated local type with the real
exported type: import ContextMenuItem and change the local declarations — remove
ContextItemLike and update EditorWithContextItems.elementContextItems to return
ReadonlyArray<ContextMenuItem> (and adjust any usage that referenced the local
run/selected fields to the actual ContextMenuItem shape). Ensure the import is
added at the top of the test file and update any mock objects to satisfy the
real ContextMenuItem interface.
- Line 2465: Replace the loose falsy assertion with an explicit boolean check to
match the rest of the suite: change the assertion that checks items.find((it) =>
it.label === 'Align text top')?.selected from using .toBeFalsy() to .toBe(false)
so the boolean flag is asserted precisely (target the test in editor.test.ts
where the items.find(... 'Align text top')?.selected is asserted).
🪄 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: f9645dcc-93c1-4196-9d58-da910ee72704

📥 Commits

Reviewing files that changed from the base of the PR and between 53fbdc9 and 893eae6.

📒 Files selected for processing (17)
  • docs/design/slides/slides-themes-layouts-import.md
  • docs/tasks/active/20260529-slides-pptx-text-vertical-anchor-todo.md
  • docs/tasks/active/20260529-slides-text-vertical-align-menu-todo.md
  • packages/docs/src/view/text-box-editor.ts
  • packages/docs/test/view/text-box-editor.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/element.ts
  • packages/slides/src/view/canvas/text-renderer.ts
  • packages/slides/src/view/editor/context-menu.ts
  • packages/slides/src/view/editor/editor.ts
  • packages/slides/src/view/editor/text-box-editor.ts
  • packages/slides/test/import/pptx/text.test.ts
  • packages/slides/test/view/canvas/text-renderer.test.ts
  • packages/slides/test/view/editor/context-menu.test.ts
  • packages/slides/test/view/editor/editor.test.ts

Comment thread docs/design/slides/slides-themes-layouts-import.md Outdated
Comment thread packages/slides/test/view/editor/editor.test.ts
- Split the verticalAnchor support-matrix row into the table's three
  columns (was 2 cells in a 3-column table → MD056 lint warning,
  could misalign rendering).
- Strengthen the idempotent-click test to also assert canUndo/canRedo
  stay unchanged so a future regression in the short-circuit guard
  fails the spec instead of silently pushing no-op undo entries.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@hackerwins
hackerwins merged commit 9708b6e into main May 29, 2026
3 checks passed
@hackerwins
hackerwins deleted the slides-pptx-text-vertical-anchor branch May 29, 2026 15:06
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