Session lifecycle, worktree projects, and directory picker#759
Open
backnotprop wants to merge 28 commits into
Open
Session lifecycle, worktree projects, and directory picker#759backnotprop wants to merge 28 commits into
backnotprop wants to merge 28 commits into
Conversation
The daemon now tracks per-connection client state: tab visibility and active session ID. The frontend reports these via a new `client-state` WebSocket message type on connect, visibility change, and route navigation. The event hub exposes `getFrontendState()` which returns whether any frontend is connected, any tab is visible, and which sessions are actively being viewed. This is the foundation for smart session opening — the daemon will use this state to decide between opening a browser and sending an in-app notification.
The daemon now decides how to present new sessions based on frontend connection state. If a frontend tab is connected and visible, it sends a notification event (no new tab). If no frontend is connected or the tab is backgrounded, it opens a browser. - Add presentSession() to daemon runtime with decision matrix - Add legacyTabMode config: always opens browser when enabled - Remove handleServerReady/handleReviewServerReady/handleAnnotateServerReady calls from CLI hook — the daemon handles it - Add browserAction field to POST /daemon/sessions response - CLI sessions --open command kept as-is (explicit user action)
Phase 3: When the daemon notifies instead of opening a browser, it publishes a session-notify event. The frontend shows an auto-dismissing toast (8s) with mode, project, and an Open button. Toasts are gated on document.visibilityState — queued when tab is backgrounded, flushed on return. Phase 4: Completed sessions no longer disappear from the sidebar. The terminal-status splice in event-store was removed — sessions now update in-place with their new status. Only explicit session-removed events cause removal.
SidebarProvider defaultOpen is now based on the initial route: collapsed when loading /s/:id directly, open when loading /. Users can still toggle the sidebar manually after the initial render.
When a session completes, the daemon writes a content snapshot to ~/.plannotator/sessions/<id>.json before disposing the handler. Snapshots capture the plan markdown, diff data, or annotation content — everything the frontend needs to render the session read-only. The daemon server serves snapshot content when a request hits a disposed or missing session. This means completed sessions survive page refresh and daemon restart. Snapshots are capped at 5MB to avoid oversized review diffs. Each session type provides a snapshot callback in the factory that closes over its content at creation time.
When legacyTabMode is set in config.json, the daemon always opens a browser (already wired in Phase 2), and both surfaces render the full-screen CompletionOverlay with auto-close instead of the inline CompletionBanner — even in embedded mode. This preserves the old tab-per-session + auto-close experience for users who prefer it. The legacyTabMode flag flows through getServerConfig() → /api/plan and /api/diff responses → surface state.
Completed sessions from previous daemon runs now appear in the sidebar immediately. On startup, the daemon reads all snapshots from ~/.plannotator/sessions/ and creates completed records in the store. These records have no handlers but serve content via the snapshot fallback in the server.
Projects that are git worktrees auto-detect their parent repo and nest underneath it. The landing page shows projects as collapsible tree nodes — expanding a project fetches its worktrees via git worktree list and shows them with branch names. - DaemonProjectEntry gains optional parentCwd and branch fields - addProject detects worktrees via git rev-parse --git-common-dir and auto-registers the parent repo - New GET /daemon/projects/worktrees?cwd= endpoint lists worktrees - Frontend ProjectTable refactored to collapsible tree with worktree children, selection passes cwd to session creation - Session labels include branch name when created from a worktree cwd
…abels - Parent auto-registration now adds directly to the flat array instead of calling registerProject, avoiding name-based dedup that could overwrite unrelated projects with the same derived name - All session modes (annotate, archive, goal-setup) now include the branch name in their labels, matching plan and review
When adding a directory that is a worktree, the daemon auto-creates the parent project. But the store only added the returned entry (the worktree child), leaving the parent missing from the frontend state. Since the worktree has parentCwd set, the topLevel filter found zero entries and nothing rendered. Fix: when the added entry has parentCwd, re-fetch the full project list so the auto-created parent is included.
Each worktree gets a lastActive timestamp derived from: 1. Git index file mtime (updates on add, checkout, stash — reflects active work even without commits) 2. Last commit timestamp (fallback if index unavailable) 3. Directory mtime (fallback for brand new worktrees) All three signals are cross-platform (fs.statSync + git log). Worktrees are sorted most-recently-active first.
- Don't call presentSession for origin "plannotator-frontend" — the frontend already navigates to the session it just created - Strip internal prefixes from session label in toast description, suppress description when it matches the project name - Style toast action button with theme primary colors - Widen project selector to max-w-2xl - Remove opacity-50 from worktree icons
The Add Project dialog is now a searchable directory browser inspired by OpenCode's project picker: - Type a path (~/work/, /Users/...) and see child directories listed - Arrow keys to navigate, Enter to select, Tab to navigate into a dir - Recent projects shown at top for quick re-selection - ~ expansion handled server-side - Hidden directories (.git, .cache, etc.) filtered out - 150ms debounced directory listing for responsive typeahead New daemon endpoint: GET /daemon/fs/list?path= returns child directories for any path with ~ expansion.
- Fetch worktrees eagerly on mount instead of on expand, so the chevron only appears when there are actual worktrees to show - Projects without worktrees get a plain spacer instead of the chevron - Add a "Worktrees" section label above the expanded list
…folders - The whole project row is now one selectable button with folder icon consistently at the left - Worktree expand chevron moved to the right end, only visible on hover area — doesn't block the selectable feel - Removed all GitBranch icons from worktree entries — just indentation and the branch/worktree name - Projects without worktrees have no chevron at all, no spacer needed
Applied Emil's design engineering principles: - Interactive rows use text-foreground by default, not text-muted-foreground. Muted text is only for metadata (paths, timestamps, section labels). Items should look clickable at rest, not disabled. - Replaced all opacity-60 on secondary text with text-muted-foreground (semantic token instead of raw opacity) - Borders use border-border (full opacity) not border-border/40 — borders should be visible enough to serve their structural purpose - Removed transition-colors (was a transition: all risk) — hover states are instant by design for frequently-used UI - Changed expand <span role="button"> to proper <button> with aria-label - Added select-none to ASCII banner (decorative element) - Used proper ellipsis character (…) instead of ... - Selected state uses bg-primary/10 instead of bg-primary/8 for clearer feedback
Applied Emil's design engineering principles to AddProjectDialog: - Interactive rows use text-foreground by default, not text-muted-foreground - Removed all /50 and /60 opacity modifiers on borders and text — borders use border-border, metadata uses text-muted-foreground - Input uses text-base (16px) to prevent iOS zoom on focus, with sm:text-[13px] for desktop - Fixed nested <button> inside <button> (invalid HTML) — directory rows now use a <div> wrapper with two sibling buttons - Navigate-into chevron is always visible (was opacity-0 hover-only, violating "never rely on hover for core functionality") - Added aria-labels to close button and navigate buttons - Removed transition-colors from interactive rows (speed over delight) - Used proper ellipsis character (…) in placeholder and loading text - Removed unused displayName prop from DirectoryRow
…#766) * Add shadcn Dialog and Tabs primitives to frontend Dialog wraps @radix-ui/react-dialog with DiffKit prototype styling: bg-black/55 backdrop, rounded-2xl card, entrance animation with reduced-motion support. Sized at max-w-4xl for the settings dialog. Tabs wraps @radix-ui/react-tabs with vertical orientation support. Tab triggers use text-foreground by default (per design audit). Also backlogged AddProjectDialog migration to use these primitives. * Add unified settings dialog with vertical tabs layout The frontend app now has a single app-level settings dialog accessible via Cmd+, (Ctrl+, on Windows/Linux) or the sidebar Settings button. It uses a wide Radix Dialog (896px) with vertical tabs grouped into four sections: General, Plan Review, Code Review, and Integrations. Tab content components are imported from the shared UI package: - ThemeTab, KeyboardShortcuts, HooksTab (already separate files) - GitTab, ReviewDisplayTab, CommentsTab (newly exported from Settings.tsx) - SegmentedControl, ToggleSwitch (extracted to settings/shared.tsx) The existing per-surface Settings modals remain for standalone mode. * Wire per-surface gear icon to unified settings dialog when embedded Both PlanAppEmbedded and ReviewAppEmbedded now accept an onOpenSettings callback. When provided (embedded mode), the gear icon opens the app-level unified settings dialog instead of the per-surface modal. Standalone mode is unchanged — no callback means the built-in modal opens as before. * Complete settings dialog: all 4 sections, 13 tabs, AI tab Dialog now has all four sections from the plan: - General: General (placeholder), Theme, Shortcuts - Plan Review: Display (placeholder), Saving (placeholder), Labels (placeholder), Hooks - Code Review: Git, Display, Comments, AI - Integrations: Files (placeholder), Obsidian (placeholder) 7 tabs have full content (Theme, Shortcuts, Hooks, Git, Review Display, Comments, AI). 6 tabs show placeholder text indicating what settings will go there — these need the inline content extracted from the monolithic Settings.tsx in a follow-up. Reduced motion is already handled globally via theme.css and tailwindcss-animate. * Extract settings tabs: General, PlanGeneral, PlanDisplay, Saving Extracted four tab components from the Settings.tsx monolith into self-contained files in packages/ui/components/settings/: - GeneralTab: identity + auto-close (shared across all modes) - PlanGeneralTab: permission mode (Claude Code) + agent switching (OpenCode) — plan-specific, moved out of General - PlanDisplayTab: TOC, sticky actions, plan width with preview - SavingTab: plan save toggle/path, default notes app, integration quick-nav buttons with onNavigateTab callback Dialog now has 16 tabs across 4 sections. 11 have full content, 5 remain as placeholders (Labels, Files, Obsidian, Bear, Octarine). Tab grouping updated per design review: - Permission Mode and Agent Switching moved from General to Plan Review - Bear and Octarine added to Integrations section * Complete all 16 settings tabs — zero placeholders remaining Extracted remaining tab components from the Settings.tsx monolith: - LabelsTab: quick annotation labels with emoji, color, AI tips, shortcuts - FilesTab: file browser enable + directory list management - ObsidianTab: vault detection, path/folder/filename config, auto-save, vault browser toggle - BearTab: custom tags, tag position, auto-save - OctarineTab: workspace, folder, auto-save All 16 tabs across 4 sections now render full settings content: - General (3): General, Theme, Shortcuts - Plan Review (5): General, Display, Saving, Labels, Hooks - Code Review (4): Git, Display, Comments, AI - Integrations (4): Files, Obsidian, Bear, Octarine Each extracted tab is self-contained — manages its own state reads/writes via the existing utility functions and configStore. * Fix critical settings dialog issues found in eight-agent audit Critical fixes: - AISettingsTab now receives required props (providers, selectedProviderId, onProviderChange). Fetches /api/ai/capabilities when dialog opens. - PlanGeneralTab now receives origin from active session so Permission Mode and Agent Switching tabs actually render. Functional fixes: - Stale state on re-open: mountKey increments when dialog opens, forcing Radix Tabs to re-mount tab content so useState initializers re-read fresh cookie values. - Cmd+, now toggles (close if open, open if closed) instead of only opening. Remaining items deferred: Tater Mode toggle in PlanDisplayTab, ThemeTab preview mode, diffTabSize in ReviewDisplayTab, content gaps in Obsidian/ Octarine/Saving helper text. * Self-review fixes: stale AI provider, type safety, ObsidianTab fetch - Re-read aiProviderId from cookies on each dialog open (was stale if changed via per-surface settings between opens) - Remove `as any` cast on origin prop — PlanGeneralTab now accepts Origin | string | null - ObsidianTab no longer depends on useSessionFetch (breaks outside SessionProvider). Uses fetchFn prop with globalThis.fetch default. Vault detection gracefully falls back to manual input if fetch fails. * Fix critical issues from second eight-agent audit 1. AI capabilities fetch: route through active session API path (/s/:id/api/ai/capabilities) instead of broken root /api/ path. Skips fetch when no session is active — AI tab shows empty state. 2. Z-index: dialog overlay and content bumped from z-50 to z-[110], above CompletionOverlay's z-[100]. Settings dialog now renders on top of everything except toasts. 3. Keyboard shortcuts: unified dialog now shows BOTH plan and code review shortcuts with section labels, not just plan mode. * Full parity: every setting from original Settings.tsx now in extractions Parity fixes: - Tater Mode toggle added to PlanDisplayTab (with optional props) - diffTabSize stepper added to ReviewDisplayTab (was only in popover) - SavingTab: description text under Default Save Action select restored - SavingTab: auto-reset effect when integration becomes unavailable - ObsidianTab: filename format variables hint line restored - ObsidianTab: filename separator helper text restored - ObsidianTab: frontmatter preview block restored - OctarineTab: workspace and folder helper text restored - LabelsTab: tip editor onFocus cursor handler restored - PlanGeneralTab: agent warning SVG icon restored - PlanGeneralTab: stale agent "not found" disabled option restored Resource cleanup: - Hidden <Settings> no longer mounts in embedded mode — both plan review (skipBuiltInSettings prop) and code review (guard on externalOpenSettings) skip rendering when the unified dialog handles it * Add daemon global settings API — settings work without active sessions Five new daemon endpoints for settings that previously required a session-scoped API path: - GET/POST /daemon/config — read/write ~/.plannotator/config.json - GET /daemon/git/user — detect git config user.name - GET /daemon/vaults — detect Obsidian vaults - GET /daemon/hooks/status — hook file status + PFM reminder All are thin wrappers around existing functions in packages/shared/ with no session context needed. Frontend routing: added globalFetchBase fallback to apiFetch. When __PLANNOTATOR_API_BASE__ is unset (landing page, no session), config writes route through /daemon/config. Session base still takes precedence when set. Settings dialog: fetches gitUser from /daemon/git/user on open, initializes configStore from /daemon/config, passes daemon-routed fetch to ObsidianTab and HooksTab so vault detection and hook status work from the landing page. * Fix ObsidianTab vault URL: accept both /daemon/vaults and /daemon/obsidian/vaults * Fix theme preview mode and slow theme switching with keep-alive Theme preview: wired onPreview to ThemeTab in unified dialog. Clicking "Launch Preview Mode" hides the dialog and opens a bottom drawer with a compact ThemeTab. Escape or Done button returns to the dialog. Theme switching performance: hidden keep-alive surfaces now skip color transitions entirely via [style*="visibility: hidden"] * { transition- duration: 0s }. Only visible content animates, eliminating the lag from thousands of elements transitioning simultaneously. * Add performance scope documents: global keyboard registry + configStore Zustand migration * Move performance scope docs to backlog directory * Document performance findings: 15 identified issues ranked by impact * Update performance findings: 22 issues across 4 tiers, prioritized for normal use * Add 3 new findings from agent sweep: layout thrashing, context invalidation, getComputedStyle * Add memory leak findings: draft maps, uncancelled rAF, WS subscription accumulation * Final sweep: 39 total findings incl bundle size, static imports, backdrop-blur, monolith components * Performance Phase 1: stop hidden sessions from degrading active session Fix 1 — React.memo on SessionSurface: Layout re-renders (sidebar toggle, dialog open, session switch) no longer cascade into every mounted session's component tree. The bootstrap prop is reference- stable so memo always bails out for hidden sessions. Fix 3 — Scope document.querySelector to session container: - StickyHeaderLane: new containerRef prop, queries within the session's root instead of the global document. Prevents hidden sessions from attaching ResizeObservers to the active session's elements. - TableOfContents: scopes block-id query to scrollViewport instead of document. Prevents hidden session TOC clicks from scrolling the active session. - PlanCleanDiffView: new containerRef prop for diff-block-index query. Prevents hidden session annotation selections from highlighting blocks in the active session. Fix 4 — Gate document-level mutations on visibility: - CSS custom properties (--diff-font-override etc.) now set on rootRef.current instead of document.documentElement. Each session scopes its own font/tab-size vars. No more global style recalc from hidden sessions writing to :root. - document.title gated with isVisible() in both surfaces. Hidden sessions no longer overwrite the active session's title. * Self-review fixes: PlanCleanDiffView self-scoping, font-size CSS selectors - PlanCleanDiffView now has its own rootRef on its wrapper div, so the querySelector for diff-block-index elements scopes to its own tree without needing containerRef threaded from the parent - CSS font-size-override selectors changed from :root[style*="..."] to .has-font-size-override class — the :root selectors broke when CSS vars moved from document.documentElement to rootRef.current - Cleanup removes the class on effect teardown * Fix tab size not applying to diffs and title not updating on session switch Tab size: moved from CSS variable on rootRef (which didn't penetrate Pierre's shadow DOM) to direct unsafeCSS injection via usePierreTheme, matching how font-family and font-size already work. Title: replaced static isVisible callback (empty deps, never re-triggered) with useSessionVisible hook that uses MutationObserver to reactively detect when Layout.tsx toggles the parent's visibility style. * Use content-visibility: hidden on inactive sessions to skip layout/paint The browser was doing full layout and style recalc on every hidden session's DOM (60-120k nodes with 3+ sessions). content-visibility: hidden tells the browser to skip all rendering work on inactive subtrees while preserving DOM state, scroll positions, and React component state. Also runs oxfmt on frontend files to fix pre-existing formatting drift. * Scroll to annotation line when clicking sidebar comment in all-files view Previously only scrolled to the file header. Now finds the [data-annotation-id] element and scrolls to it directly, with a retry loop for lazy-mounted diffs and file header fallback. * Add Zustand review store with per-session scoping Introduces a Zustand store for the code review surface, replacing annotation useState calls with store state. Panels can now subscribe to specific slices via selectors instead of re-rendering on every unrelated state change through the ReviewStateContext god object. Phase 1: annotations slice (hot path), diff-options slice, files slice. DiffHunkPreview migrated as first panel consumer. ReviewStateContext stays alive for unmigrated panels during the transition. * Fix deleted annotations reappearing after refresh When all annotations were deleted, the draft auto-save skipped the save entirely, leaving the stale draft on the server. On refresh it restored the deleted annotations. Now tracks whether a draft exists on the server and issues a DELETE when annotations drop to zero. * Stabilize callbacks via getState() and memo FileTreeNodeItem Migrate files and activeFileIndex to store-owned state (remove sync bridge). Stabilize 7 callbacks + keyboard handler by reading pendingSelection, files, activeFileIndex, externalAnnotations, selectedAnnotationId, and allAnnotations from storeApi.getState() instead of closing over them. Callbacks now have stable references that don't recreate on every hover or file switch. Wrap FileTreeNodeItem in React.memo — 531 instances were re-rendering 29 times each (15,399 total, 1,202ms) due to parent cascades. Baseline: 55% of render time (4,825ms) was full-tree cascades triggered by ReviewApp. These changes eliminate the callback instability that caused most of those cascades. * Add React.memo to shared UI components and stabilize plan review props Wrap BlockRenderer, InlineMarkdown, CodeFileLink, and TableBlock in React.memo. These render hundreds of times per plan/review session due to parent cascades — profiler showed 778 BlockRenderer renders and 888 InlineMarkdown renders in a single session. Memo prevents re-renders when the block content and callbacks haven't changed. Stabilize plan review App.tsx props to Viewer: replace inline arrow functions (onPlanDiffToggle) with useCallback, replace inline linkedDocInfo object with useMemo. These created new references on every render, defeating any memo on child components. * Fix backLabel reference-before-initialization in plan review The linkedDocInfo useMemo was placed above the backLabel variable it depends on, causing a crash on plan session load. Moved the useMemo below backLabel's declaration. * Unified frontend: Git Dashboard, PR listing, sidebar redesign, and settings cleanup (#765) * Add PR listing, multi-select launch, stacked PR grouping, and project management to frontend landing page - Fix worktree project registration: session factory now uses addProject() instead of registerProject() so worktrees nest under parent projects - Fix directory typeahead: handle partial paths by splitting into parent dir + prefix filter - Add daemon endpoint GET /daemon/projects/prs for listing PRs via GitHub/GitLab CLI - Add PR list with stacked PR detection using headBranch/baseBranch chain matching - Add multi-select for PRs and worktrees with parallel session launch via Promise.allSettled - Restructure worktrees from badge pills to row layout matching PR list style - Add tabbed expansion panel (PRs default, Worktrees) under each project - Add shared formatSessionLabel() for clean display names across sidebar, landing page, and toasts - Add right-click context menu to remove projects with session cancellation and history cleanup - Add PullRequestIcon with state-based coloring (open/merged/closed) * Clean up settings dialog: proper header, version info, compact display tab layout - Replace absolute-positioned close button with dedicated header bar to prevent toggle overlap - Remove settings cog icon, add version number and send feedback link - Rewrite Code Review Display tab with grouped sections (Typography, Layout, Options) - Replace font size slider with +/- stepper matching tab size pattern - Make segmented controls inline with labels for compact layout - Add live preview showing both font family and size together - Fix Theme tab spacing: add divider and gap between Mode and Theme sections - Move Line Backgrounds toggle to end of Options (sub-setting at bottom) * Git dashboard, settings cleanup, sidebar redesign, and PR data pipeline - Add PRDetailedListItem type and fetchPRDetailedList for dashboard-specific PR data - Add daemon endpoint GET /daemon/projects/prs/detailed with 30s cache - Add git-dashboard-store (Zustand) with parallel multi-project fetch and dedup - Build Git Dashboard with PRRow, PRGroup, MetricCards components - Dashboard groups PRs into Open/Draft/Recently Merged with scroll-to navigation - Clicking a PR row launches a review session via createReviewSession - Add CSS translateX slide transition between landing page and dashboard - Clean up settings dialog: proper header bar, version info, send feedback link - Rewrite Code Review Display tab with grouped sections and compact inline controls - Fix Theme tab spacing between Mode and Theme sections - Redesign sidebar: sprite mascot header, Instrument Sans brand font, version display - Remove session status dots/checkmarks, move mode icons to group headers - Compact sidebar session rows (text-xs, h-7) with alignment spacers - Embed sidebar trigger in landing page card instead of dedicated nav bar - Add right-click context menu for project removal with session/history cleanup - Fix directory typeahead prefix filtering for partial path input - Fix worktree project registration via addProject() instead of registerProject() - Add multi-select session launch with parallel Promise.allSettled - Add stacked PR grouping via headBranch/baseBranch chain detection - Add shared formatSessionLabel() for clean display across sidebar/landing/toasts - Deduplicate PRs across projects sharing the same remote * Add hover-triggered peek sidebar when sidebar is closed - Extract AppSidebarContent for reuse between real sidebar and peek - SidebarPeek renders a floating panel on left-edge hover (80vh, centered) - Backdrop overlay fades in at bg-black/30 behind the panel - Panel slides in/out at 150ms, backdrop at 200ms - Uses bg-sidebar token for consistent background with real sidebar - Remove session tooltip hovers - Only active when sidebar is collapsed * Remove unused asset files (banners, mascot, sprite backups, index.html) * Address PR review findings: security fix, dedup, lazy loading, error surfacing - Fix path traversal: sanitize project name before use in rmSync path - Switch project deletion from name-based to cwd-based identification - Deduplicate archive launches by cwd when multiple selections share a project - Gate dashboard PR fetch on visibility (only fetch when dashboard slide is active) - Gate worktree fetch on expanded state (don't shell out until user expands) - Track project count in dashboard store for cache invalidation on add/remove - Surface auth and CLI errors in dashboard instead of showing misleading empty state - Consolidate duplicate PR ref resolution into single handler for both endpoints - Delete unused carousel.tsx and remove motion dependency - Move @radix-ui/react-context-menu from devDependencies to dependencies * Tighten path sanitization: reject dot-only names in history cleanup * Fix review findings: timer leak, stale tabs, blank dashboard, clear on remove, path guard - Fix SidebarPeek timer leak: clear existing timeout before setting new one in hide() - PR tabs now refetch after 30s instead of being permanently cached - Dashboard isEmpty derived from visible groups, not raw array (fixes blank state for closed-only repos) - Dashboard clears stale PRs when all projects are removed - Defense-in-depth: verify resolved rmSync path is inside history root before deleting - Fix handleRemove missing project.cwd in useCallback dependency array * Move tater mode to configStore and fix formatting from L10 merge Tater mode was useState inside plan review App.tsx — unreachable from the unified settings dialog. Now lives in the global configStore as a cookie-backed setting. PlanDisplayTab reads it directly via useConfigValue, no props needed from AppSettingsDialog. Also runs oxfmt on files from the L10 merge (git dashboard, sidebar, settings dialog). * Address PR review findings: stale closures, dead code, missing deps - Fix stale closure in applyPRResponse: read currentPath before setFiles - Deduplicate allAnnotations: App.tsx useMemo now calls selectAllAnnotations - Remove dead fields from files slice (viewedFiles, stagedFiles, reviewBase, activeDiffBase and their setters — never called, data still flows through ReviewStateContext) - Wrap default ReviewApp export in ReviewStoreProvider - Add missing daemon endpoints to AGENTS.md - Add dependency array to SavingTab useEffect (was firing every render) - Add activeSessionId to AppSettingsDialog AI capabilities fetch deps * Fix fetchDiffSwitch stale closure + unstable deps, correct AGENTS.md allowlist - Read currentPath from getState() before setFiles in preserveFile branch (same pattern as applyPRResponse fix) - Remove files and activeFileIndex from fetchDiffSwitch dep array — last unstable callback, now reads from getState() consistently - Fix AGENTS.md: POST /daemon/config allows conventionalComments and conventionalLabels, not legacyTabMode and verifyAttestation * Fix L10 bugs: stale selections, PR error clearing, dashboard cache - Clean up selections when projects are removed — prevents stale selections from enabling launch buttons for deleted projects - Clear PR error on successful fetch — errors no longer stick after the user fixes their CLI setup - Dashboard cache invalidates on project identity (cwd set), not just count — swapping projects within the same count now triggers refresh
Adds "Open sessions in new tabs" toggle to General settings. When enabled, every session opens in a separate browser tab with the full-screen completion overlay and auto-close, like the classic Plannotator experience. The daemon already supports this via legacyTabMode in config.json — this just adds the UI toggle and wires it through POST /daemon/config.
…gStore migration, keyboard registry
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
legacyTabModeconfig toggle preserves old tab-per-session + auto-close behavior.Test plan
legacyTabMode: true→ each session opens new tab, overlay with auto-close