refactor: migrate live modals and overlays to Svelte components#67
Merged
Conversation
Cut the message pane (#messages) over to the reactive <SessionContent>
component in both the live app and the static export, retiring the
imperative DOM build/patch path (sub-step B of the migration plan).
- SessionPage owns a $state content runtime (window.__piContentRuntime);
runSessionApp injects renderEntry + afterRender. <SessionContent>
renders model.activePath inside #messages; firstMessageStub dropped.
- Navigator gutted to nav-state + scroll only (no #messages build,
entryCache, or per-entry button wiring). Copy/fork/label handled by a
single delegated click listener on #messages.
- Live reload no longer patches the DOM: handleSessionReload gained a
reactive mode that reconciles through the model and flags new entries
for follow/scroll + new-entry highlight. live-entries kept (legacy
branch + tests) but unwired from the live content path.
- export-entry mounts <SessionContent> into #messages; navigator
simplified the same way. session-entry-renderer kept (SessionEntry
still wraps it via {@html} for this pass).
- Fix: byId/toolCallMap/labelMap switched to SvelteMap so in-place
.set/.clear are reactive (a plain $state Map's mutations are not),
which load-earlier relies on (prepend without an active-leaf change).
- CSS: #messages-list uses display:contents to preserve flex/gap layout
and keep chat-preview siblings correctly spaced.
- Repoint the Go toggle-state source guard to <SessionContent>.afterRender.
Verified: web 554 unit tests, build (live + export), knip clean,
go test/vet, and the Desktop Chrome e2e suite (53 passed / 2 skipped).
First Phase-3 increment: convert the click-to-zoom image overlay from the imperative ui/image-modal.js into a self-contained ImageModal.svelte used by both the live app and the static export. - Reactive open/src/alt; document-level delegated listener for .message-image / .pi-chat-attachment-preview; Escape/backdrop close. - Live: SessionPage renders <ImageModal/> (replaces static #image-modal). - Export: session.html exposes #image-modal-host; export-entry mounts the component into it (static markup removed). - Delete ui/image-modal.js + test; behaviour ported to ImageModal.test.js. Starts Phase 3 with an isolatable component (no session-data/navigateTo coupling); the coupled chat/live core lands later as session.js is dismantled. Verified: web 553 unit tests, build (live + export), knip clean, go test/vet, Desktop Chrome e2e (53 passed / 2 skipped).
…ader
Move the resume ("Terminal") and new-session button behavior out of the
imperative live/resume-button.js + live/new-session-button.js into
SessionHeader.svelte, the component that already owns those hidden
command-relay buttons.
- SessionHeader onMount wires #resume-btn / #new-btn by id, so existing
getElementById(...).click() callers (command menu, Cmd+T, header btn)
keep working unchanged. SessionPage passes cwd + sessionId.
- runLiveReload drops its resumeButton/newSessionButton/cwd deps and the
setup calls; session.js drops the imports.
- Delete both modules + their tests.
- Repoint the Go resume source-guards to SessionHeader.svelte
(document.*/navigator.* instead of the old DI-style names).
Verified: web 544 unit tests, build (live + export), knip clean,
go test/vet, Desktop Chrome e2e (53 passed / 2 skipped).
Begin the shared-sheet-infra conversion (1/4). Port live/full-screen-sheet.js (showSheet) to FullScreenSheet.svelte — same markup/classes/behavior: ref-counted scroll-lock, focus trap, Escape/backdrop close, and the mobile synthetic-history back-gesture close — driven by a single bindable `open`. The backdrop click listener is attached imperatively (matching the repo's delegated convention) to avoid an a11y lint on a non-interactive element. ShortcutsModal.svelte ports live/shortcuts-modal.js with reactive search, rendered over FullScreenSheet. Triggers are bridged via window.__piOpenShortcuts (set by SessionPage, called from session.js's Cmd+/ and #shortcuts-help-btn). - Delete live/shortcuts-modal.js; add component tests for both. - full-screen-sheet.js stays until the other 3 sheet consumers migrate. Verified: web 547 unit tests, build (live + export, no a11y warnings), knip clean, go test/vet, Desktop Chrome e2e (54 passed / 2 skipped).
Port live/model-usage-modal.js to ModelUsageModal.svelte: pure stat/cost/ model-breakdown helpers + reactive markup over <FullScreenSheet>, computed from the shared session model via context. No escapeHtml needed (Svelte auto-escapes); formatTokens comes from session-stats. The command menu's model-usage action now invokes window.__piOpenModelUsage (bridge set by SessionPage). Also: - Add backdropClass/panelClass/bodyClass props to FullScreenSheet so each modal can tag the sheet for CSS. This fixes a missed shortcuts-sheet-* styling regression from the previous (1/4) commit. - Add a destroy-time cleanup to FullScreenSheet (remove global listeners, release the ref-counted scroll lock) for SPA route changes + test isolation. - Delete live/model-usage-modal.js + test (ported to ModelUsageModal.test.js); update command-menu.test.js for the bridge. Verified: web 545 unit tests, build (live + export, no a11y warnings), knip clean, go test/vet, Desktop Chrome e2e (53 passed / 2 skipped).
Port live/fork-modal.js to ForkModal.svelte: searchable user-message
palette with keyboard navigation (up/down/Enter), a preview pane, and an
onSelect(entryId) fork callback, rendered over <FullScreenSheet>.
- <script module> exports buildUserMessageList so SessionPage's bridge can
do the "no user messages" empty check (returns false, and the command
menu shows the toast — parity with the old null-sheet return).
- command-menu's fork action fetches fresh entries then calls
window.__piOpenForkModal({ entries, onSelect }).
- Delete live/fork-modal.js + test (ported to ForkModal.test.js).
Verified: web 546 unit tests, build (live + export, no a11y warnings),
knip clean, go test/vet, Desktop Chrome e2e (54 passed / 2 skipped).
Final sheet-infra step. Move the showCatSettings sheet UI into a reactive
CatGatekeeperSettings.svelte over <FullScreenSheet>; the pure storage
helpers stay in cat-settings.js (now exports LIMITS, no longer imports
showSheet). The cat-gatekeeper controller's openSettings() bridges via
window.__piOpenCatSettings({ controller, onChange }).
With its last consumer migrated, delete live/full-screen-sheet.js + test —
the imperative showSheet util is fully replaced by <FullScreenSheet>.
- cat-settings.test.js keeps the pure-helper tests; the sheet test is
ported to CatGatekeeperSettings.test.js.
Sheet-infra chunk complete: FullScreenSheet + 4 modals are now Svelte,
5 modules deleted across the chunk.
Verified: web 530 unit tests, build (live + export, no a11y warnings),
knip clean, go test/vet, Desktop Chrome e2e (54 passed / 2 skipped).
Begin the coupled-core conversions with the most self-contained piece. Absorb live/share-overlay.js into ShareDialog.svelte: wires the hidden #share-btn relay to POST /share, then shows the gist/preview URLs (or an error) in a reactive overlay with clipboard-copy + toast. - runLiveReload drops its shareOverlay dep, the setupShareButton block, and the now-unused local escapeHtml helper; session.js drops the import. - Delete live/share-overlay.js + test (ported to ShareDialog.test.js). - Repoint the Go share source-guard to ShareDialog.svelte (document.*/ navigator.* instead of the old DI names). Verified: web 528 unit tests, build (live + export, no a11y warnings), knip clean, go test/vet, Desktop Chrome e2e (54 passed / 2 skipped).
Move live/command-menu.js behavior into CommandMenu.svelte's onMount: open/close (desktop popover + mobile panel), outside-click/Escape close, and the full action dispatch. Share/new/terminal click the hidden relay buttons; tree uses sidebarApi; model-usage + fork go through the existing window bridges; rename/clone use fetch; version/user-docs/diff as before. The session-list palette is now opened via window.__piOpenSessionPalette (set in session.js), used by both the list-sessions action and Cmd+K — replacing the setupCommandMenu._palette coupling. session.js drops the setupCommandMenu import + call. - Delete live/command-menu.js + test (ported to CommandMenu.test.js). Verified: web 528 unit tests, build (live + export, no a11y warnings), knip clean, go test/vet, Desktop Chrome e2e (53 passed / 2 skipped).
Port ui/label-modal.js to LabelModal.svelte (set/clear an entry's tree
label). Opened via window.__piOpenLabelModal({ entryId, currentLabel,
onSave }); session.js's delegated label button still owns the save (API +
tree refresh). Backdrop close attached imperatively; Enter/Escape on the
input.
- Delete ui/label-modal.js + test (ported to LabelModal.test.js).
Verified: web 528 unit tests, build (live + export, no a11y warnings),
knip clean, go test/vet, Desktop Chrome e2e (54 passed / 2 skipped;
labels/annotations/artifacts/session-view green).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR completes the Svelte migration of the session UI layer by porting all remaining imperative modal/overlay modules (
full-screen-sheet.js,command-menu.js,model-usage-modal.js,fork-modal.js,shortcuts-modal.js,share-overlay.js,image-modal.js,label-modal.js,resume-button.js,new-session-button.js) to reactive Svelte components. The migration consolidates these into a cohesive component library underweb/src/components/session/, eliminating the imperative DOM-building pattern in favor of declarative, state-driven rendering.Key changes:
FullScreenSheet.svelte,CommandMenu.svelte,ModelUsageModal.svelte,ForkModal.svelte,ShortcutsModal.svelte,ShareDialog.svelte,ImageModal.svelte,LabelModal.svelte,CatGatekeeperSettings.svelte) replace their imperative counterparts with reactive props and snippets.FullScreenSheet.svelte— a reusable base component for all modal/sheet UIs, handling scroll-lock (ref-counted), focus trap, Escape/backdrop close, and synthetic history on mobile. Maintains the same markup/classes/behavior as the originalshowSheet().CommandMenu.svelte— absorbs the command-menu logic and wires the desktop popover + mobile panel, delegating actions to window bridges (model-usage, fork, session-list palette) or hidden relay buttons (share, new session, terminal).SessionHeader.svelte— now owns the resume/new-session button logic (formerly in separate modules), showing toast notices inline.SessionPage.svelte— orchestrates all modals via bindableopenstate and window bridges, exposing them to the live app controller.live/full-screen-sheet.js,live/command-menu.js,live/model-usage-modal.js,live/fork-modal.js,live/shortcuts-modal.js,live/share-overlay.js,live/resume-button.js,live/new-session-button.js,ui/image-modal.js,ui/label-modal.jsand their tests.cat-gatekeeper/cat-settings.jsretains only the framework-free storage helpers; the UI now lives inCatGatekeeperSettings.svelte.session-navigation.jsnow handles nav-state + scroll only;<SessionContent>renders the message DOM reactively from the shared model.live-reload-runner.jsgains areactiveContentflag to skip DOM patching when<SessionContent>owns#messages.All components maintain the original behavior (scroll-lock, focus management, history integration, keyboard shortcuts, mobile responsiveness) while gaining Svelte's reactivity and testability.
Related issue
Closes #(Svelte migration Phase 3 — modals/overlays)
Type of change
refactor— code change that neither fixes a bug nor adds a featureLive vs. Export
internal/ui/embedded/in sync withweb/src/session/changesTesting
make checkpasses (test + build + vet)vitest) cover the change — migrated all existing tests to Svelte component tests; new tests forFullScreenSheet,CommandMenu,ModelUsageModal,ForkModal,ShortcutsModal,ShareDialog,ImageModal,LabelModal,CatGatekeeperSettingshttps://claude.ai/code/session_01RRKnATzpTerM15iS1DXXL5