Skip to content

Add mobile shell and morphing toolbar for slides editor#268

Merged
hackerwins merged 4 commits into
mainfrom
slides-mobile-shell-toolbar
May 19, 2026
Merged

Add mobile shell and morphing toolbar for slides editor#268
hackerwins merged 4 commits into
mainfrom
slides-mobile-shell-toolbar

Conversation

@hackerwins

@hackerwins hackerwins commented May 19, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Replace the bespoke 44px header in MobileSlidesView with the docs/sheets shell (SidebarProvider + SiteHeader + SlidesToolbar); add MobileSlidesLayout in slides-detail.tsx parallel to the existing DesktopSlidesLayout.
  • Add a horizontal ThumbnailStrip with renderThumbnail mini-canvases (120 ms debounced repaint, auto-scrollIntoView on active, + cell at end appends a blank slide and jumps to it).
  • Morph the mobile toolbar across idle / object / text-edit via the existing getToolbarState state machine (hoisted in SlidesToolbar). Bottom sheets surface contextual ShapeControls / ImageControls / TextElementControls / TextStyleGroup / TextFormatGroup / TextParagraphGroup — no parallel mobile API.
  • ShapePicker and LinePicker gain an optional trigger?: ReactNode prop so the mobile Insert sheet's Shape/Line entries match Text box and Image visually. Desktop trigger path is byte-identical.
  • Drop the "Slide" text label from SlideGroup's primary button (icon-only +) and remove the entire SlideGroup from the mobile toolbar — add-slide now lives in the thumbnail strip.
  • Wire activeTheme subscription into MobileSlidesLayout so the Format sheet's theme-bound pickers (shape fill, text color, font family) actually render swatches.

Why

The previous mobile slides UI was read-mostly: no insert, no format, no arrange, no thumbnails — just left/right swipe nav. Phone users couldn't add a text box, change a shape's fill, undo a change, or even see which slide they were on relative to the deck. This brings mobile up to feature parity with desktop for the common edit paths while keeping the desktop code path byte-identical (state machine, controls, and ShapePicker/LinePicker defaults all unchanged).

Out of scope (tracked separately)

  • Mobile-friendly ThemePanel container (overflow Theme item still toggles the desktop side panel).
  • Public editor.deleteSelected() / editor.duplicateSelected() — mobile trash button calls store.removeElements directly.
  • useSlidesShellState hook to dedupe MobileSlidesLayout vs DesktopSlidesLayout (~200 lines of parallel state plumbing).
  • 44px touch targets — most icon buttons stay at h-7 (28px) for now.

Tracked in:

  • docs/tasks/active/20260519-slides-mobile-shell-todo.md
  • docs/tasks/active/20260519-slides-mobile-toolbar-morph-todo.md

Test plan

  • `pnpm verify:fast` (792 tests) green locally — confirmed
  • `pnpm --filter @wafflebase/frontend exec tsc --noEmit` clean — confirmed
  • `pnpm --filter @wafflebase/frontend lint` clean (--max-warnings 0) — confirmed
  • CI green
  • Manual smoke (`pnpm dev` + Chromium mobile emulation 375×667):
    • Deck opens with the standard sidebar drawer + SiteHeader; title rename works; Present launches; ShareDialog opens
    • Thumbnail strip: scrolls horizontally, tap navigates, active slide outlined + auto-centered; + cell at end appends a blank slide and jumps to it
    • Toolbar morphing:
      • Idle → tap "Insert" → bottom sheet with Text/Image/Shape ▾/Line ▾, all uniform style; Shape and Line picker popovers render correctly above the sheet
      • Tap a shape on the canvas → toolbar morphs to object state; tap "Format" → Fill/Border picker shows; tap "Arrange" → align/order/group menu
      • Tap trash → selection deleted
      • Double-tap a text box → toolbar morphs to text-edit; B/I/U toggle pressed-state on click; tap "Format" → Style/Color/List/Align sheet; tap "Done" → exits text editing
    • Overflow → Theme… opens panel (currently desktop side-panel — acceptable per scope notes)
    • Desktop unchanged (browse ≥ 768px viewport)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Mobile slides toolbar now morphs between idle, object, and text-edit modes with context-aware controls.
    • New delete selected elements action for slides.
  • Improvements

    • Enhanced mobile slides view state management for better presentation mode support.
    • Mobile toolbar now organizes advanced controls into bottom sheets for improved usability.
  • UI Changes

    • Add slide button on mobile changed to icon-only for compact toolbar layout.

