feat(sessions): scoped preview refresh + multi-target live editors + pipeline preview#10006
Conversation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Multi-target migration P0. SessionWrapper's inline editor pane was dead (the sessions page always mounts it with hideEditor); remove it and the single-target machinery (setSessionTarget/pickEditorTarget/target-keyed editor views). Scope the raw-app file/runnable preview reload to args.path (the app's workspace path) instead of the session target. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Multi-target migration P1. Replace the three per-kind singleton stores/slots with per-(kind,path) cell maps, created on demand and kept (eviction deferred to P3). The runtime's public interface is unchanged: the flowStore/scriptStore/savedScript/rawApp/... getters and slot(kind) now forward to the 'active cell' per kind (a single-target shim, tracked by activePath, removed in P2 when the UI mounts one editor per tab). loadFlow/loadScript/loadRawApp and syncPreviewWithDeployed operate on the resolved cell; load logic and semantics are otherwise unchanged, so loading one item no longer clobbers another's state. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Multi-target migration P2 — the behavioral flip. resolvePreviewTab no longer takes a target: any editable route (script/flow/raw_app) resolves to an in-process editor, so several items are live at once (iframes remain only for real pages and regular non-raw apps). Each editor binds its own per-(kind,path) cell; the draft codecs close over that cell's store so two editors never cross-write. The single-target shim (activePath + the flowStore/scriptStore/... getters + slot(kind)) is removed; runtime exposes flowCell/scriptCell/rawAppCell(path). Tab open/navigate dedupe by (kind,path) and no longer setTarget. setLiveEditorDraft is gated on the visible tab (isActiveTab) so N editors don't clobber the one-per-(workspace,kind) live-draft slot (path re-key deferred to P4). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…r LRU Multi-target migration P3. Bound the per-(kind,path) editor cell maps: pruneEditorCells drops every cell no open preview tab still references, wired to a new onTabsChanged adapter callback fired on each tab-set change — so closing or navigating a tab away from an item reclaims its cell (dedupe keeps <=1 editor tab per item, so a pruned item has no live editor to strand). Also remove the now-dead editorWarmIds/promoteEditorWarm/MAX_WARM_EDITORS warm-editor LRU: its only reader (SessionWrapper.mountEditor) was removed in P0, and mounted editors are already capped per-tab by mountedTabKeys. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Multi-target migration P4 (final). Remove the session.target field and setSessionTarget: the preview is driven entirely by the tab model now (P2). hydratePreviewTabs no longer seeds a tab from target (saved previewTabs only); openEditorInSession seeds the preview via resetSessionPreviewTabs; normalizeLegacySession drops the retired target field from old records. The setLiveEditorDraft focus gate (isActiveTab, one-per-(workspace,kind)) is kept as-is; a per-path re-key is a possible future refinement, not needed for correctness. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…story Address standards review: AGENTS.md requires comments describe the code as it is, not its drafting history. Drop the 'used to be per-kind singletons' / 'pre-refactor empty editor' / 'now' phrasings from the cell comments. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address spec review: two comments still referenced the removed runtime.rawApp.val accessor; the live code uses the per-cell store now. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Deploying windmill with
|
| Latest commit: |
7e48ae6
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://bd3a0ed9.windmill.pages.dev |
| Branch Preview URL: | https://glm-session-multi-target.windmill.pages.dev |
Main's #9993 added svelte-ignore comments describing the old runtime.savedFlow.val / runtime.rawApp.val singleton bindings. The multi-target refactor binds each tab's own editor cell (cell.store / cell.saved), so update the comment text to match; the ownership_invalid_binding directives themselves remain correct (the targets are still runtime-owned). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The multi-target refactor removed the old single-target editor pane —
PipelineEditorView's only mount point — so open_preview(kind="pipeline")
opened nothing, even though the chat tool and system prompt still make it
the first step of pipeline authoring.
Route a /pipeline/<folder> preview tab to the in-process graph editor:
- previewRouter: parsePipelineRoute + resolvePreviewTab map the folder to a
pipeline editor slot; PreviewSlot.editorKind gains 'pipeline'.
- previewTargetForSessionTarget('pipeline') returns the folder route target
(was undefined); open() keeps a single pipeline tab and retargets it to the
requested folder, since all pipeline tabs share one runtime.pipelineEditorState.
- PreviewTabHost mounts PipelineEditorView for the pipeline slot.
- PipelineEditorView gains an `active` prop; AI-helper registration and the
live-badge poll now gate on isActiveSession && active.
Register the pipeline tools on the session's own chat, not the singleton:
PreviewTabHost mounts the view outside the SessionWrapper subtree that
provides the scoped aiChatManager context, so getAiChatManager() fell back to
the app-wide singleton — build_pipeline_node / edit_pipeline_node never
reached the session chat and the model fell back to write_script (whose draft
never appears on the canvas). Use runtime.manager directly instead.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…changes The scoped-refresh pass reloaded every open list-page preview tab on any workspace mutation (reloadPages: boolean), so creating a schedule also refreshed the Resources / Variables tabs. Replace the blanket flag with the specific page paths each tool can change: write_schedule → /schedules, write_resource → /resources, write_variable → /variables, create_folder → /folders, write_trigger → the trigger kind's page; delete/deploy/discard/rebase map their `type` to its page (none for script/flow/app). Item-editor writes now reload no pages — their live editor self-syncs. reloadTabs refreshes a list-page tab only when its own path is in the touched set. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… previewReload module
Post multi-target, every editable item is a live editor whose reload() no-ops,
and the one iframe item kind (legacy drag-drop apps) is never emitted as a
scope — so the whole `scopes` half of the preview-reload machinery could never
fire. Remove it (PreviewKind, PreviewScope, scopeKey, itemTypeToPreviewKind,
pendingScopes, and the item-route branch of reloadTabs); the `pages` path
already covers every real reload.
Lift the surviving pure logic out of the 900-line route component into
previewReload.ts — toolReloadEffect(name,args) -> {pages} and a new
tabsToReload(tabs,pages) mirroring selectPreviewTabsToClose — and cover it with
previewReload.test.ts (per-tool page mapping, item kinds reload nothing, the
unknown/local-tool silent-stale guard, loc-over-url matching).
Also clear session.target leftovers: delete the unread EDITOR_TARGET_KINDS
export and rewrite five comments that still described the removed single-target
pane / target-record write.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Consolidate the "live editors self-sync, only list pages reload" rationale to previewReload.ts and drop the drafting-history phrasings the review flagged: the update_user_instructions incident and the "(not the runtime)" contrast in sessionDraftCodecs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @Guilhem-lm's task in 5m 41s —— View job cc @Guilhem-lm Mergeable, but should ideally address nits: stray editor-cell allocation in SummaryLarge, cleanly-executed refactor: drops the single I verified the central correctness claim of part 1: every mutating global-chat tool maps correctly through No P0/P1 issues. The findings below are all minor. FindingsP2 — P2 — P2 — stale comment after AGENTS.md compliance
Test coverageFrontend pure-logic only (per policy, Svelte components aren't unit-tested here):
Manual verification worth doing before merge: open two editable tabs of the same kind (two scripts) and confirm each edits its own draft with no cross-write, then close one and confirm the other's Monaco state survives (exercises the per-cell stores + |
|
I have now read the full diff, all modified Svelte components, and the key logic modules. Let me compile the review. Pi ReviewGood to merge This is a well-executed, frontend-only refactor of the sessions preview system. It ships three related improvements — scoped preview-tab refresh, multi-target live editors via per-(kind,path) cells, and pipeline preview restored as a live editor tab — with thorough test coverage and no remaining dead code. What I verified
Test coverage
|
Codex Reviewcc @Guilhem-lm [P1] Test coverage |
Address PR review findings on the multi-target preview. P1 (Codex) — draft sync stayed bound to the old cell after an in-place tab retarget. useUserDraftSync captured `codec` once, but navigate() re-points a live editor tab (script/flow/raw_app) to another item without remounting, so path/workspace/ready followed the new item while the codec still read/wrote the previous cell's store — cross-writing drafts. Make `codec` a reactive getter like the hook's other inputs; SessionEditorTarget rebuilds it per path. P2 (Claude) — navigate() now enforces the single-pipeline-tab invariant that open() does: retargeting to a /pipeline/<folder> route focuses and re-points the existing pipeline tab instead of turning the active tab into a second editor racing the shared pipelineEditorState. P2 (Claude) — the deploy-in-session handler peeked an editor slot via the create-on-miss cell accessors, allocating an empty cell for items with no open tab. Add a non-creating runtime.loadedEditorPath(kind, path) and use it. P2 (Claude) — correct a SessionPicker comment left stale by the session.target removal (the preview no longer seeds from a target). Tests: two navigate() pipeline-invariant cases. npm run check 0 errors; 167 session unit tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pi ReviewGood to merge I verified the three prior-review findings against the current diff:
What I verified independently
Test coverage
|
Codex ReviewGood to merge No issues found. Checked for bugs, regressions against the prior Codex finding, and AGENTS.md compliance. The previous draft-sync retargeting issue is addressed in this head: Test coverage Frontend-only diff. The changed pure logic has focused tests for preview reload scoping, preview routing, tab-model behavior including pipeline single-tab retargeting, draft codec store isolation, and session persistence. I did not run the suite locally during this read-only review. Manual verification before merge should still exercise the actual session UI: keep two live editor tabs open for different items, retarget one via breadcrumb/picker, and confirm draft writes stay scoped to the newly selected item. Also verify a pipeline preview mounts in-panel, retargets to a second folder without opening a second pipeline tab, and the pipeline AI tools update the visible canvas. |
Three related session-preview improvements, shipped together. Supersedes #9999 (its commit is the base of this branch).
1 — Smart, scoped preview-tab refresh (was #9999)
After a chat round we used to reload every open preview tab. Now refresh is scoped to what a tool actually touched:
toolReloadEffect(name, args)returns{ scopes, reloadPages }(an allowlist of preview-affecting tools + the items they edited by path), andreloadTabsreloads only the matching item tabs — plus page tabs (Runs/Schedules/Resources/…) when a workspace mutation warrants it. Debounced across a round.2 — Multi-target preview: every editable tab is its own live editor
Dropped the single
session.target. A session now holds N live editors at once, one abstraction per tab: script / flow / raw_app always mount their in-process builder, never an iframe (iframes remain only for real pages — runs / schedules / home).SessionWrappereditor pane + single-target machinery; scoped raw-app reload toargs.path.(kind,path)editor cells (flowCell/scriptCell/rawAppCell); public interface preserved via a temporary shim.resolvePreviewTab(url)drops the target param → any editable route mounts its own editor; each editor binds its own cell; draft codecs close over the cell's store (no cross-write); tab open/navigate dedupe by(kind,path).pruneEditorCellsevicts cells no open tab references (wired to a newonTabsChanged); removed the dead warm-editor LRU.session.target(field + setter + reads); preview hydrates from persisted tabs alone.3 — Data-pipeline preview restored as a live editor
The P0 refactor removed the old single-target editor pane —
PipelineEditorView's only mount point — soopen_preview(kind="pipeline")opened nothing, even though the chat tool + system prompt still make it the first step of pipeline authoring. Restored inside the tab model:/pipeline/<folder>tab now mounts the in-processPipelineEditorView(never an iframe).parsePipelineRoute+resolvePreviewTabmap the folder to apipelineeditor slot;previewTargetForSessionTarget('pipeline')returns the folder route (wasundefined).runtime.pipelineEditorState, soopen()keeps at most one pipeline tab and retargets it to the requested folder rather than opening a second editor that would fight over the shared state.PipelineEditorViewgained anactiveprop; AI-helper registration + the live-badge poll gate onisActiveSession && active(only the foreground tab of the foreground session).build_pipeline_node/edit_pipeline_nodeonruntime.managerdirectly, not viagetAiChatManager().PreviewTabHostmounts it in the preview panel — outside theSessionWrappersubtree that provides the scopedaiChatManagercontext — so a context lookup fell back to the app-wide singleton and the tools never reached the session chat (the model then fell back towrite_script, whose draft never lands on the canvas). Using the runtime's own manager fixes it.Sync with main
Merged
origin/maintwice:openRunInPreviewis wired into the new tab model; the deleted singleton API stayed deleted; two stale comments main added (referencing the removedruntime.savedFlow.val/runtime.rawApp.val) were corrected to describe the editor cells.DraggableTabscomponent (drag-reorder + keyboard nav,reorder()on the tab model), while the tab content stays the multi-targetPreviewTabHoststack — the two land in distinct regions and coexist.Validation
npm run check→ 0 errors.reordersuites).build_pipeline_nodeadds a node live to the canvas; after the feat: shared tab system, universal markdown code blocks, subtle scrollbars #10003 merge theDraggableTabsstrip drives the editor hosts and tab-switching works.Screenshots
The Playwright screenshot tool times out on the Monaco/graph-heavy preview pane (5s MCP cap), so no static image is attached — but each behaviour above was exercised live via the accessibility tree (two editors with distinct content and no iframe fallback; the pipeline canvas with its nodes; single-tab retarget crm → crm_analytics).
🤖 Generated with Claude Code