Skip to content

refactor: migrate session coupled core (chat, live reload, btw, cat) to Svelte 5#68

Merged
setkyar merged 11 commits into
mainfrom
svelte/phase-3-coupled-core
Jun 7, 2026
Merged

refactor: migrate session coupled core (chat, live reload, btw, cat) to Svelte 5#68
setkyar merged 11 commits into
mainfrom
svelte/phase-3-coupled-core

Conversation

@setkyar

@setkyar setkyar commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Migrates the tightly-coupled "core" of the session viewer from the imperative
innerHTML runtime to self-contained Svelte 5 components, continuing
docs/dev/svelte-migration-plan.md (Phase 3). The motivation is to make the
live app render from declarative, state-driven components (shared with the
static export where applicable) and to shrink session.js toward deletion.

Key changes:

  • RightSidebar group → Svelte. RightSidebar.svelte absorbs the scratchpad
    chrome (load/save, resize, tabs), and the Artifacts/Notes panes are now
    ArtifactPanel.svelte and AnnotationLayer.svelte (declarative rendering;
    selection/highlight/SSE stay imperative). Deletes ui/right-sidebar.js,
    artifacts/artifact-panel.js, annotations/annotation-layer.js.
  • View-state ownership → model/SessionPage. SessionPage builds the
    navigator from the reactive SessionDataModel and exposes window.navigateTo
    before children mount; active leaf/target/filter/search live solely on the
    model. session.js drops its mirrored locals + own navigator.
  • ChatComposer + LiveReload → components. ChatComposer.svelte self-inits
    the chat runner + git footer; new LiveReload.svelte self-inits the SSE
    runner (reactiveContent). session.js exposes only
    window.__piReconcileEntries for live reload + load-earlier.
  • BtwPopup + CatGatekeeper → components. BtwPopup.svelte renders the
    floating scratch-chat transcript declaratively (drag/resize/SSE imperative);
    CatGatekeeper.svelte renders the focus/break overlay while
    cat-gatekeeper.js keeps the DI-testable timer logic via an injected view.
    Deletes live/btw-popup.js.
  • All migrated modules' tests were ported to @testing-library/svelte
    component tests; new e2e specs (e2e/tests/btw.spec.ts, cat.spec.ts) were
    added before converting btw/cat (neither had coverage).

Related issue

Part of the Svelte 5 migration tracked in docs/dev/svelte-migration-plan.md
(Phase 3 — coupled core). No standalone issue number.

Type of change

  • refactor — code change that neither fixes a bug nor adds a feature
  • test — adding or updating tests

Live vs. Export

  • Considered both the live app and the export snapshot
  • Kept internal/ui/embedded/ in sync with web/src/session/ changes
  • No live-only chrome (Vite scripts, active composer, SSE/API) leaked into export

The export self-containment guard (TestExportBundleIsSelfContained) stays
green; chat/live/btw/cat components are live-only and never imported by
web/src/export/export-entry.js.

Testing

  • Frontend tests (vitest) cover the change — npm run test (528 passed),
    npm run knip clean, npm run build (zero vite-plugin-svelte a11y warnings)
  • UI changes verified in a browser — npx playwright test --project="Desktop Chrome"
    (57 passed / 2 skipped; load-earlier passes on its documented retry)
  • make check / full go test ./... — ran go build -o pi-web ./cmd/pi-web,
    go test ./internal/ui/... (incl. the export guard), and go vet ./... green.
    Did not run the full Go suite: internal/git TestDescribeDefaultBranch fails
    only in this sandbox due to commit-signing (pre-existing, unrelated).