Review Change Stack

Replace the bespoke 44px header in MobileSlidesView with the docs/sheets
shell (SidebarProvider + SiteHeader + SlidesToolbar) and make the toolbar
morph between idle/object/text-edit states with bottom sheets for
contextual editing. Adds a horizontal thumbnail strip for slide nav with
an inline + cell that appends a blank slide and jumps to it.

The previous mobile slides UI was read-mostly: no insert, no format, no
arrange — just swipe navigation. This brings mobile up to feature parity
with desktop for the common edit paths while keeping the desktop code
path byte-identical (state machine + controls all reused).

ShapePicker and LinePicker gain an optional `trigger` prop so the mobile
Insert sheet's Shape/Line entries match the Text box and Image entries
visually. SlideGroup drops the "Slide" text label (icon-only `+`), with
add-slide now living in the thumbnail strip.

Tracked in:
- docs/tasks/active/20260519-slides-mobile-shell-todo.md
- docs/tasks/active/20260519-slides-mobile-toolbar-morph-todo.md

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

coderabbitai Bot commented May 19, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@hackerwins has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 30 minutes and 36 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, 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 have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a0dbcfa6-92d6-4c14-b95c-3457475e0582

📥 Commits

Reviewing files that changed from the base of the PR and between dc7657d and 5657020.

📒 Files selected for processing (4)
  • packages/frontend/src/app/slides/line-picker.tsx
  • packages/frontend/src/app/slides/mobile-slides-view.tsx
  • packages/frontend/src/app/slides/shape-picker.tsx
  • packages/frontend/src/app/slides/slides-detail.tsx
📝 Walkthrough

Walkthrough

This PR implements the mobile slides shell (Phase B-0) by refactoring mobile slides view to lift state upward, introducing a new mobile-specific toolbar renderer that morphs between contextual modes via bottom sheets, and orchestrating a new MobileSlidesLayout that reuses desktop shell elements for mobile.

Changes

Mobile Slides Shell & Toolbar

