Skip to content

UI 2.0: reskin the production frontend to the prototype design#832

Open
backnotprop wants to merge 60 commits into
feat/single-server-runtimefrom
feat/ui2-plan
Open

UI 2.0: reskin the production frontend to the prototype design#832
backnotprop wants to merge 60 commits into
feat/single-server-runtimefrom
feat/ui2-plan

Conversation

@backnotprop
Copy link
Copy Markdown
Owner

Reskins the production frontend (the single-server daemon shell) to match the DiffKit goal-prototype design. Stacks on #733 (single-server-runtime); base is `feat/single-server-runtime`, not `main`.

39 commits, grouped by theme:

Design foundation

  • UI 2.0 design docs (legacy state → prototype target → transfer map) and locked decisions
  • Simple theme — faithful port of the prototype palette
  • packages/ui primitive foundation: cn util, Card, Button (canonical home, + success/accent variants), Badge, StatePill, Textarea, DropdownMenu
  • One shared design-system stylesheet across frontend + portal

Header reskin

  • App bar, annotation toolstrip (animation preserved), and diff-badge bands rebuilt onto the primitives
  • Prototype-matched header button styling + custom sidebar-trigger glyph

Plan document reskin

  • Optional grid background, default off, via the config store
  • Flat/embedded plan card (grid-off) vs floating card (grid-on); in-plan sidebars unified to bg-card
  • Native scrollbars — OverlayScrollArea gutted to a thin shim, dead overlayscrollbars deps/CSS removed

Settings consolidation

  • Unified into one shared, daemon-aware dialog; the monolith is deleted
  • Plan-width moved to the config store (+ ultrawide tier); diff-option constants extracted

Annotation / comment restyle

  • Flat prototype look; token-based comment color (--annotation-comment, blue only in Simple theme — no cross-theme regression)
  • Redline backspace glyph restored as a RedlineIcon component

Sidebar + sprites

  • Tater sprites consolidated into one packages/ui/components/sprites/ module; scattered sprite_package_* dirs removed
  • Sidebar: "New project" button + "Projects" label, worktree "W" glyph, lowered project tree; AddProjectDialog filters recents while typing

