Skip to content

feat(dialogs): resize note and session dialogs - #956

Merged
matt2e merged 4 commits into
mainfrom
wider-notes-and-sessions
Sep 24, 2026
Merged

matt2e merged 4 commits into
mainfrom
wider-notes-and-sessions

Conversation

@matt2e

@matt2e matt2e commented Sep 24, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add persisted dialog widths and accessible drag/keyboard resize controls for note and session dialogs.
  • Preserve note/chat split sizing while keeping chat toggle animation stable.
  • Add unit coverage plus a Playwright regression harness for dialog resize behavior.

Tests

  • Push hooks ran: crates-fmt, crates-lint, crates-test, differ-ci, staged-ci

matt2e and others added 3 commits September 24, 2026 11:34
NoteModal, WriteNoteModal, SessionModal and NewSessionModal each get a
draggable right edge. Today's fixed width becomes the minimum, the maximum
follows the window (32px of gutter per side), and the chosen width persists per
dialog kind in `~/.staged/preferences.json` — so a user who never drags sees no
change.

Two shared pieces under the dialog primitive do the work:

- `dialogWidth.svelte.ts` holds the width per preference key. Instances are
  cached at module level because every mount site wraps its dialog in `{#if …}`,
  and the note viewer and note editor deliberately share one key. `set()` clamps
  against the live window size and the inline style also carries a
  viewport-relative `max-width`, so shrinking the window only caps rendering and
  the dialog returns to its saved width when the window grows back.
- `dialog-resize-handle.svelte` is an 8px right-edge button using pointer
  capture. The dialog stays centred, so it applies twice the pointer delta to
  keep the right edge under the cursor. Arrow keys nudge by 16px, Home/End jump
  to the bounds, double-click resets, and it renders nothing on mobile, where
  dialogs are full-screen.

NoteModal's chat split changes shape to suit: instead of swapping 700px for
1080px, the chat pane becomes a fixed 380px column added on top of the persisted
note width. Toggling chat no longer changes how wide the note reads, and the
grid's `1fr` note column can grow past the pane's old 390px cap without leaving
a gap.

Startup hydration runs from `initPreferences` after the UI unblock so the first
dialog of an app run opens at the saved width rather than jumping to it.

Verified with `pnpm run check`, `pnpm test` (1014 passing, including 13 new
cases for the width store) and `prettier --check`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
Resolve the resizing review by measuring rendered geometry for pointer and keyboard input, keeping previews separate from preferred widths, and persisting only changed gesture results. Cancel on pointer interruption, capture loss, unmount, or layout changes, including pointer-up racing a breakpoint change, while restoring body styles and NoteModal transition state.

Add focusable separator semantics with live pixel values and effective bounds. Reserve the agreed 8px desktop inner gutter in note, editor, and session dialogs. Preserve existing defaults, mobile behavior, shared note/editor preference, and the fixed chat addition through temporary viewport compression.

Keep hydration nonblocking, retain oversized preferences, ignore stale reads after interaction, and contain read/write failures. Add gesture and storage regressions.

Validation: 1096 frontend tests pass across 81 files; static checks have zero errors or warnings; full frontend formatting checks pass. A temporary harness exercised the real four dialogs with mocked backend responses in headless Chrome and Playwright WebKit, including keyboard/focus/ARIA, cancellation, breakpoints, chat, shared widths, and reopen/reload. Scrollbar thumb dragging passed in Chrome; WebKit wheel scrolling and separation passed but native thumb dragging was unavailable. Native WKWebView, VoiceOver, and real on-disk persistence were not verified.
Signed-off-by: Matt Toohey <contact@matttoohey.com>
Serialize saves within each cached width preference so a delayed resize cannot overwrite a newer reset. Recover from rejected saves before continuing the queue, while keeping different preference keys independent.

Suspend width transitions before measuring keyboard, pointer, and reset commands. Flush layout before restoring the chat transition, and explicitly disable the inherited transition while resizing so rapid keys use the completed effective width.

Add deferred-save regressions and a repeatable browser harness using the real NoteModal and mocked HTTP transport. Verify rapid keys, chat animation interruption, pointer resizing, reset/reload ordering, and recovery after failed HTTP saves.

Validation: 1,099 frontend tests and 12 Chrome/WebKit browser cases pass; static checks report zero errors or warnings; frontend formatting passes. Native WKWebView and real on-disk persistence were not verified.
Signed-off-by: Matt Toohey <contact@matttoohey.com>

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d0aa1b0327

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +57 to +60
geometry: () => ({
...resizeBounds(minWidth, window.innerWidth - DIALOG_VIEWPORT_GUTTER * 2),
width: dialog?.getBoundingClientRect().width ?? minWidth,
}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Measure the unscaled dialog width

When a saved dialog wider than its minimum is dragged immediately after opening, dialog-content.svelte applies a data-open:zoom-in-95 animation for 100 ms, so getBoundingClientRect().width includes the temporary scale. The gesture preview then starts from that smaller value; for example, a 1000 px dialog can begin at 950 px and a small drag can persist that unintended reduction. Use an untransformed layout measurement such as offsetWidth, or wait for the entrance animation to settle before initializing the gesture.

Useful? React with 👍 / 👎.

Use offsetWidth for pointer and keyboard resize geometry so the opening scale animation cannot shrink a saved dialog width.

Add browser regressions that pause the entrance animation and verify layout widths and persisted results for dragging and keyboard input.

Validation: 16 Chrome/WebKit browser cases, 51 focused unit tests, frontend static checks, and formatting pass.
Signed-off-by: Matt Toohey <contact@matttoohey.com>
@matt2e
matt2e merged commit f912579 into main Sep 24, 2026
4 checks passed
@matt2e
matt2e deleted the wider-notes-and-sessions branch September 24, 2026 05:18
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