Layer / File(s) Summary
Planning & Documentation
docs/tasks/README.md, docs/tasks/active/*, docs/tasks/archive/README.md
Task specifications for Phase B-0 (mobile shell) and Phase B-1 (toolbar morphing) document the problem, goals, non-goals, implementation plan, verification steps, and identified risks.
SlidesEditor deleteSelected() Action
packages/slides/src/view/editor/editor.ts
Add deleteSelected() public method to SlidesEditor to remove selected elements from the active slide, clear selection, and repaint; update context menu Delete entry to call the new method.
Toolbar Trigger Props Customization
packages/frontend/src/app/slides/line-picker.tsx, packages/frontend/src/app/slides/shape-picker.tsx
Add optional trigger prop to LinePicker and ShapePicker to allow parent components to override the default dropdown trigger, enabling flexible toolbar trigger wiring for mobile/desktop renderers.
Mobile Toolbar Detection & Routing
packages/frontend/src/app/slides/toolbar/index.tsx
Update SlidesToolbar to detect mobile layout via useIsMobile and conditionally render MobileSlidesToolbar instead of the desktop fixed-zone toolbar, preserving state initialization and subscriptions across both renderers.
MobileSlidesView State Lifting & Thumbnail Strip
packages/frontend/src/app/slides/mobile-slides-view.tsx
Refactor MobileSlidesView to lift editor/store state upward via optional onStoreReady/onEditorReady callbacks, simplify React state to track only slide IDs, add addBlankSlide action, and introduce ThumbnailStrip for horizontal slide navigation with auto-scrolling and debounced thumbnail rendering.
MobileSlidesToolbar Implementation
packages/frontend/src/app/slides/toolbar/mobile-toolbar.tsx
Implement MobileSlidesToolbar as a state-driven morphing renderer with idle/object/text-edit modes, each rendering contextual controls via bottom sheets: insert sheet for adding elements, format sheet for shape/image/text properties, text-format sheet for typography, arrange menu, and overflow menu with theme toggle.
SlideGroup Icon-Only Button
packages/frontend/src/app/slides/toolbar/slide-group.tsx
Update SlideGroup's "Add slide" button from labeled text to icon-only square layout for compact mobile toolbar presentation.
MobileSlidesLayout Shell Orchestration
packages/frontend/src/app/slides/slides-detail.tsx
Introduce MobileSlidesLayout to mount the shared desktop shell (sidebar, header, presence, share, theme tracking, title management) on mobile alongside the refactored MobileSlidesView, with full state management including editor/store wiring, presentation mode, and image upload integration.

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly Related PRs

  • wafflebase/wafflebase#244: Main PR implements the MobileSlidesToolbar morphing flow on top of the shared contextual toolbar architecture introduced in that PR.
  • wafflebase/wafflebase#261: Main PR refactors MobileSlidesView's props/wiring and slides-detail.tsx's mobile branch, directly overlapping with that PR's work on mounting SlidesEditor on mobile.
  • wafflebase/wafflebase#255: Main PR evolves the existing mobile slides implementation by refactoring MobileSlidesView and adjusting slides-detail.tsx's mobile mounting logic, building on that PR's introduction of a mobile read-only slides view.

Poem

🐰 Hopping through the mobile shell
Bottom sheets and toolbar spells,
State floats up, slides now flow,
Touch morphs toolbar to and fro!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: adding a mobile shell and morphing toolbar for the slides editor, which aligns with the primary objectives of replacing the bespoke mobile header with a shared shell and introducing a state-driven mobile toolbar.
Docstring Coverage ✅ Passed Docstring coverage is 84.21% which is sufficient. The required threshold is 80.00%.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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-mobile-shell-toolbar

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

Copy link
Copy Markdown
Contributor

Verification: verify:self

Result: ✅ PASS in 219.3s

Lane Status Duration
sheets:build ✅ pass 14.2s
docs:build ✅ pass 12.6s
slides:build ✅ pass 14.2s
verify:fast ✅ pass 130.4s
frontend:build ✅ pass 18.9s
verify:frontend:chunks ✅ pass 0.3s
backend:build ✅ pass 5.1s
cli:build ✅ pass 2.2s
verify:entropy ✅ pass 21.4s

Verification: verify:integration

Result: ✅ PASS

@codecov

codecov Bot commented May 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

hackerwins and others added 2 commits May 19, 2026 23:20
Regenerated via `pnpm verify:browser:docker:update`:

Intended changes (28 baselines × 7 scenarios × 4 profiles):
- slides-toolbar, slides-toolbar-idle, slides-toolbar-shape-selected,
  slides-toolbar-image-selected, slides-toolbar-text-element-selected,
  slides-toolbar-text-editing, slides-toolbar-multi-select
- Desktop: SlideGroup label "Slide" removed → icon-only `+`
- Mobile: completely new MobileSlidesToolbar morph renderer (was
  desktop renderer clipped at 430px before)

Incidental drift (43 baselines):
- 4 harness landing page captures (aggregate of all scenarios)
- 39 mobile-only slides-canvas/shapes baselines — small antialiasing
  shifts (300-500 byte deltas) from Docker browser re-pull. Desktop
  variants of these scenarios are byte-identical, confirming canvas
  rendering logic is unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
The mobile toolbar's trash button called store.removeElements +
editor.setSelection([]) directly, but neither path triggers a canvas
repaint. The thumbnail strip subscribes to store.onChange and refreshed
instantly, so the deleted shape vanished from the bottom strip while
remaining visible on the main slide until the next interaction.

Add a public editor.deleteSelected() that wraps the desktop context
menu's existing batch + selection.clear + requestRender pattern, and
have both the context menu and the mobile trash button call it.

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 (3)
packages/frontend/src/app/slides/line-picker.tsx (1)

121-123: ⚡ Quick win

Narrow trigger to a single element compatible with asChild.

DropdownMenuTrigger asChild expects one element that can receive forwarded props/ref; React.ReactNode allows invalid values and weakens type safety. Tighten this to an element type.

Proposed fix
-import { useEffect, useRef, useState } from "react";
+import { useEffect, useRef, useState, type ReactElement } from "react";
@@
-  trigger?: React.ReactNode;
+  trigger?: ReactElement;

As per coding guidelines, "Use type annotations in TypeScript to improve code clarity and catch errors early".

Also applies to: 159-161

🤖 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/frontend/src/app/slides/line-picker.tsx` around lines 121 - 123, The
prop type for "trigger" is too broad—change its type from React.ReactNode to a
single element type compatible with DropdownMenuTrigger's asChild (e.g.
React.ReactElement or React.ReactElement<any, any>) so the component only
accepts one element that can receive forwarded props/refs; update the "trigger?:
React.ReactNode" declaration (and the identical declaration around lines
159-161) to "trigger?: React.ReactElement" (or React.ReactElement<any, any>) and
ensure any call sites pass a single element (like SheetActionButton) rather than
fragments/strings.
packages/frontend/src/app/slides/shape-picker.tsx (1)

73-77: ⚡ Quick win

Use a stricter trigger type for Radix asChild composition.

Because this is passed directly to DropdownMenuTrigger asChild, the prop should be a single element type instead of ReactNode to prevent invalid children at call sites.

Proposed fix
-import { useEffect, useRef, useState } from "react";
+import { useEffect, useRef, useState, type ReactElement } from "react";
@@
-  trigger?: React.ReactNode;
+  trigger?: ReactElement;

As per coding guidelines, "Use type annotations in TypeScript to improve code clarity and catch errors early".

Also applies to: 116-118

🤖 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/frontend/src/app/slides/shape-picker.tsx` around lines 73 - 77, The
prop type for trigger is too permissive (React.ReactNode) for Radix's
DropdownMenuTrigger asChild — change the trigger prop declaration(s) in
shape-picker.tsx (the `trigger` prop at the earlier definition and the similar
one at lines ~116-118) to accept a single React element (e.g.,
React.ReactElement or React.ReactElement<any>) instead of React.ReactNode so
callers cannot pass invalid/multiple children; update the prop type annotations
and any related usages to accept/forward a single React element compatible with
asChild.
packages/frontend/src/app/slides/mobile-slides-view.tsx (1)

133-141: ⚡ Quick win

Avoid re-rendering the whole mobile shell on every store change.

refresh() always allocates a new slideIds array, so typing into a text box still rerenders MobileSlidesView and the full thumbnail strip even when the slide list did not change. Guard the state update so only structural slide-list changes trigger React work.

♻️ Suggested change
   useEffect(() => {
     if (!store) return;
     const refresh = () => {
-      const r = store.read();
-      setSnapshot({ slideIds: r.slides.map((s) => s.id) });
+      const nextSlideIds = store.read().slides.map((slide) => slide.id);
+      setSnapshot((prev) =>
+        prev.slideIds.length === nextSlideIds.length &&
+        prev.slideIds.every((id, index) => id === nextSlideIds[index])
+          ? prev
+          : { slideIds: nextSlideIds },
+      );
     };
     refresh();
     return store.onChange(refresh);
   }, [store]);
🤖 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/frontend/src/app/slides/mobile-slides-view.tsx` around lines 133 -
141, The effect's refresh callback always allocates a new slideIds array and
calls setSnapshot even when the logical slide list hasn't changed; modify the
refresh function (the one created inside the useEffect that calls store.read()
and setSnapshot) to compute the new slideIds (r.slides.map(s => s.id)) and
compare it to the current snapshot.slideIds (or previous value) for equality
(same length and same ids in order) and only call setSnapshot when they differ,
so typing or other non-structural store changes won't trigger a re-render of
MobileSlidesView or the thumbnail strip.
🤖 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/frontend/src/app/slides/mobile-slides-view.tsx`:
- Around line 462-468: The ThumbnailStrip is being given
onAddSlide={addBlankSlide} even when the surrounding component is in read-only
mode (mode="view"), allowing mutations; update the render in
mobile-slides-view.tsx so that ThumbnailStrip only receives the onAddSlide prop
when the component is not in view/read-only mode (e.g., conditionally pass
onAddSlide={addBlankSlide} or omit the prop based on mode), leaving other props
(store, slideIds, currentSlideId, onSelectSlide) unchanged.

In `@packages/frontend/src/app/slides/slides-detail.tsx`:
- Around line 527-545: handleImagePick currently captures slideId before opening
the OS file picker which lets the user change slides while the picker is open;
fix by resolving the target slide inside the input.onchange handler (call
editor.getCurrentSlideId() there and validate it) and then call
insertImageOnSlide with that fresh slideId; keep the existing checks for
store/workspaceId and the existing error handling (console.error/toast.error)
and ensure uploadFn is still passed to insertImageOnSlide.

---

Nitpick comments:
In `@packages/frontend/src/app/slides/line-picker.tsx`:
- Around line 121-123: The prop type for "trigger" is too broad—change its type
from React.ReactNode to a single element type compatible with
DropdownMenuTrigger's asChild (e.g. React.ReactElement or
React.ReactElement<any, any>) so the component only accepts one element that can
receive forwarded props/refs; update the "trigger?: React.ReactNode" declaration
(and the identical declaration around lines 159-161) to "trigger?:
React.ReactElement" (or React.ReactElement<any, any>) and ensure any call sites
pass a single element (like SheetActionButton) rather than fragments/strings.

In `@packages/frontend/src/app/slides/mobile-slides-view.tsx`:
- Around line 133-141: The effect's refresh callback always allocates a new
slideIds array and calls setSnapshot even when the logical slide list hasn't
changed; modify the refresh function (the one created inside the useEffect that
calls store.read() and setSnapshot) to compute the new slideIds (r.slides.map(s
=> s.id)) and compare it to the current snapshot.slideIds (or previous value)
for equality (same length and same ids in order) and only call setSnapshot when
they differ, so typing or other non-structural store changes won't trigger a
re-render of MobileSlidesView or the thumbnail strip.

In `@packages/frontend/src/app/slides/shape-picker.tsx`:
- Around line 73-77: The prop type for trigger is too permissive
(React.ReactNode) for Radix's DropdownMenuTrigger asChild — change the trigger
prop declaration(s) in shape-picker.tsx (the `trigger` prop at the earlier
definition and the similar one at lines ~116-118) to accept a single React
element (e.g., React.ReactElement or React.ReactElement<any>) instead of
React.ReactNode so callers cannot pass invalid/multiple children; update the
prop type annotations and any related usages to accept/forward a single React
element compatible with asChild.
🪄 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: c408b67a-4a7c-4e90-aaea-9f6f33ac70fd

📥 Commits

Reviewing files that changed from the base of the PR and between 5b6197e and dc7657d.

⛔ Files ignored due to path filters (72)
  • packages/frontend/tests/visual/baselines/harness-visual.browser.desktop.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.shapes-action-buttons.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.shapes-action-buttons.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.shapes-adjustments-p3b-arrows.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.shapes-adjustments-p3b-arrows.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.shapes-adjustments-p3b-basics.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.shapes-adjustments-p3b-basics.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.shapes-adjustments-pilot.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.shapes-adjustments-pilot.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.shapes-adjustments-sweep.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.shapes-adjustments-sweep.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-callout-tail.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-callout-tail.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-default-dark.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-default-dark.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-default-light.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-default-light.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-donut-evenodd.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-donut-evenodd.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-focus.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-focus.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-layout-big-number.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-layout-big-number.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-layout-section-header.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-layout-section-header.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-layout-title-body.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-layout-title-body.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-material.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-material.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-shapes-catalog-dark.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-shapes-catalog-dark.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-shapes-catalog-light.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-shapes-catalog-light.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-shapes-catalog-material.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-shapes-catalog-material.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-streamline.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-canvas-streamline.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-pickers.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-pickers.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-theme-panel.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-theme-panel.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-toolbar-idle.desktop.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-toolbar-idle.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-toolbar-idle.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-toolbar-idle.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-toolbar-image-selected.desktop.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-toolbar-image-selected.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-toolbar-image-selected.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-toolbar-image-selected.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-toolbar-multi-select.desktop.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-toolbar-multi-select.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-toolbar-multi-select.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-toolbar-multi-select.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-toolbar-shape-selected.desktop.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-toolbar-shape-selected.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-toolbar-shape-selected.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-toolbar-shape-selected.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-toolbar-text-editing.desktop.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-toolbar-text-editing.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-toolbar-text-editing.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-toolbar-text-editing.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-toolbar-text-element-selected.desktop.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-toolbar-text-element-selected.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-toolbar-text-element-selected.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-toolbar-text-element-selected.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-toolbar.desktop.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-toolbar.mobile.dark.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-toolbar.mobile.png is excluded by !**/*.png
  • packages/frontend/tests/visual/baselines/harness-visual.browser.slides-toolbar.png is excluded by !**/*.png
📒 Files selected for processing (12)
  • docs/tasks/README.md
  • docs/tasks/active/20260519-slides-mobile-shell-todo.md
  • docs/tasks/active/20260519-slides-mobile-toolbar-morph-todo.md
  • docs/tasks/archive/README.md
  • packages/frontend/src/app/slides/line-picker.tsx
  • packages/frontend/src/app/slides/mobile-slides-view.tsx
  • packages/frontend/src/app/slides/shape-picker.tsx
  • packages/frontend/src/app/slides/slides-detail.tsx
  • packages/frontend/src/app/slides/toolbar/index.tsx
  • packages/frontend/src/app/slides/toolbar/mobile-toolbar.tsx
  • packages/frontend/src/app/slides/toolbar/slide-group.tsx
  • packages/slides/src/view/editor/editor.ts

Comment thread packages/frontend/src/app/slides/mobile-slides-view.tsx
Comment thread packages/frontend/src/app/slides/slides-detail.tsx
Two correctness fixes + three typing/perf nitpicks raised on #268:

- Pass `onAddSlide` to ThumbnailStrip only when `mode === "edit"` so
  the documented read-only `mode="view"` shell can't mutate the deck
  by appending a slide.
- Resolve the target slide inside the file picker's `onchange`
  handler instead of capturing it before `input.click()`. A user
  swiping to a different slide while the native picker is open would
  otherwise drop the image into the slide that was current at
  open-time.
- Tighten `ShapePicker`/`LinePicker` `trigger` prop from
  `ReactNode` to `ReactElement`. Radix's `asChild` slot needs a
  single element that can receive forwarded refs and handlers, and
  `ReactNode` would let callers pass fragments or text the slot
  can't bind to.
- Guard the mobile-shell `setSnapshot` call with a shallow
  slide-id array equality check. `store.onChange` fires on every
  keystroke during text editing; the previous `refresh` always
  allocated a fresh array and triggered a React rerender of
  `MobileSlidesView` even when the slide list was unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@hackerwins
hackerwins merged commit 0bd93a5 into main May 19, 2026
1 check passed
@hackerwins
hackerwins deleted the slides-mobile-shell-toolbar branch May 19, 2026 15:05
hackerwins added a commit that referenced this pull request May 20, 2026
Captures lessons from PR #269 (diagnosis-by-instrumentation,
rotation-preserving refit math, ghost-pattern reuse, DOM ownership
across renderOverlay rebuilds) and archives the three May 19 slides
tasks (mobile-shell, mobile-toolbar-morph, precise-shape-hit-test)
that shipped in PRs #266 / #268 but stayed in `active/` until now.
@hackerwins hackerwins mentioned this pull request May 24, 2026
2 tasks
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