setkyar added 11 commits June 7, 2026 10:58
Absorb right-sidebar.js (scratchpad load/save, resize drag, tab
switching, expand/collapse) into RightSidebar.svelte's onMount. The
component exposes its controls on window.__piRightSidebar so
session-ui-runner and the annotation/keyboard wiring keep working.
Delete right-sidebar.js and port its tests to RightSidebar.test.js.
Replace the imperative innerHTML artifact-panel.js renderer with a
reactive ArtifactPanel.svelte mounted inside RightSidebar's Artifacts
pane. session.js still owns artifact collection/filtering and pushes the
visible set through window.__piArtifactPanel (also consumed by the
annotation layer for select/resolve). The component lazy-loads
highlight.js and renders markdown previews itself. Delete
artifact-panel.js and port its tests to ArtifactPanel.test.js.
Replace the imperative innerHTML annotation-layer.js with a reactive
AnnotationLayer.svelte rendered inside RightSidebar's Notes pane. The
notes list, floating comment popover, and note modal render
declaratively; selection detection, highlight (re)application, and the
annotations API stay imperative. session.js supplies runtime deps
(api/scopes/composer/callbacks) through window.__piAnnotationLayer.init.
The popover + modal relocate to <body> so their fixed positioning stays
viewport-relative. Delete annotation-layer.js and port its tests to
AnnotationLayer.test.js.
…plan

Update the svelte-migration-plan §11 progress log to reflect the
completed RightSidebar-group cut-over (chrome + artifact panel +
annotation layer) and point the next executor at the coupled
ChatComposer + LiveReload core.
…ionPage

Prep for the ChatComposer + LiveReload migration. SessionPage now creates
the session navigator from the reactive model and exposes navigateTo on
window before the child components mount, so the tree, chat composer, and
live reload share one instance. The active leaf/target/filter/search
state lives solely on SessionDataModel — session.js drops its mirrored
locals, its own navigator, and syncTreeRendererState; search-filters
callbacks write the model directly. Pure refactor; behavior unchanged.
ChatComposer.svelte now self-inits the chat runner + git footer in
onMount, and a new LiveReload.svelte self-inits the SSE live-reload
runner. Both read the shared model + navigateTo (owned by SessionPage)
from window. session.js no longer wires chat/live: it just exposes model
reconciliation on window.__piReconcileEntries for LiveReload to call (also
used by load-earlier) and drops the runner/selector imports. <LiveReload>
mounts before <ChatComposer> so the optimistic message-sent listener
exists before the user can send. The chat/live runner modules stay as
component-owned implementation detail (deletion is the later cleanup).
Update svelte-migration-plan §11 with the navigateTo/view-state prep and
the ChatComposer + LiveReload component cut-over; point the next executor
at btw-popup + cat-gatekeeper (add e2e first) and session.js teardown.
btw.spec: open/close from the git bar with empty state, and the
optimistic user bubble + running state on send. cat.spec: skip-to-break
shows the enforced break overlay with a countdown, enabling the gatekeeper
page-locally via a settings route stub so the shared server store (and
the rest of the suite) stays untouched. These pin the selectors/behavior
the upcoming BtwPopup + CatGatekeeper components must preserve.
…elte

Split the focus/break + bedtime companion: cat-gatekeeper.js keeps the
DI-testable timer/phase logic but no longer builds the overlay — it calls
an injected `view` (showBreak/setBreakTimer/showSleep/hide). The overlay
is now CatGatekeeper.svelte, which renders it declaratively, blocks input
while shown, plays the cat video, and exposes the controller on
window.__piCatGatekeeper. The overlay relocates to <body> (fixed,
full-screen, top z-index). session.js drops setupCatGatekeeper; the
component self-wires in onMount. cat-settings.js (pure storage) stays.
Unit test reworked to assert the controller's view calls + state.
Replace the imperative innerHTML live/btw-popup.js with BtwPopup.svelte:
the transcript (markdown + tool chips + optimistic/streaming/working
bubbles) renders declaratively; drag, resize, per-session + per-parent
SSE, status polling, and submit/cancel stay imperative. It wires its
#pi-btw-button trigger (in <ChatComposer>) by id and relocates the window
to <body> for fixed positioning. session.js drops setupBtwPopup;
SessionPage renders <BtwPopup cwd parentId>. Delete btw-popup.js and port
its tests to BtwPopup.test.js.

openWindow clears `hidden` synchronously before placeInitial measures the
window, so it isn't positioned off-screen while Svelte's reactive flush
is still pending.
Update svelte-migration-plan §11 with the e2e-coverage-first step and the
CatGatekeeper + BtwPopup component cut-overs; point the next executor at
the session.js teardown + docs pass.
@setkyar setkyar merged commit 4e96b74 into main Jun 7, 2026
1 of 2 checks passed
@setkyar setkyar deleted the svelte/phase-3-coupled-core branch June 7, 2026 18:03
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