CI: /compact (self-check)#2
Open
lemotw wants to merge 107 commits into
Open
Conversation
) * refactor: replace session modal window bridges with a reactive store Introduce web/src/session/session-modals.svelte.js, a $state-backed store that owns the session viewer's modal open-state (shortcuts, model-usage, fork, cat-settings, label). SessionPage binds the modal components to it and every opener — the command menu, keyboard globals, content runtime, and cat-gatekeeper — imports the store helpers instead of reaching through window.__piOpen*. This removes the five global openers and the onMount ordering hazard they created. The store is live-only and never pulled into the static export (TestExportBundleIsSelfContained stays green). Phase 5 step 1. * refactor: replace session component-handle window bridges with a registry Add web/src/session/session-runtime.js, a plain singleton registry whose slots (annotations, artifacts, rightSidebar, layout) hold the imperative control surfaces child components used to publish on window.__pi*. Each component assigns its slot on mount and clears it on destroy; consumers (SessionTree, LiveReload, session-ui-runner, session-globals, SessionPage) read sessionRuntime.<slot>?.method() instead of reaching through window. __piCatGatekeeper was a window round-trip inside CatGatekeeper.svelte; it now uses a hoisted local controller. SessionPage resets the registry on unmount. The registry is dependency-free and stays out of live-only code, so the export bundle (rebuilt) no longer references any of these globals and TestExportBundleIsSelfContained stays green. Phase 5 step 2. * refactor: move toggle-state handle into the session runtime registry Replace the last window globals in the session UI layer with the sessionRuntime registry. setupSessionUi now registers the toggle controller as sessionRuntime.toggleState instead of writing window.sessionToggleState + window.applyToggleStateToNode; the message-pane afterRender hook (live content-runtime and the static export) reads sessionRuntime.toggleState ?.applyToNode(). Drops the dead window.sessionToggleState (no readers) and export's __piFilterState (written, never read). The imperative setupSessionUi runner itself stays: it is shared by the live app and the server-less export to wire the static session.html DOM, so it is legitimate shared infrastructure, not migration residue. Folding that wiring into components would require migrating export's static sidebar chrome to a component mount (a separate, E2E-gated change). Phase 5 step 3. * feat: client-side SPA navigation via pushState/popstate Make App.svelte's router reactive: it now listens for popstate (back/ forward) and wraps history.pushState/replaceState to swap the active page when the pathname changes, instead of reading the path once at mount. Route the index/settings navigation through a shared navigate() helper so links no longer trigger full page reloads: session cards, the command palette, new-session creation, the settings menu link, the Cmd+, shortcut, and the settings back link. External target=_blank links and modified clicks (Cmd/Ctrl/Shift/Alt, middle-click) keep their default browser behaviour. * feat: client-side session→session navigation Key <SessionPage> on the ?id= query param so navigating between sessions (same pathname, different id) tears down and remounts it instead of falling through as a no-op — SessionPage reads ?id= only at mount. App.svelte now tracks search alongside pathname to drive the key. Route fork/clone/new-session navigation (CommandMenu, SessionHeader, the entry fork in session-content-runtime) through navigate() so they no longer trigger full page reloads. Within-session navigation never mutates the URL, so the key stays stable while reading a session. Also remove the pi-session-reload listener in SessionPage's onMount cleanup, which a remount would otherwise leak. * fix(e2e): restore cat-gatekeeper test hook and pin its sleep window The dd7d654 registry refactor dropped window.__piCatGatekeeper, which the cat e2e test uses to force a break (skipToBreak) without waiting out the 25-min focus timer. The controller has no in-app consumers, so re-expose it as a plain window seam set on mount / cleared on destroy rather than a sessionRuntime registry slot. Also pin the test's bedtime == wakeup (zero-width sleep window): the default 23:00–07:00 window made the gatekeeper enter bedtime mode when the suite ran overnight, showing the sleep overlay instead of the break and failing across all browser projects. * test(e2e): de-flake load-earlier by dropping the intermediate poll The per-window expect.poll (banner gone OR button re-enabled) was the flake source: prepending the merged conversation triggers a synchronous full re-render that, under the parallel matrix, briefly jams the main thread and can blow the fixed 15s poll budget even though the load succeeds (the failure snapshot showed the banner already gone and the earliest message rendered). Drive the clicks with click()'s built-in enabled-auto-wait plus a best-effort settle, and let only the final auto-retrying assertions (banner count 0 + earliest message rendered) be authoritative, so a transient render jam no longer fails the test. * fix: blank settings page when navigating with a custom font set AppearanceSettings.fontSelectValue() wrote $state (customVisible, customValues) while computing the font <select> value inside the {#each} font-kinds block. An {#each} is a Svelte block effect, so that write throws state_unsafe_mutation — but only when the component mounts during a client-side route swap (the new pushState navigation) rather than a fresh page load, and only when a custom (non-builtin) font is configured so the branch runs. The error aborted the swap, leaving the settings page blank (or the source page stuck). Make the template-called helpers pure: derive the select value and the custom-field visibility from settings, and track in-progress edits in a draft written only from event handlers. Add an e2e regression (client-side nav to /settings with a custom font) that blanks without this fix. * test: fix data race in fakeSender handleNewSession initializes the worker on a background goroutine, so the chatSender's methods (EnsureWorker/SetModel/GetState/...) run concurrently with test assertions that read the fake's recorded fields — a data race under -race (e.g. TestHandleNewSessionCopiesSourceModelAndThinking polling ensureWorkerSessionID). Guard the mutated fields with a sync.Mutex: lock the writes in each sender method (channel sends stay outside the lock) and read them in tests through accessor helpers. Config fields set once at construction stay lock-free. Test-only; no production behavior changes.
* refactor(session): extract live runtime helpers * docs(dev): record session refactor goals
2d78edf to
6a0ee52
Compare
Replace the five void-dependency reads in the auto-scroll $effect with a single $derived transcriptSignature, removing the Svelte 5 anti-pattern where dropping a dep silently breaks tracking.
Introduce session-title.svelte.js as the single source of truth for the session name. SessionHeader renders it and owns the document.title sync; CommandMenu's rename and LiveReload's auto-title now update the store instead of reading/mutating #session-header-title in foreign DOM.
Add shared/links.js (repo, user-docs, Telegram) and consume it from the command menu, home menu, and About settings instead of repeating the literals. Name the command-menu close-animation timeouts (260/160ms) that mirror the CSS transition durations.
Extract newSessionSummary and applySummaryContext so ParseSummary and ParseFile share the summary init, LastActivity fallback, name precedence, and cwd/chat-disabled handling (incl. the disabled-reason string) instead of duplicating them. The per-line scan stays typed vs map to preserve the index fast path.
Extract the rename/fork/clone/load network calls into session-menu-actions.js (with unit tests), and drive the desktop popover and mobile panel from shared primaryItems/footerItems descriptors via a snippet instead of duplicating the item markup. data-action attributes and classes are unchanged, so the existing click dispatch keeps working.
Closes 3 Dependabot alerts (GHSA-hcg3-q754-cr77, GHSA-j5w8-q4qc-rx2x, GHSA-f6x5-jh6r-wrfv) covering SSH key-exchange DoS, ssh unbounded memory, and ssh/agent OOB-read panic.
Closes Dependabot alert GHSA-mh63-6h87-95cp (excessive memory allocation during JWT header parsing).
The rpc copy was never referenced; the canonical declaration lives in internal/workers/manager.go.
ChatAvailable and ChatDisabledReason are coupled, so the OR clause always reduced to ChatAvailable; use it directly.
Use the exported DefaultIdleTTL directly in NewManager.
The {#if modalOpen} block rendered nothing; modals manage their own
overlays. Drop the block and the now-unused derived.
The navigator.clipboard -> execCommand fallback was reimplemented in 5 places. Centralize it in web/src/shared/clipboard.js (copyToClipboard, returning success) and have the copy sites delegate, keeping their own flash/toast UI. Update the export guard tests to assert against the shared helper.
Export MOBILE_BREAKPOINT_PX/MOBILE_MEDIA_QUERY from session/ui/sidebar.js and route every (max-width: 900px) JS check through isMobileLayout(), including CommandMenu which previously reimplemented it inline.
handleApiForkSession/handleApiCloneSession launched initializeNewSessionWorker in a goroutine bound to the request context, so a client disconnect could cancel worker creation mid-flight and leave a half-created worker. Match handleNewSession and use context.Background().
server.New() always sets s.now, so call it directly: remove projects.go's nowTime() wrapper and chat.go's inline guard. Tests that built Server literals relied on the guard, so set now: time.Now in those, matching how production constructs the server.
AGENTS.md bans unicode glyphs for icons. Swap the annotation-delete and btw-close × for the X icon, and the btw send/stop ▷/◼ for Send/Square. Adds Square to the shared icon set.
Add share.* keys to en.js and replace the raw English title, field labels, and action buttons in ShareDialog with t() lookups (reusing menu.share and common.close).
The back-button and close-button aria-labels were hardcoded English. Use
t('common.close') and a new common.closeNamed for the titled variant.
Declare settingAutoTitleEnabled/Mode/Model constants and use them in auto_title.go and the settingDefaults map, matching the named-key convention of the other settings.
Decode the common type/level fields once into a typed struct and fold the stray if-blocks into the existing switch, instead of double-unmarshalling each line and re-checking raw[type] in four places.
… (B15) The function called time.Now() 3-4 times, so the filename and header (and the model/thinking entries) could straddle a clock tick. Capture now once and reuse it for the filename and every RFC3339 timestamp.
Replace the os.ReadFile + strings.Split full-read (which allocated the content twice and lacked a line-size guard) with the same bufio.Scanner + bounded-buffer pattern used by the other parsers.
…s (B10) Pull the four-map auto-title bookkeeping and the metrics-dashboard fields out of the Server god struct into named metricsState/autoTitleState sub-structs, so each subsystem owns its fields and lock and ownership is documented at the type level.
Replace scattered inline durations with named consts: annotation flash (1200), artifact "Copied" reset (1500), btw idle grace (3000) / status poll (1500), sessions reload debounce (500), and right-sidebar reset width (320).
…(F16) artifacts/hiddenCount/selectedId are now $derived from `collected` (live) or an imperative $state (standalone), so the self-looping untrack(setArtifacts) $effect is gone. Selection clamps to the list via a derived; preview resets on selection change.
Drive the active-tab via $state with template class:/aria-selected/hidden bindings instead of the createRightSidebarTabs classList controller, and inline the collapse/expand <body>-class logic with template-wired buttons, removing the two controller modules. Visibility stays synchronous (it shifts page layout and callers expect an immediate effect). Resize, scratchpad, and help-modal wiring remain imperative.
AnnotationLayer now declares its config (api, scopes, composerEl, countEl, callbacks, selectionDelayMs) as $props() and sets up via a reactive $effect once the api + scopes are present, dropping the imperative init() up-call and its silent no-op-if-never-called failure mode. SessionShell assembles the config — DOM anchors resolved after mount, callbacks routed through the shared session runtime — and threads it through RightSidebar, replacing the session-page-annotations.js wiring (deleted). Tests pass config as props.
6a0ee52 to
4699154
Compare
Adds a "Compact context" button to the context-usage popover plus a composer-scoped Cmd/Ctrl+Shift+K shortcut. Both call a new POST /api/compact endpoint that runs pi's dedicated `compact` rpc command (session.compact()). Compaction is NOT a chat prompt: pi's rpc prompt path only expands extension/skill/template commands, so sending "/compact" as a message would reach the model as literal text and never compact. The backend adds BuildCompactCommand, piRPCWorker.Compact, Manager.Compact and handleCompact (async; broadcasts a reload once compaction completes). UX: - "Compacting…" banner above the composer + a status label pinned to "compacting" while it runs (guarded against firing while the worker is busy). - updateContextUsage is now compaction-aware: it mirrors pi's estimateContextTokens so the context ring reflects the reduced size after a compaction entry (summary + kept entries, chars/4) instead of stale pre-compaction usage, switching to the measured value on the next turn. Tests: vitest (compact endpoint path, banner show/hide, context estimate vs measured) and Playwright (real compaction marker, banner, no prompt echo) with a stub pi that implements the compact command. Closes ygncode#41
4699154 to
3eb330f
Compare
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.
Self-check PR to run the fork's CI (test + E2E) on
feat/compact-button.Not for merge — the real PR is ygncode#74. This only exercises CI on the fork.