Resize fluidity (latest)

  • Resize handles rewritten: Pointer Events on window + rAF coalescing + imperative apply (render-free, drives width through a :root var so heavy hosts don't re-render mid-drag)
  • Fixed a global width-transition that was leaking onto every element and making panels trail the cursor — now scoped to the sidebar only; all panels snap 1:1

Tooling

  • scripts/launch-session.sh ad-hoc launcher; prototype sample plan as test fixture 16

Test plan

  • bun run typecheck + bun run build:hook green
  • Manual: global sidebar, in-plan Contents, and Annotations panels all drag smoothly (render-free); themes unaffected; sprites render

- legacy-design-state.md: current production UI anatomy
- prototype-design-state.md: the DiffKit goal-prototype as authoritative target
- transfer-map.md: current→target diff + phased migration plan
Ports the DiffKit goal-prototype's shadcn-neutral palette as a first-class
theme. Pins explicit --surface-0/1/2 elevation (stepping up from the card)
rather than the global muted-derived surfaces, preserving the prototype's
layered look. Maps the prototype's light-default :root → .theme-simple.light
and .dark → .theme-simple (dark default) to match Plannotator's polarity.
Stand up the shared primitive layer in packages/ui (the package both the plan
and code-review apps consume), mirroring the frontend's shadcn-style conventions
(cva + cn + data-slot):
- lib/utils.ts: shared cn() = twMerge(clsx)
- deps: class-variance-authority, clsx, tailwind-merge, @radix-ui/react-slot
- exports: ./components/ui/*, ./lib/*
- components/ui/card.tsx: Card + Header/Title/Description/Content/Footer

Card ports the prototype's primitive verbatim against shared tokens. Additive —
no existing component consumes it yet.
Three leaf primitives ported from the prototype against shared tokens:
- badge.tsx: cva variants (default/secondary/destructive/outline), asChild
- state-pill.tsx: tone-based status pill (open/closed/merged/muted/secondary)
- textarea.tsx: field-sizing-content auto-grow input

Additive; no consumers yet.
…ants

Move the Button primitive to packages/ui (shared by the shell and the embedded
plan/review apps) and add two token-driven variants for the plan app's actions:
- success: solid green (Approve), via --success
- accent:  soft accent tint (Feedback)

The frontend's components/ui/button.tsx becomes a thin re-export, so its
"@/components/ui/button" importers are unchanged and there is a single Button
implementation. Verified: ui + frontend typecheck, frontend single-file build.
Radix-backed dropdown menu (shadcn-style) to replace the plan app's hand-rolled
ApproveDropdown / QuickLabelDropdown / header menus. Ported from the prototype with
lucide-react icons; adds @radix-ui/react-dropdown-menu to packages/ui.
Resolve the transfer-map §8 forks: keep dark-first polarity, keep all ~50 themes,
keep the production project→worktree sidebar, no Cmd+K command palette, keep
highlight.js, keep production-grade markdown rendering, rebuild primitives inside
packages/ui (not vendored). Code-review layout engine deferred (out of plan-app scope).
Full catalog of the header surface before any styling change: the shell, the
per-mode button sets (callback/goal-setup/annotate/normal), action buttons +
Approve hover-warning, the OpenCode Approve split-dropdown + agent-switch
persistence, panel/AI toggles, the dense Options menu (theme switcher, Settings,
Export, Agent Instructions, Download, Print, Share, Import, version section), and
the 11-tab Settings modal. Decision recorded: keep the Options dropdown, do not
add the prototype grid button. Everything listed must survive the reskin.
Action buttons (Feedback/Approve/Exit) -> Button primitive (accent/success/secondary
variants); ApproveDropdown split button -> Button while preserving agent-switch
persistence + dropdown; PlanHeaderMenu trigger -> ghost Button and all item icons ->
lucide (every menu item, gating, theme switcher, version section, update dot intact);
AppHeader panel/AI toggles -> ghost Button + lucide (SparklesIcon + unread dot kept;
orchestration + Approve hover-warning untouched). Integration fixes: restore exact
gray-on-disabled for the action buttons. Adds lucide-react dep to plan-review.
Swap the 6 hand-rolled tool SVGs to lucide icons and align the warning hover token
(amber -> --warning). The expand-on-hover mechanics are byte-for-byte intact
(ICON_SIZE/H_PAD/GAP/DURATION, width measurement, compact/iconOnly/default modes).
Integration fix: keep group/help tracks on bg-muted/50 (not bg-surface-1, which is
not bridged in the portal build).
Route PlanDiffBadge classes through cn(); keep the active tint on bg-primary/15 to
match the app-bar toggle active-states. Toggle behavior + counts unchanged. (DocBadges
+ PlanDiffViewer chip/icon cleanup deferred to a follow-up — out of this band's scope.)
Flexible dev launcher for manual/visual UX testing against the running daemon, using
the tests/test-fixtures/ markdown fixtures. Supports plan, annotate, and review modes;
resolves a fixture by path, number, or name fragment; prints an auth-included openable
URL. Supersedes the hardcoded scripts/test-plan-session.sh for everyday testing.
- simple.css: --success-foreground was near-black in LIGHT mode → Approve rendered
  black text on green. Set to white (matches dark mode; white-on-green like prototype).
- Remove the broken 'accent' Button variant (text-accent on bg-accent = zero contrast
  in neutral-accent themes like Simple — the cause of the grey/invisible Feedback text).
- Feedback button → outline variant + Send icon (the prototype's design: neutral
  outline, foreground text, paper-plane icon — readable in every theme/mode).
- Approve button → add the Check icon (prototype: green + white + check).
- AppHeader panel toggle → stateful PanelRightClose/PanelRightOpen (matches prototype,
  more elegant than the static panel glyph).
…ons)

The prototype's left sidebar icon is a hugeicons SidebarLeft (softer rounded panel +
divider + content ticks, stroke 1.5), not lucide's plainer PanelLeft. Replicate just
that one glyph as a custom SVG in packages/ui/components/icons/PanelLeftIcon.tsx and
use it in the shell SidebarTrigger. Avoids adding the hugeicons dependency; everything
else stays on lucide-react.
Cited anatomy of both the production plan-document area and the prototype PlanEditor,
with a transfer matrix. Key divergences: grid is always-on in production vs optional/
default-off in the prototype; production uses custom overlayscrollbars vs the prototype's
native OS scrollbars; the prototype moves metadata + toolstrip INSIDE the article and
keeps a minimal top bar; view modes differ (production reshapes layout, prototype is
width-only). Lists transfer decisions + production-wins kept out of scope.
…dth-selector spec

Records the decisions: custom scrollbar -> native OS (with #354/#540 regression
watchlist), grid optional+default-off, and the Phase-2 view-mode restructure (4-tier
width dropdown next to Copy-plan + separate Focus mode + fix the broken Settings width).
Make the plan-document grid pattern an opt-in preference (default off) instead of
always-on. Wired through the Zustand config store (a 'gridEnabled' SettingDef, like
taterMode) — App reads it reactively via useConfigValue('gridEnabled'), the toggle in
Settings -> Plan Display sets it via configStore. No uiPreferences prop-drilling.
Match the prototype: grid OFF (default) drops the heavy shadow-xl + border for a
subtle --card-shadow (clean, flat-feeling document); grid ON keeps the floating
shadow-xl+border card on the grid pattern. Viewer takes a gridEnabled prop (default
false = flat), passed from the plan App's config-store value.
The --card-shadow I'd just added includes a 1px ring that reads as a border. Grid-off
now has no shadow, no border, no ring — just bg-card + rounded corners, so the plan
feels embedded like the prototype. Grid-on still floats (shadow-xl + border).
The plan app root is bg-background but the article was bg-card; in most themes
--card != --background, so the plan read as a distinct panel (only blended in Simple).
Grid-off now uses bg-background (matches the page) -> embedded in all ~50 themes.
Grid-on keeps bg-card + shadow-xl + border (floating card on the grid).
…olor)

Revert the bg-background experiment. Keep the plan as bg-card, and make the document
scroll area (<main>) bg-card too when grid is off, so they match -> the plan embeds
seamlessly at the card color in every theme. Grid-on: main shows the grid pattern and
the plan floats as a bg-card card (shadow-xl + border).
Left content sidebar (bg-card/50), annotations panel + AI panel (bg-card/30) -> solid
bg-card (drop the translucency + backdrop-blur). The whole plan app now reads as one
continuous card surface in every theme, separated only by thin borders.
HTML content (HtmlViewer / --render-html) is always full view — edge-to-edge, no card,
no grid, ever. Does NOT get the plan document's grid/embed treatment. Records the
reference fullViewport implementation (built in feat/collab, tied to rooms) and that it
arrives via that merge — deferred here, not re-implemented.
…px rail

Replace the overlayscrollbars-react wrapper (#509) with native OS scrollbars. The
element rendered IS the scroll node now, so OverlayScrollArea collapses 217 -> ~80 lines
while preserving the exact API (onViewportReady + handle) — the ~20 consumers are
untouched. Removes the deferred-init event dance, the force-recompute ResizeObserver
(the perf cost), the ClickScrollPlugin, and the os-theme CSS.

Critically: delete the ::-webkit-scrollbar { width: 6px } rail in theme.css AND
styles.css — that thin rail was the original 'can't grab the scrollbar' bug (#354).
Native scrollbars are styled via the standard scrollbar-width: thin + scrollbar-color
(cross-browser, grabbable). Lib is now unused/tree-shaken (~48KB smaller); dep removal +
print.css/globals.d.ts cleanup to follow.
Self-review sweep — nothing left hanging:
- Remove overlayscrollbars + overlayscrollbars-react deps from packages/ui (unused)
- Drop dead print.css rules (.os-scrollbar + [data-overlayscrollbars-*]) — the native
  <main>/[data-print-region] print rule already un-clips
- Remove .os-scrollbar from the popout z-index rule in theme.css
- Dedup: drop the frontend styles.css scrollbar block (theme.css already defines it)
- Refresh stale comments (theme.css scrollbar header, useOverlayViewport jsdoc)
Verified: ui + frontend typecheck, frontend single-file build.
Extracts the PLAN_MD template literal from the DiffKit goal-prototype's PlanEditor.tsx
(Real-time Collaboration plan) into a reusable markdown fixture for visual/reskin
testing — rich content: multi-language code fences, tables, callouts, headings.
Launch with: ./scripts/launch-session.sh plan 16
The Plan Width setting didn't apply because planWidth lived in uiPreferences (local
useState) — which doesn't cross the settings<->embedded-plan-app React tree boundary.
Moved it to the Zustand config store (like grid/taterMode): reactive across trees, so
changing it now updates the plan live.

Add 'ultrawide' (px: null = no max-width cap, full width). planMaxWidth now derives
from PLAN_WIDTH_OPTIONS so ultrawide -> null -> full width.

Touches both width controls (Settings.tsx monolith + PlanDisplayTab) because they
duplicate the picker — see note in reply about that debt.
…ig-store dialog

Workflow-produced + adversarially-reviewed plan (13 units). Key findings: most migration
already done (settings on config store, embedded apps already route to global dialog);
monolith kept alive by only 4 import sites + the portal (the blocker). Adversarial pass
caught a real regression (identity re-tag is wired only through the monolith) + that
code-review's aiProviders is not settings-only. Records units, critical path, fixes, and
the open product decisions (portal settings, scope, section-visibility UX).
…diffOptions.ts

First cut severing the monolith: move the 7 diff-option constant arrays out of Settings.tsx
into a neutral settings/diffOptions.ts; repoint DiffOptionsPopover off the monolith. Also
record the LOCKED decisions (one universal dialog, daemon-aware config store, full migration).
…onolith-free

Sequential extraction (workflow waremxcbr, per-unit adversarial verify):
- U2 GitTab -> settings/ReviewGitTab.tsx
- U3 ReviewDisplayTab -> settings/ReviewDisplayTab.tsx (co-located SegmentedControl/ToggleSwitch for visual parity)
- U4 CommentsTab (+ CCLabel helpers) -> settings/CommentsTab.tsx
- U5 AppSettingsDialog repointed to the new files -> imports NOTHING from the monolith

All config-store-only, byte-for-byte parity verified. Monolith still compiles (its own
JSX + the portal/standalone path) until later units. Verified: frontend typecheck +
build, AND build:portal green.
…tag, code-review rewired

- U6: global dialog defaults to the active session's section (plan->plan-general,
  review->review-display, else general); all sections stay reachable; aiProviders type
  carries models. (Fixed the plan-tab default the verifier caught.)
- Identity re-tag preserved via a decoupled window event ('plannotator:identity-change'):
  identity util fires old->new; both apps subscribe + re-author annotations. Works from
  the global dialog with no prop threading; idempotent on the monolith double-fire.
- U7: code-review deletes its dead <Settings> mount + monolith import + openSettingsMenu;
  opens only the global dialog. KEEPS the aiProviders fetch (drives the live AI chat).
Verified: ui+frontend typecheck, frontend build, build:portal.
One SettingsDialog (packages/ui/components/settings/SettingsDialog.tsx) now
serves every surface, adapting via a `daemonAvailable` flag instead of being
wired to the frontend app-store:

- Daemon/session present (frontend): full dialog, server-synced, all tabs.
- No daemon (standalone plan editor / portal): same dialog, cookie-only,
  daemon-only tabs hidden (AI, Hooks, git-name, legacyTabMode).

- New shared dialog + Dialog/Tabs primitives in packages/ui (radix-tabs dep).
- AppSettingsDialog is now a thin frontend adapter feeding the shared dialog
  its session context (mode/origin/apiBase) from the app-store.
- Plan editor's standalone built-in (AppHeader) renders the shared dialog
  (sessionContext=null, daemonAvailable=false); embedded path unchanged.
- configStore skips the server POST when no daemon/session base resolves
  (no doomed request in the portal); cookie writes unchanged.
- Deleted packages/ui/components/Settings.tsx (the last monolith).

Verified: packages/ui + apps/frontend typecheck clean; build:hook (verified
single-file) and build:portal both green; two adversarial reviews found nothing.
The portal (share.plannotator.ai) was shipping unstyled on this branch: the
UI2.0 redesign wired the frontend's Tailwind entry but never the portal's, so
the portal built a ~4KB stylesheet with zero utilities. Both apps render the
same plan editor, so the design system now lives in ONE shared file imported
by both — they look identical, one source underneath.

- New packages/ui/design-system.css: fonts + theme tokens + tailwindcss +
  animate plugin + the @theme bridge extras (sidebar/surface) + @layer base
  surfaces + editor animations + plan-review rules. @source globs are anchored
  to this file so they resolve for both consumers.
- Frontend styles.css imports the shared file and keeps only shell-only bits
  (its own @source scans, sidebar transitions, grid, print, scroll-shadow).
- Portal imports the shared file (replacing the styles-only import); CDN font
  and hljs <link>s dropped (now bundled).
- packages/ui gains tailwindcss + tailwindcss-animate + shared fonts as deps so
  the shared file resolves them deterministically.
- Elegance: the base color/radius/font bridge stays solely in theme.css (also
  used standalone by the marketing site); the shared file only adds the
  sidebar + surface extras, not a duplicate base bridge. Instrument Sans is
  frontend-shell-only, imported from the frontend entry so the portal does not
  bundle an unused font.

Verified by rebuilding both: portal CSS ~4KB -> 186KB with real utilities, all
theme tokens (--primary x235), surfaces, animate classes, and plan-review
rules; frontend unregressed (identical atomic class set). Two adversarial
reviewers independently rebuilt and confirmed parity; 0 critical/major.
Addresses the three Act-On findings from the interrogate review.

- toc/sticky -> config store: migrate tocEnabled + stickyActionsEnabled from
  the non-reactive uiPreferences module to the reactive config store (same
  cookie keys, default true), so toggling Auto-open Sidebar / Sticky Actions
  takes effect live everywhere, including the standalone/portal plan editor.
  Delete UIPreferences/getUIPreferences/saveUIPreferences (keep PlanWidth +
  PLAN_WIDTH_OPTIONS). Drop the uiPrefs state in plan App.tsx.
- Dead code: remove the 5 vestigial settings-feed props (taterMode, gitUser,
  onTaterModeChange, onIdentityChange, onUIPreferencesChange) from AppHeader +
  the App.tsx call site, plus the orphaned handleTaterModeChange + gitUser
  state, and GeneralTab's dead onIdentityChange prop. Re-tag flows through the
  window event; taterMode toggles via the config store.
- AI provider: pass origin to AISettingsTab in SettingsDialog so per-origin
  provider overrides actually persist (was silently dropped).
- Extras: guard SettingsDialog's daemon fetches against post-close state
  writes; drop the stale Instrument Sans mention in the portal fonts comment.

Verified: packages/ui + apps/frontend typecheck clean; build:hook + build:portal
green; two adversarial reviewers found 0 actionable issues.
Match the DiffKit prototype's annotation UI while retaining all behavior.

- AnnotationPanel cards: flatten from bordered badge-pill cards to borderless
  hover:bg-surface-1/50 (selected: bg-surface-1 ring-1 ring-border/50) cards
  with a single header row (type word + author·time + hover-revealed
  pencil/trash), a line-clamped mono quote, a text-[13px] body, and an inline
  edit textarea with Button xxs Cancel/Save. Panel header flattened to h-10,
  count pill bg-primary/10, two-line empty state.
- Toolstrip active-tool indicators use the prototype's blue/red/yellow; the
  toolbar + card type-word keep the theme's accent/destructive so the card
  color stays consistent with the in-document highlight CSS (.comment amber,
  .deletion red). Global label uses purple (legible on light + dark cards).
- EditorAnnotationCard is shared with the code-review sidebar: added a
  variant ('plan' | 'code-review') prop so code-review keeps its bordered
  look and only the plan editor gets the flat restyle.

Retained + verified: click annotation -> scroll-to-doc (data-annotation-id +
listRef effect), edit-in-place (Cmd+Enter/Esc), delete/redline, image
attachments, global comments, diff-view annotations, copy/share footer. The
in-doc highlight CSS + useAnnotationHighlighter were not touched.

Verified: packages/ui + apps/frontend typecheck clean; build:hook verified;
two adversarial reviewers (behavior-retention + visual fidelity) — the one
real regression they found (EditorAnnotationCard bleeding into code-review)
was fixed via the variant prop.
Replace the mouse/touch + setState-per-move handlers with Pointer Events on
window (fire reliably for every move, even past the window edge), rAF-coalesced
to one write per frame, and an optional imperative `apply` that drives width
through a :root CSS var so heavy hosts never re-render mid-drag. The handle bar
is hidden while dragging.

Also scope the width-animation transition to the global sidebar only. It was
applied to every element under [data-slot="sidebar-wrapper"] (the whole app), so
every panel animated its width over 150ms and trailed the cursor when dragged.
Now in-plan panels snap 1:1.
Restore the redline backspace glyph as a RedlineIcon component (from the icons
package, not inlined). Drive the comment accent through a new
--annotation-comment token that defaults to the theme accent and only overrides
to blue in the simple theme, so fixing simple-theme contrast no longer regresses
every other theme. Flat toolstrip/toolbar styling to match the prototype.
Flatten the nested TocItemComponent / CountBadge layer into the main component.
Consolidate the scattered Tater sprites into one packages/ui/components/sprites/
module (SpriteSheet primitive + per-pose components, exported from the package);
drop the old top-level sprite_package_* dirs and stray TaterSprite files; swap in
the new sidebar sprite.

Sidebar: add a 'New project' button + 'Projects' label above the tree, drop the
project tree down a touch (logo stays pinned), replace the worktree git-branch
icon with a 'W' glyph, and bump row sizing. AddProjectDialog now filters recent
projects as you type instead of showing the full list.

Plan App: drive the TOC and annotation panel widths through :root CSS vars so
the new render-free resize applies to them too.
Remove the disclosure chevron from project and worktree rows (it ate a column
of space). The project's folder icon now doubles as the open/closed indicator
(Folder vs FolderOpen). Drop the chevron-width spacer on session rows so the
tree nests by indent alone, and delete the now-dead CHEVRON constant + group/disc
markers.
Both in-plan sidebar headers now share one spec: h-10 band, text-xs font-medium
labels (the left tab bar was shorter with text-[10px]; the right title was
font-semibold).

Add a collapse button to the shared ResizeHandle (onCollapse), centered on the
handle, that reveals on hover of the *whole* adjacent sidebar — pure CSS via
:has() on the panes container + data-plan-sidebar markers, no JS state or layout
wrappers (which would risk the sidebar's sticky positioning). Drop the redundant
left-header close X now that both sides collapse via the handle.
Extract the chat-bubble comment glyph to CommentIcon in the icons package (it was
inlined in AnnotationToolbar) and use it for the header annotations toggle, which
the reskin had swapped to lucide's PanelRight* (a formal sidebar panel). De-dupe
the toolbar to the packaged icon.

Fade the app-shell sidebar trigger to text-muted-foreground/60 (brightens on
hover), matching the prototype.
The article card carried full padding (p-5..xl:p-12) in every mode, so the
plan text was inset while the badges and action buttons sat at the card's
outer edge. With a visible card (grid on) that reads as a padded card with a
header bar at its edges. With grid off there's no visible card, so the chrome
looked ~80px wider than the text.

Match the prototype: horizontal card padding only exists in grid mode.
- Article: split p-* into py-* (always) + px-* (grid only).
- Badges: flush to left-0 when grid off (was left-3), card-corner inset when on.
- Action buttons: cancel the panel's own p-1/md:p-2 with -mr-1/md:-mr-2 when
  grid off so they sit flush to the right text edge; keep the larger -mr pull
  for grid mode.

Grid-on layout is unchanged.
Only the in-plan Contents sidebar had snap-to-close. Wire onSnapClose into
the other resizable panels so all of them close the same way:
- Right panel (annotations + Ask AI) snaps shut via setIsPanelOpen(false).
- Global app sidebar snaps shut via setOpen(false). Its close fn lives inside
  SidebarProvider (useSidebar), so Layout bridges it through a ref that
  LayoutContent fills.
Theme switching stays in Settings. Drops the toggle button plus its now-dead
useTheme hook, toggleTheme callback, and Moon/Sun/useCallback imports.
Mirrors apps/portal for the plan editor: a tiny Vite app that mounts the
code-review App and renders its built-in DEMO_DIFF, so the code-review screen
can be developed daemon-free with hot reload. New command: bun run dev:review.
Zero changes to the code-review app itself.
Every project and worktree row gets an 'Annotate' row that opens (or reuses) a
folder-annotate session and navigates to it. When the session is live the row IS
that session: it highlights when active and isn't duplicated as a separate row.

- client: createAnnotateFolderSession(cwd) (action annotate + mode annotate-folder).
- sidebar: FolderAnnotateRow under projects + worktrees; empty worktrees now render.
- server: folder matches reuse any LIVE session (not just awaiting-resubmission), so
  repeat opens land in the same session; single-file/last annotate unchanged.
- expose matchKey on the session summary; sidebar folds the folder session into the
  Annotate row (excluded from session rows + count).
- replace the overloaded collapsedWorktrees set with worktreeOpen overrides +
  computed default (open when real session or contains the active session).
- hide the linked-doc breadcrumb in folder mode (sidebar browser is the way back);
  kept in plan/single-file/HTML where it's the only way back.

Docs in goals/folder-annotate/.
…-picker add dialog

Landing page reorganization (the C1 direction):
- Project rows are now act-on-the-row: hover Review/Annotate launch directly on the
  project (or worktree/PR via a right-chevron drill-down). Removed the multi-select +
  batch 'Launch' bar; one launch() helper creates-or-reuses and navigates. (A proper
  'select multiple' UX is a planned follow-up.)
- Two-line project rows: name + branch on top, ~-prettified mono path below, aligned.
- Active sessions: sorted (live first, then most-recent) via shared session-sort.ts;
  the box is just the list, with 'History →' on the section heading. ConjoinedSessions
  History replaced by a focused ActiveSessionsList.
- History page (FullSessionsHistoryView) defaults to the history tab and sorts by date;
  groups inherit recency order.
- Add Project dialog: removed 'Recent' (it only re-added existing projects) — now a
  clean directory picker.
- prettyPath() added to packages/shared/project.ts (home → ~, browser-safe).
- goals/landing-redesign/ design mocks (current + C1..C5 explorations).
Interactive CLI commands (review/annotate/annotate-last/setup-goal/copilot-*)
now print the session URL to stderr so a launched session is never a silent
black box. Adds browserAction to the create-session response for the message.

presentSession now always opens the session URL in a focused tab for local
sessions instead of trying to reuse a connected tab. Browser self-reported
visibility (document.hidden/hasFocus) can't distinguish 'the user is looking at
Plannotator' from 'a Plannotator window is open elsewhere' across macOS Spaces,
so reuse-and-activate surfaced the wrong window. Opening the URL focuses the
session in the window the user is actually in. Remote keeps stream-into-visible.

goals/session-presentation/findings.md documents the research and dead ends.
…o top

- Sidebar drag-to-snap-shut + hover-reveal collapse handles to match plan app.
- Unified file-tree/annotations headers; FolderTree icon for the left toggle;
  options menu moved to the far right of the header.
- AgentsTab reskin (segmented pickers, toggle, status squares) with the launch
  panel moved to the TOP of the sidebar; dropdowns open downward.
- Add Opus 4.8 / Opus 4.8 (1M) to the agent-job Claude model picker.
- Declare lucide-react in the code-review package (was relying on hoisting).
… session presentation, Opus 4.8

Brings the sibling code-review branch onto feat/ui2-plan:
- Code-review sidebar reskin (parity resize, headers, agents tab, agent launchpad to top).
- Git dashboard: project filter + open/merged queried separately + hide zero +/- stat.
- Session presentation: print the session URL and always open a tab locally.
- Opus 4.8 / 4.8 (1M) in the Claude Ask AI provider + picker; lucide-react dep fix.

Both sides only co-touched daemon-protocol.ts (distinct interfaces) — auto-merged.
The daemon is a local-only dev tool, not an enterprise service. The
per-start auth token (query-param bootstrap → HttpOnly cookie → Bearer
header) added friction on every tab open and across restarts without
buying enough for the threat model. Remove it entirely:

- state.ts: drop authToken field, createDaemonAuthToken(), and the
  query-param/cookie constants. createDaemonBrowserAuthUrl() now returns
  a plain URL.
- server.ts: drop the cookie-bootstrap 302 redirect and the /daemon/*
  auth gate. Keep the same-origin WebSocket check (DNS-rebinding guard).
- event-hub.ts: drop daemonAuthenticated connection state and its gates.
- client.ts: drop Bearer header and withDaemonAuth helpers.
- vite.config.ts, launch-session.sh, test-plan-session.sh: drop token
  plumbing.
- tests: rewrite the auth tests to assert no-auth access and origin-only
  WS gating.

Verified: 153 daemon tests pass, frontend+server typecheck clean,
daemon serves /daemon/status, /daemon/sessions, and / with no auth.
Self-review of the daemon auth removal surfaced three leftovers:

- release.yml: the macOS/Linux/Windows release smoke gated its entire
  health check on reading authToken from the state file (`if [ -n "$token" ]`
  / `if ($token)`). With the token gone that branch never ran, so $ok
  stayed false and the smoke would FAIL with 'did not expose a
  daemon-scoped endpoint'. Drop the token plumbing and curl the now-open
  /daemon/sessions directly. This would have broken the release pipeline.
- Rename createDaemonBrowserAuthUrl -> createDaemonBrowserUrl (state.ts,
  runtime.ts, apps/hook index.ts ×5). The name no longer describes
  anything — there is no auth in the URL.
- manual-test.md: drop the stale jq .authToken / Bearer header lines.

Verified: server typecheck clean, 153 daemon tests pass.
Final sweep after the auth-token removal — eliminate the last dead
remnants of the mechanism:

- server.test.ts: drop AUTH_TOKEN const, the authHeaders() helper, the
  daemonAuthenticated FakeSocket data, the authToken state props, and
  rewrite every authHeaders(...) call site to plain headers.
- runtime.test.ts: drop the daemonAuthHeaders helper (it read the
  now-removed state.authToken) and its call sites.
- client.test.ts: drop the now-unused AUTH_TOKEN const.
- frontend client.ts: drop the dead 'action-level unauthorized' WS
  fallback (only the token gate ever produced it) and the now-unused
  DaemonHubActionError import.

Kept (not auth-token remnants): the same-origin WebSocket guard and its
'unauthorized' error code (server.ts/daemon-protocol.ts), and the
general DaemonHubActionError class.

Verified: 153 daemon tests pass, frontend + server typecheck clean.
Extensive lexicon sweep turned up two stale spots missed earlier:

- manual-test.md: still had 3 'Authorization: Bearer ${AUTH}' header
  lines (the earlier pass only fixed the first block). With AUTH no
  longer defined, those curls were broken — removed all three.
- ui2point0/legacy-design-state.md: corrected two 'Bearer auth'
  descriptions of the daemon client/proxy to 'no auth — open on
  localhost'.

Left intact: provenance.md and historical plan.md (immutable records of
when auth existed), PR-platform auth (checkPRAuth / gh / glab), and the
same-origin WebSocket guard.
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