Skip to content

Session lifecycle, worktree projects, and directory picker#759

Open
backnotprop wants to merge 28 commits into
feat/frontend-plan-review-surfacefrom
feat/session-lifecycle
Open

Session lifecycle, worktree projects, and directory picker#759
backnotprop wants to merge 28 commits into
feat/frontend-plan-review-surfacefrom
feat/session-lifecycle

Conversation

@backnotprop
Copy link
Copy Markdown
Owner

Summary

  • Smart session opening: Daemon decides between opening a browser and sending an in-app toast based on frontend WebSocket connection state (visible, hidden, disconnected). CLI no longer calls openBrowser.
  • Session persistence: Completed sessions stay in sidebar with status badges. Content snapshots written to disk survive page refresh and daemon restart.
  • Legacy tab mode: legacyTabMode config toggle preserves old tab-per-session + auto-close behavior.
  • Worktree-aware projects: Adding a worktree directory auto-detects the parent repo. Projects show collapsible worktree hierarchy sorted by last activity.
  • Directory picker: Searchable typeahead replaces manual text input for adding projects. Recent projects, path navigation, Tab to drill down.
  • Design audit: Fixed color contrast, removed opacity abuse, proper accessibility (aria-labels, real buttons, 16px inputs for iOS).

Test plan

  • No frontend open → CLI plan command → browser opens
  • Frontend visible → CLI plan command → toast appears, no new tab
  • Frontend backgrounded → CLI plan command → new tab opens
  • Approve plan → banner shows, session stays in sidebar with badge
  • Refresh after approval → content loads from snapshot
  • Restart daemon → old sessions appear in sidebar
  • Add worktree directory → parent auto-detected, hierarchy shown
  • Expand project → worktrees listed, sorted by activity
  • Directory picker → type path, arrow keys, Tab to navigate, Enter to select
  • legacyTabMode: true → each session opens new tab, overlay with auto-close

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.
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