Skip to content

feat(phase-4): topic clustering with haiku tagging and hdbscan-lite re-cluster#698

Closed
4Gaige wants to merge 12 commits into
siteboon:mainfrom
4Gaige:feat/topics
Closed

feat(phase-4): topic clustering with haiku tagging and hdbscan-lite re-cluster#698
4Gaige wants to merge 12 commits into
siteboon:mainfrom
4Gaige:feat/topics

Conversation

@4Gaige
Copy link
Copy Markdown

@4Gaige 4Gaige commented Apr 23, 2026

Summary

  • Server-side automatic topic assignment layered under the existing Phase 2 sidebar topic UI.
  • Per-session Haiku tagging fires within seconds of title generation (via a new titlerEvents EventEmitter).
  • Nightly 3am cron runs HDBSCAN-lite (Voyage voyage-3-lite embeddings + single-linkage clustering + Haiku cluster naming) on projects with ≥20 conversations; small projects get a Haiku catch-up pass.
  • Manual drag-and-drop assignments persist with method='manual' and survive automatic re-clustering.

Changes

Server — all new files except one additive edit and three import lines

  • server/database/migrations/001_conversation_topics.sql — new table per brief.
  • server/database/topic-store.js — better-sqlite3 wrapper; runs the migration on import (mirrors the inline-DDL pattern from db.js).
  • server/services/topic-prompt.js — Haiku tagging + cluster-naming prompts (adapted from open-webui), plus title-case normalizer.
  • server/services/embeddings-client.js — Voyage HTTP wrapper, no SDK dependency, gracefully no-ops without VOYAGE_API_KEY.
  • server/services/topic-clusterer.js — both strategies + manual-override entry point; Haiku call mirrors Phase 3's Direct SDK → Agent SDK fallback.
  • server/services/topic-clusterer-cron.js — self-arming setTimeout-at-3am (no node-cron dep); per-session debouncing; subscribes to titlerEvents.
  • server/services/session-titler.jsadditive only: adds a titlerEvents EventEmitter export and one emitTitled(...) call after successful title writes.
  • server/routes/topics.jsGET /api/topics, GET /api/topics/project/:slug, POST /assign, POST /cluster, POST /cluster/project/:slug, POST /tag/session.
  • server/index.js — 3 new lines (1 route import, 1 cron side-effect import, 1 app.use mount).

Client

  • src/components/sidebar/topics/useServerTopics.ts (replaces useTopicStorage.ts) — same API shape, backed by /api/topics; refreshes on projects_updated WebSocket events; optimistic updates for drag-drop assignment; preserves local ghost topics until they get a session.
  • src/components/sidebar/topics/TopicChip.tsx — adds a session-count badge (server-provided).
  • src/components/sidebar/topics/SidebarTopicGroup.tsx — keys active-topic state by project slug (matches server project_key); filters visible topics per project.
  • src/components/sidebar/topics/SidebarProjectTree.tsx — switched to useServerTopics; drag-end now passes projectKey through.

Acceptance criteria (from phase-4-brief.md)

  1. ✅ Every session gets exactly one topic (HDBSCAN noise points fall back to per-session Haiku; Haiku failures fall back to 'Misc').
  2. ✅ Pastels are consistent for the same topic in a project (findAccentForTopic before pickAccentForProject).
  3. ✅ Manual drag-drop assignments persist (method='manual' preserved by replaceForProject).
  4. ✅ Small projects (<20 convos) use Haiku tagging with existing-topics context.
  5. ✅ Large projects (≥20 convos) run HDBSCAN-lite nightly when VOYAGE_API_KEY is set.
  6. ✅ No regression to the Phase 2 sidebar tree.

Constraints

  • ✅ Never edit server/projects.js or src/components/sidebar/subcomponents/SidebarProjectItem.tsx. server/index.js gains exactly 3 lines (1 route import, 1 cron side-effect import, 1 app.use).
  • ✅ No raw Tailwind color classes. Only Midnight-mapped semantic vars and .ds-chip* component classes.
  • ✅ Mobile-first: chips are min-h-[44px], chip row is overflow-x-auto, root has pb-safe-area-inset-bottom.
  • ✅ Commit subjects are lowercase conventional-commits.

Verification

  • npm run build
  • npm run typecheck
  • npm run lint ✅ (auto-fixed import-order warnings)
  • npm test → n/a (no test script in package.json)
  • Bundle size: main index-*.js 2,530,745 B → feat/topics 2,535,757 B = +0.20% (well under 5% budget)

Test plan

  • On first boot post-merge, the conversation_topics table gets created.
  • A freshly-created Claude conversation shows a pastel topic chip in the sidebar within ~seconds of the title landing (titler-driven Haiku tag).
  • Dragging a conversation onto a different topic chip persists across reload and survives nightly clustering.
  • Projects with ≥20 convos run HDBSCAN at 3am local (timer is re-armed after each fire to handle DST).
  • With no VOYAGE_API_KEY, the nightly falls back to per-session Haiku tagging and logs skipped/no-voyage-key for large-project attempts.
  • GET /api/topics returns { byProject: { <slug>: { topics: [...], assignments: {...} } } }.

Fresh-eyes review

A clean-context Opus reviewer ran through CLAUDE.md's full checklist + the phase-4 brief's acceptance criteria and flagged no required fixes. Optional follow-ups: add a dedicated topics_updated WS broadcast from the cron (today nightly updates surface on the next projects_updated), and a rate-limit on POST /api/topics/cluster.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Midnight design system with semantic color tokens and improved visual hierarchy.
    • Mobile-first responsive layout with bottom tab navigation.
    • Conversation organization by topic with auto-clustering.
    • Preview pane with device presets and Chrome remote debugging viewer.
    • Task management with kanban columns synchronized from session activity.
    • Parallel worktree sessions for concurrent development.
    • MCP server discovery and integration.
    • Auto-generated session titles.
  • Documentation

    • Build plan and multi-phase development roadmap.
    • Visual review guides and screenshot capture workflows.

4Gaige and others added 12 commits April 22, 2026 23:19
agent playbook, master plan, six phase briefs, midnight design system
copy, five orchestration shell scripts, three github actions (ci,
upstream sync, auto-merge). no source changes.
watchdog.sh monitors each phase worktree's claude pid. intervenes only
when cpu near zero, no child processes, and no recent file writes for
15 minutes. sigterm first, sigkill only if ignored after 30 seconds.

run-phase.sh now retries up to 3 times. retry uses a recovery prompt
that shows git status, recent commits, open prs so claude resumes from
where it stopped instead of starting over. falls back to sonnet if
opus is overloaded.
* feat(skin): install Midnight design system tokens and shadcn mapping

Adds the Midnight v1.1 token set as src/styles/midnight.css and wires
shadcn-style semantic vars (--background, --foreground, --primary, etc.)
to resolve against Midnight tokens so legacy bg-background / text-muted-
foreground utilities keep working. Tailwind config extends with Midnight
palette, pastels, radii, shadows, and motion curves. HTML root now
declares class=dark and preconnects to Inter + Geist Mono.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(skin): add mobile tab bar and persistent desktop left rail

Introduces a five-slot primary navigation — Chat, Sessions, Preview,
Browser, More — that renders as a bottom .ds-tabbar below 1024px and a
64px left rail at lg+. Sessions slot opens the sidebar as a Midnight
.ds-sheet on mobile (swipe-down-to-dismiss) and selects the persistent
second-column sidebar on desktop. Per-section data-accent wires
focus rings and selection highlights to the section's pastel.

Touch targets are 44x44px minimum on mobile; safe-area-inset-bottom
is respected via .ios-bottom-safe on the tab bar container.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(skin): replace raw Tailwind color classes with Midnight semantics

Rewrites the hardcoded color sites called out in the phase-1 brief so
none rely on literal Tailwind palette classes. MCP provider buttons
now use primary/card; kanban columns compose .ds-tile-inset with per-
status pastel accents; TaskCard becomes a .ds-tile-hover with status-
accented dots; the image viewer sits on a .ds-sheet-backdrop inside a
.ds-tile; login submit becomes .btn-primary; Queue indicators use the
pastel mint/sky tokens.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(skin): a11y polish — drop tablist role, make sheet keyboard-aware

DesktopRail drops the role="tablist" / role="tab" pair since the
component does not implement the roving-tabindex / arrow-key semantics
those roles promise. Replaces with aria-current="page" on the active
item, which is accurate for primary nav.

MobileSidebarSheet now subtracts --keyboard-height from its 80vh/svh
target so the iOS keyboard does not clip the session list when a text
field inside focuses.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
orchestrator: pre-create worktrees serially to avoid git config lock
race. check if phase pr is already merged and skip work if so, making
reruns safe.

run-phase: add 90 min wall clock deadline per phase. after attempt 1
with zero new commits, fail fast because retry is unlikely to help.

watchdog: anchor on run-phase.sh wrapper pid instead of regex matching
the claude argv which has embedded newlines that break pgrep.
…s-touched chips (#2)

* feat(mcp): add bootstrap service for recommended MCP servers

Registers codebase-memory-mcp and claude-code-mcp in ~/.claude.json on boot
unless the user has dismissed them. Companion state file tracks dismissals so
re-installs don't fight the toggle. Exposes HTTP endpoints for list/toggle,
sub-agent spawn (SSE), and session files-touched derivation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(mcp): add sub-agent composer button, recommended-mcps settings, files-touched chips

- SpawnSubAgentButton in chat composer opens a mobile-first modal with agent type chips
  and a prompt textarea; submit streams SSE output from /api/mcp-bootstrap/spawn-sub-agent.
- RecommendedMCPsTab in Settings lists the two Dispatch-recommended MCPs with toggles
  wired to /api/mcp-bootstrap/recommended; toggling writes ~/.claude.json and persists
  dismissals so the bootstrap does not fight the user.
- SessionFilesTouchedChips under each sidebar session lazily fetches derived "files
  touched" from session JSONL tool uses; uses IntersectionObserver to defer work until
  the row scrolls into view.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* ci: auto-create upstream-sync branch if missing

The aormsby sync action requires the target branch to already exist. Dispatch
the workflow on a fresh fork and it fails with "pathspec 'upstream-sync' did
not match any file(s)". Create the branch from main up front so the first
sync run (and any future re-creation) just works.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(mcp): harden path traversal, touch target, and config-parse safety

- Reject '..', slashes, and null bytes in session-files-touched route params;
  defense-in-depth prefix-check on the resolved path so no response can read
  outside ~/.claude/projects.
- Bump recommended-MCPs toggle to 44x44 with a 52px-wide track so the mobile
  touch target meets the phase's minimum.
- If ~/.claude.json is unreadable/unparseable, ensureRecommendedMCPs and the
  toggle endpoint now abort instead of silently rewriting an empty config
  over the user's state. The toggle surfaces a 409 with the path so the user
  can fix their file manually.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(mcp): keep /recommended endpoint responsive when ~/.claude.json is corrupt

describeRecommendedMCPs now treats an unparseable config the same as a missing
one so the Settings page still renders (with items reported as not installed)
instead of blowing up with a 500. Matches the guard already in place on the
bootstrap and toggle paths.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* feat(sidebar): repo grouping service

Wrap getProjects output with a new repo-grouper service so the sidebar can
render a Repo → Topic → Conversation tree. The service walks up each
project's fullPath looking for .git, reads remote.origin.url, normalizes
the URL, and detects worktrees (.claude/worktrees path or .git pointer
file). Results cached in ~/.cloudcli/project-config.json.

Project type gains optional repoGroup, repoDisplayName, isWorktree,
gitBranch, gitRoot, gitOrigin. server/projects.js gets one new import
line and one new line at the tail of getProjects — no internal edits.
Registers server/services/*.js as a new eslint boundaries element so
the plugin classifies the new module.

* feat(sidebar): topic tree render + drag-to-reassign

Render projects as a Repo -> Topic -> Conversation tree via a new
SidebarProjectTree orchestrator. Projects with the same repoGroup (from
the server-side grouper) collapse into a single collapsible repo header
so worktrees no longer appear as top-level entries.

Each project is wrapped by SidebarTopicGroup, an additive wrapper around
SidebarProjectItem. When expanded, it renders a horizontal Topic chip
row (the All chip plus any user-created topics) and filters the sessions
prop by the active topic before forwarding to SidebarProjectItem. The
wrapped item itself is untouched.

Drag-and-drop via @dnd-kit/core: SidebarSessionItem becomes a draggable
(one-line root wrapper, activation constraints keep tap/click intact),
topic chips are droppable. Dropping a session on a chip persists the
assignment in localStorage under dispatch.sidebar.topics.v1; dropping on
All unassigns. Pointer+touch sensors mean long-press-drag works on
mobile too.

New files under src/components/sidebar/topics/: TopicChip,
SidebarTopicGroup, SidebarProjectTree, useTopicStorage. SidebarContent
swaps SidebarProjectList for SidebarProjectTree. Adds @dnd-kit/core.

* feat(sidebar): search scope relabel + disable-when-empty

The Projects/Conversations tabs in the sidebar header were a search-scope
toggle mislabeled as a primary navigation control. Reorder the search
input above the scope row so the primary input becomes visually dominant,
and disable the scope buttons (cursor-not-allowed + opacity-40) until
the user types. Placeholder shown on an empty input is now a single
"Type to search" string, switching to the scope label once text is
entered.

i18n: relabel search.modeProjects -> "Search projects" and
search.modeConversations -> "Search conversations" across all 8 locales
(en, de, it, ja, ko, ru, tr, zh-CN). Add two new keys in search
(typeToSearch, scopeLabel) and a new topics section (all, createTopic,
renamePrompt, deleteConfirm, namePlaceholder, chipRowLabel) used by
SidebarTopicGroup.

* fix(sidebar): restore document.title update in tree

SidebarProjectList had a useEffect that kept document.title in sync
with the selected project's displayName (e.g. tab title read
"my-app - CloudCLI UI"). When SidebarProjectTree replaced it as the
rendered tree the effect was lost. Port the same 8-line effect.

* fix(sidebar): scope toggle active state + repo header polish

Address visual-review findings:
- Replace the ad-hoc flex/rounded-lg scope toggle with the Midnight
  .ds-segment + .ds-segment-item + .ds-segment-item-active catalog so
  the active scope reads white-on-black against the dark canvas
  instead of blending into bg-background (both desktop + mobile).
- Ensure scope tabs hit the 44px touch-target minimum on both
  viewports by adding min-h-[44px] on each segment item.
- Swap the ad-hoc rounded-full count badge next to repo group names
  for the Midnight .badge token.
- Tint the origin-present FolderGit2 icon with the sidebar accent
  (var(--midnight-lavender)) to reinforce per-section accent
  discipline on the tree.

Also adds Phase 2 visual artefacts under docs/screenshots/phase-2/:
Playwright captures at 1440x900 + 375x812, a README explaining the
acceptance-criteria evidence, and the regeneration helper
_capture.mjs (auth-token seeding, onboarding skip, loading wait).
…s kanban (#4)

* feat(phase-5): add backend routes for preview proxy, chrome screencast, worktrees, tasks

Scaffolds four new Express/WS routes under server/routes/ and wires each one
into server/index.js with a single import + mount line per feature.

- preview-proxy: reverse-proxy /preview/:port/* to 127.0.0.1:{port} with
  WS upgrade passthrough for dev-server HMR. Strips CSP/X-Frame-Options
  and rewrites Set-Cookie domain to our host.
- chrome-screencast: CDP Page.startScreencast over ws://host/ws/chrome-view.
  Forwards JPEG frames to client, acks back to Chrome, and translates
  client input messages to Input.dispatch* calls. Status + tab endpoints
  under /api/chrome-view for UI bootstrapping.
- worktrees: list/create/delete under /api/worktrees plus a /spawn helper
  that launches 'claude' in tmux when available and falls back to a
  detached spawn. Worktrees live in <repo>/.claude/worktrees/<slug>.
- tasks: reads the active claude session's JSONL and returns the latest
  TodoWrite tool_use bucketed into todo/in_progress/completed columns.

server/index.js additions are additive only (4 imports, 4 app.use lines,
2 attach* calls for the WS upgrade handlers). No behavior of existing
routes is changed.

* feat(phase-5): add preview, browser, tasks, and worktree UI for phase 5

Frontend half of phase 5. Ships the four features via additive new
component directories and a minimal set of wiring edits to non-restricted
shared files.

- preview: PreviewPane (iframe + URL bar + device presets) and
  PreviewModal (mobile fullscreen). Renders /preview/{port}{path}
  against the new server proxy. data-accent="mint" per nav discipline.
- browser: BrowserPane (canvas renderer for CDP screencast frames,
  take-control toggle that forwards mouse/keyboard/scroll events via
  ws://host/ws/chrome-view) and BrowserModal. data-accent="peach".
- tasks: TasksPane (3-column kanban on desktop, swipeable segmented
  on mobile with ds-segment), TaskCard (ds-tile), TasksModal (bottom
  sheet). Polls /api/tasks every 4s and refreshes on TodoWrite events
  seen on the chat WS. data-accent="butter".
- sidebar/worktrees: WorktreeList rendered beneath each project's
  session list. Lets users list, create, spawn and delete worktrees
  under <repo>/.claude/worktrees/. Activity dots green/yellow/red/grey
  drive from the session protection state (wired-ready).

Wiring edits (all to non-restricted files):
- src/types/app.ts: AppTab union gains 'browser' variant
- src/components/layout/useAppNavItems.ts: resolveActiveSlot routes
  browser → browser slot
- src/components/app/AppContent.tsx: nav select handler opens the
  browser tab; main-column data-accent maps preview/browser/tasks
- src/components/main-content/view/MainContent.tsx: preview/browser
  slots now render their panes; floating Tasks button + right drawer
  (desktop) or bottom-sheet modal (mobile) surface TasksPane
- src/components/sidebar/view/subcomponents/SidebarProjectSessions.tsx:
  renders WorktreeList below the session list per project
- .gitignore: carve-out so src/components/tasks/ escapes the
  tasks/ ignore that exists for TaskMaster storage

* fix(phase-5): enforce ws auth on new upgrade paths and clean up dead code

Review fixes from the fresh-eyes audit:

- server/routes/preview-proxy.js: switch to prependListener on the
  http upgrade event so /preview/{port}/* wins the race against the
  root wss in server/index.js (which otherwise accepts and
  immediately closes unknown paths). Verify the JWT off the token
  query param / bearer header before forwarding upstream. Preserve
  upgrade and connection headers through to the dev-server.
- server/routes/chrome-screencast.js: same prependListener + JWT
  verification at the /ws/chrome-view upgrade.
- src/components/preview/PreviewPane.tsx: drop the dead iframeRef
  and the tautological DEVICE_PRESETS.find(... preset.id === preset.id)
  block that the reviewer flagged.
- src/components/sidebar/worktrees/WorktreeList.tsx: fix the invisible
  delete button on mobile — the touch:opacity-100 pseudo-class isn't
  configured, and the row hover class was bg-midnight-surface-hover
  (undefined). Switch to a visible-on-mobile, reveal-on-hover-on-desktop
  pattern with a 44×44 touch target, and use hover:bg-accent/40 for
  the row hover state.

* fix(phase-5): resolve ws double-upgrade crash and visual review polish

Visual review found a reproducible backend crash plus two minor UI
bugs. Screenshots and review notes are committed under
docs/screenshots/phase-5/.

- server/routes/chrome-screencast.js + server/routes/preview-proxy.js:
  accept the root WebSocketServer as a second argument and patch its
  shouldHandle() to reject paths we own. Without this the ws library's
  internal upgrade listener re-entered handleUpgrade on a socket we had
  already claimed, throwing "server.handleUpgrade() was called more
  than once with the same socket" on the second Browser-tab click.
- server/index.js: pass the root wss (one-token argument change per
  existing attach call, no new lines).
- src/components/browser/BrowserPane.tsx: add shrink-0 and
  whitespace-nowrap to the take-control button so its label does not
  clip on narrow desktop panels.
- src/components/sidebar/worktrees/WorktreeList.tsx: swap the neutral
  branch label for a ds-chip-lavender pill so it reads as a proper
  Midnight badge rather than muted text.
- docs/screenshots/phase-5/*.png + VISUAL-REVIEW.md: mobile (375x812)
  and desktop (1440x900) captures covering home, preview, browser,
  worktree list, tasks-drawer fallback, plus the midnight demo
  reference. Includes the written review notes.
brief instructs an opus session to review each merged phase against
claude md checklist and phase briefs, fix critical issues in a
dedicated review worktree, then poll for phase 3 + 4 to land and
repeat. non-interfering with the running orchestrator.
* feat(titler): add haiku-based session auto-naming service

Adds `server/services/session-titler.js` and `server/services/title-prompt.js`.
The titler watches `~/.claude/projects/**/*.jsonl`, waits for 60s of idle per
file, reads the first two non-system user messages, and asks Haiku for a 3-5
word title stored in the existing `session_names.custom_name` column.

On boot it backfills any JSONL that has no custom name. Writes trigger the
existing projects watcher via a blank-line append, so the sidebar updates live
without touching server/index.js beyond a future single import line.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(titler): wire auto-namer into server boot and sidebar shimmer

- server/index.js: single-line import of session-titler service so it
  self-starts at boot (matches the "no other edits" rule for churn files).
- server/database/db.js: applyCustomSessionNames now marks Claude sessions
  without a custom_name with pendingTitle=true so the UI can flag them.
- server/services/session-titler.js: prefer @anthropic-ai/sdk directly when
  an ANTHROPIC_API_KEY is available (env or settings.json); fall back to the
  Claude Agent SDK's query() for OAuth-only environments.
- SidebarSessionItem.tsx: render the session name with the `.shimmer`
  Midnight utility class while pendingTitle is true; aria-busy for SR.
- types/app.ts: add optional pendingTitle?: boolean to ProjectSession.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(titler): skip saving when no haiku path is available

When both the direct Anthropic SDK and the Claude Agent SDK fail to produce
a result (no key, offline, rate limited, ...), treat the title as unresolved
and leave `pendingTitle=true` so the next boot or network recovery picks the
session back up. Previously we wrote 'Untitled' which poisoned the cache.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* fix(review): harden mcp-bootstrap spawn and skip bootstrap when no claude config

Two issues from the post-merge fresh-eyes review of phase 6:

- The /spawn-sub-agent endpoint passed a user-supplied workingDir straight
  into child_process.spawn's cwd with no validation. An authenticated
  client could point claude at /etc, /var, or anywhere else the server
  uid can reach. Now we resolve the path, require it to live under $HOME
  (or DISPATCH_PROJECT_ROOTS), reject sensitive credential subdirs, and
  confirm it exists as a directory before spawning.

- ensureRecommendedMCPs ran as a top-level module side-effect on import
  and would happily materialize a fresh ~/.claude.json containing only
  Dispatch's two MCPs for users who had never run claude. The bootstrap
  now refuses to create the file from nothing and defers via setImmediate
  so the import chain in server/index.js is not blocked by file IO.

* fix(review): preview port allowlist, chrome viewer operator scoping, tasks path safety

Three security fixes from the post-merge fresh-eyes review of phase 5:

- Preview proxy used to forward any port from 1-65535 to 127.0.0.1, so any
  authenticated user could probe localhost services (mysql, postgres,
  redis, the cdp port itself, etc). Now defaults to a curated allowlist
  of common dev-server ports, blocks well-known infra ports outright,
  and exposes DISPATCH_PREVIEW_PORTS / DISPATCH_PREVIEW_ALLOW_ANY_HIGH_PORT
  for users who need something else.

- Chrome screencast attaches to the operator's real browser over CDP and
  was reachable by any logged-in user. The Input.* dispatch path let any
  client drive the host browser (steal cookies via Runtime.evaluate,
  navigate to attacker URLs, etc). Now disabled by default; opt in via
  DISPATCH_CHROME_VIEW_ENABLED=true for view-only and additionally
  DISPATCH_CHROME_VIEW_ALLOW_INPUT=true for take-control. The HTTP
  /status and /tabs helpers report the disabled state cleanly so the
  client UI can render an explanation instead of a stack trace.

- /api/tasks built the JSONL path from query params with no traversal
  guard. Now rejects path separators, null bytes, and '..' on
  projectName + sessionId, then prefix-checks the resolved path against
  ~/.claude/projects.

* feat(review): wire mobile preview/browser modals and worktree activity context

Two phase 5 frontend follow-ups from the post-merge review:

- PreviewModal and BrowserModal shipped in phase 5 but were never
  imported anywhere, so mobile users on the preview/browser tabs got the
  desktop pane shoved into the tab slot with no fullscreen treatment or
  close button. MainContent now mounts both modals, gated by
  isMobile + activeTab; the inline panes stay desktop-only. Closing a
  modal returns to the chat tab.

- WorktreeList accepted activeSessions/processingSessions/blockedSessions
  /worktreeSessionMap props but its parent (SidebarProjectItem) is on the
  no-edit churn list, so the props were never passed and every dot stayed
  grey. New SessionActivityContext lives at the AppContent root and
  delivers the same protection sets to WorktreeList without prop
  drilling. worktreeSessionMap is best-effort for now: only the currently
  selected worktree's session can be attributed; cross-worktree
  resolution needs a server endpoint, tracked in docs/follow-ups.md.

* docs(review): track post-merge nice-to-haves in follow-ups.md

Captures every NICE-TO-HAVE finding from the four phase reviews so the
items aren't lost. Entries are grouped by phase with file:line citations;
each is small enough to land in a polish PR or fold into the next phase.
polls origin/main every 2 minutes. on new commits: git pull, npm install
if package.json changed, npm run build, launchctl kickstart the
com.dispatch.forge service, health check, imessage the user with the
commit subject. uses flock on /tmp/dispatch-main-worktree.lock to
serialize with other main-worktree git ops.
…e-cluster

Adds server-side automatic topic assignment (visible as pastel chip rows in the
sidebar) on top of the Phase 2 client-only topic UI.

Strategies:
- Per-session Haiku tag, fired by the titler EventEmitter so a freshly-titled
  conversation gets its topic within seconds.
- Nightly 3am full re-cluster: small projects (<20 convos) get a Haiku catch-up
  pass, large projects get Voyage embeddings (voyage-3-lite) + a single-linkage
  HDBSCAN-lite pass, with cluster names chosen by Haiku from each cluster's
  top-5 closest titles.

Server:
- migrations/001_conversation_topics.sql + topic-store.js (better-sqlite3 wrapper).
- topic-prompt.js: tagging + cluster-naming prompts adapted from open-webui.
- embeddings-client.js: Voyage HTTP wrapper, no SDK dep, gracefully no-ops
  when VOYAGE_API_KEY is unset.
- topic-clusterer.js: both strategies + manual override entry point.
- topic-clusterer-cron.js: self-arming setTimeout (no node-cron dep) +
  per-session debounce; subscribes to titlerEvents.
- routes/topics.js: GET /api/topics, POST /assign, POST /cluster, etc.
- session-titler.js: additive EventEmitter export so subscribers can react.
- server/index.js: 3 lines (1 route import, 1 cron side-effect import, 1 mount).

Client:
- useServerTopics.ts replaces the local-only useTopicStorage; same shape, but
  fetched from /api/topics, refreshed on projects_updated WS events, with
  optimistic local updates for drag-drop assignment.
- TopicChip now renders a count badge (server-provided sessionCount).
- SidebarTopicGroup keys topics by project slug to align with server project_key.

Manual drag-drop assignments persist with method='manual' and are preserved
across automatic re-clustering. Manual flow continues to feel instant via
optimistic update + post-hoc /api/topics refresh.
@4Gaige
Copy link
Copy Markdown
Author

4Gaige commented Apr 23, 2026

Wrong target — opening against the fork 4Gaige/Dispatch instead.

@4Gaige 4Gaige closed this Apr 23, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 23, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c1419843-5bb9-4e63-a17b-b07d6cf75705

📥 Commits

Reviewing files that changed from the base of the PR and between f6200e3 and 1d7d6bb.

⛔ Files ignored due to path filters (23)
  • docs/screenshots/phase-2/desktop/01-initial.png is excluded by !**/*.png
  • docs/screenshots/phase-2/desktop/02-sidebar-loaded.png is excluded by !**/*.png
  • docs/screenshots/phase-2/desktop/03-project-expanded.png is excluded by !**/*.png
  • docs/screenshots/phase-2/desktop/03b-topic-create.png is excluded by !**/*.png
  • docs/screenshots/phase-2/desktop/04-search-empty-disabled.png is excluded by !**/*.png
  • docs/screenshots/phase-2/desktop/05-search-with-text.png is excluded by !**/*.png
  • docs/screenshots/phase-2/mobile/01-initial.png is excluded by !**/*.png
  • docs/screenshots/phase-2/mobile/02-mobile-default.png is excluded by !**/*.png
  • docs/screenshots/phase-2/mobile/02-sidebar-loaded.png is excluded by !**/*.png
  • docs/screenshots/phase-2/mobile/03-sidebar-sheet.png is excluded by !**/*.png
  • docs/screenshots/phase-2/mobile/04-sheet-repo-expanded.png is excluded by !**/*.png
  • docs/screenshots/phase-5/browser-tab-desktop.png is excluded by !**/*.png
  • docs/screenshots/phase-5/browser-tab-mobile.png is excluded by !**/*.png
  • docs/screenshots/phase-5/home-desktop.png is excluded by !**/*.png
  • docs/screenshots/phase-5/home-mobile.png is excluded by !**/*.png
  • docs/screenshots/phase-5/midnight-demo-reference.png is excluded by !**/*.png
  • docs/screenshots/phase-5/preview-tab-desktop.png is excluded by !**/*.png
  • docs/screenshots/phase-5/preview-tab-mobile.png is excluded by !**/*.png
  • docs/screenshots/phase-5/tasks-drawer-fallback-desktop.png is excluded by !**/*.png
  • docs/screenshots/phase-5/tasks-drawer-fallback-mobile.png is excluded by !**/*.png
  • docs/screenshots/phase-5/worktree-list-desktop.png is excluded by !**/*.png
  • docs/screenshots/phase-5/worktree-list-mobile.png is excluded by !**/*.png
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (100)
  • .github/workflows/auto-merge.yml
  • .github/workflows/ci.yml
  • .github/workflows/sync-upstream.yml
  • .gitignore
  • docs/CLAUDE.md
  • docs/build-plan.md
  • docs/follow-ups.md
  • docs/midnight/README.md
  • docs/midnight/demo.html
  • docs/midnight/midnight.css
  • docs/midnight/tailwind.midnight.config.ts
  • docs/phase-1-brief.md
  • docs/phase-2-brief.md
  • docs/phase-3-brief.md
  • docs/phase-4-brief.md
  • docs/phase-5-brief.md
  • docs/phase-6-brief.md
  • docs/review-brief.md
  • docs/screenshots/phase-2/README.md
  • docs/screenshots/phase-2/_capture.mjs
  • docs/screenshots/phase-5/VISUAL-REVIEW.md
  • eslint.config.js
  • index.html
  • package.json
  • scripts/auto-deploy.sh
  • scripts/lib.sh
  • scripts/morning-summary.sh
  • scripts/notify.sh
  • scripts/orchestrator.sh
  • scripts/review-build.sh
  • scripts/run-phase.sh
  • scripts/watchdog.sh
  • server/database/db.js
  • server/database/migrations/001_conversation_topics.sql
  • server/database/topic-store.js
  • server/index.js
  • server/projects.js
  • server/routes/chrome-screencast.js
  • server/routes/mcp-bootstrap.js
  • server/routes/preview-proxy.js
  • server/routes/tasks.js
  • server/routes/topics.js
  • server/routes/worktrees.js
  • server/services/embeddings-client.js
  • server/services/repo-grouper.js
  • server/services/session-titler.js
  • server/services/title-prompt.js
  • server/services/topic-clusterer-cron.js
  • server/services/topic-clusterer.js
  • server/services/topic-prompt.js
  • src/components/app/AppContent.tsx
  • src/components/auth/view/LoginForm.tsx
  • src/components/browser/BrowserModal.tsx
  • src/components/browser/BrowserPane.tsx
  • src/components/chat/view/subcomponents/ChatComposer.tsx
  • src/components/chat/view/subcomponents/SpawnSubAgentButton.tsx
  • src/components/file-tree/view/ImageViewer.tsx
  • src/components/layout/DesktopRail.tsx
  • src/components/layout/MobileSidebarSheet.tsx
  • src/components/layout/MobileTabBar.tsx
  • src/components/layout/useAppNavItems.ts
  • src/components/main-content/view/MainContent.tsx
  • src/components/mcp/constants.ts
  • src/components/preview/PreviewModal.tsx
  • src/components/preview/PreviewPane.tsx
  • src/components/settings/types/types.ts
  • src/components/settings/view/Settings.tsx
  • src/components/settings/view/SettingsMainTabs.tsx
  • src/components/settings/view/SettingsSidebar.tsx
  • src/components/settings/view/tabs/RecommendedMCPsTab.tsx
  • src/components/sidebar/topics/SidebarProjectTree.tsx
  • src/components/sidebar/topics/SidebarTopicGroup.tsx
  • src/components/sidebar/topics/TopicChip.tsx
  • src/components/sidebar/topics/useServerTopics.ts
  • src/components/sidebar/view/subcomponents/SessionFilesTouchedChips.tsx
  • src/components/sidebar/view/subcomponents/SidebarContent.tsx
  • src/components/sidebar/view/subcomponents/SidebarHeader.tsx
  • src/components/sidebar/view/subcomponents/SidebarProjectSessions.tsx
  • src/components/sidebar/view/subcomponents/SidebarSessionItem.tsx
  • src/components/sidebar/worktrees/WorktreeList.tsx
  • src/components/task-master/utils/taskKanban.ts
  • src/components/task-master/view/TaskCard.tsx
  • src/components/tasks/TaskCard.tsx
  • src/components/tasks/TasksModal.tsx
  • src/components/tasks/TasksPane.tsx
  • src/contexts/SessionActivityContext.tsx
  • src/i18n/locales/de/sidebar.json
  • src/i18n/locales/en/settings.json
  • src/i18n/locales/en/sidebar.json
  • src/i18n/locales/it/sidebar.json
  • src/i18n/locales/ja/sidebar.json
  • src/i18n/locales/ko/sidebar.json
  • src/i18n/locales/ru/sidebar.json
  • src/i18n/locales/tr/sidebar.json
  • src/i18n/locales/zh-CN/sidebar.json
  • src/index.css
  • src/shared/view/ui/Queue.tsx
  • src/styles/midnight.css
  • src/types/app.ts
  • tailwind.config.js

📝 Walkthrough

Walkthrough

This PR introduces a comprehensive fork-and-skin overhaul: GitHub Actions workflows for CI/auto-merge/upstream-sync; a complete MIDNIGHT design system with CSS/Tailwind config; server services for session auto-titling and conversation topic clustering; new UI components for task management, topic filtering, preview proxy, and chrome screencast; and extensive multi-phase documentation for implementation and review.

Changes

Cohort / File(s) Summary
GitHub Actions Workflows
.github/workflows/auto-merge.yml, ci.yml, sync-upstream.yml
New automated workflows: auto-merge green PRs for 4Gaige author, CI pipeline with Node.js/npm/build/test/lint/TypeScript checks, and scheduled upstream fork sync from siteboon/claudecodeui with draft PR creation.
MIDNIGHT Design System — Docs
docs/midnight/README.md, midnight/demo.html, midnight/tailwind.midnight.config.ts
Complete design system documentation: README specifying token behavior and breaking changes, static HTML demo showcasing all components, and Tailwind config exporting the midnight theme palette with accent/animation/spacing tokens.
MIDNIGHT Design System — CSS
docs/midnight/midnight.css, src/styles/midnight.css, src/index.css
Core MIDNIGHT stylesheet and integration: standalone component classes (buttons, tiles, inputs, chips, badges, sheets, animations), Tailwind layer definitions, token mapping to semantic UI primitives, and updates to global index.css to import and apply midnight theming.
Configuration & Tooling
tailwind.config.js, eslint.config.js, .gitignore, index.html, package.json
Extended Tailwind with midnight palettes/keyframes, added backend-service ESLint boundary, excluded CLAUDE.md from ignore, updated HTML to dark mode/dispatch title/font preloading, and added @dnd-kit/core dependency.
Documentation — Project Planning
docs/CLAUDE.md, docs/build-plan.md, docs/follow-ups.md, docs/phase-{1..6}-brief.md, docs/review-brief.md
Comprehensive development guides: contribution rules and architecture overview (CLAUDE.md), multi-wave build plan with phase scope/deliverables, post-merge review follow-ups, and detailed briefs for each phase (Phase 1: Midnight skin/mobile layout; Phase 2: sidebar tree/search; Phase 3: session titling; Phase 4: topic clustering; Phase 5: preview/chrome/worktrees/tasks; Phase 6: MCP/auto-update).
Documentation — Screenshot/Visual Review
docs/screenshots/phase-{2,5}/, docs/screenshots/phase-2/_capture.mjs, docs/screenshots/phase-5/VISUAL-REVIEW.md
Playwright-based screenshot capture tooling for visual QA: Phase 2 capture script with auth injection and deterministic app interaction, and Phase 5 visual review documenting desktop/mobile viewport coverage and styling validation.
Server Routes — MCP & Preview
server/routes/mcp-bootstrap.js, server/routes/preview-proxy.js
Two new HTTP/REST APIs: MCP bootstrap registers recommended Claude Model Context Protocol servers in ~/.claude.json with install/dismiss/spawn-sub-agent endpoints, and preview proxy provides HTTP/WebSocket reverse-proxy to arbitrary dev server ports (port validation, CSP header stripping, auth gating).
Server Routes — Chrome & Worktrees
server/routes/chrome-screencast.js, server/routes/worktrees.js
Live inspection APIs: chrome-screencast via WebSocket/CDP for browser tab JPEG streaming and input forwarding (/ws/chrome-view), and worktree CRUD with git integration and tmux spawning of claude sub-agents per worktree.
Server Routes — Tasks & Topics
server/routes/tasks.js, server/routes/topics.js
Conversation-linked APIs: tasks reads latest TodoWrite tool output from session JSONL with multi-column Kanban model, and topics provides project-scoped topic listing, assignment, and clustering endpoints with haiku/hdbscan method tracking.
Server Services — Session Titling
server/services/session-titler.js, server/services/title-prompt.js
Auto-naming for Claude sessions: chokidar watcher monitors ~/.claude/projects JSONL files, debounce-enqueues, calls Haiku API to generate 3–5 word titles from first two user messages (with fallback to "Untitled"), stores in DB, and emits titled events for live UI sync.
Server Services — Topic Clustering
server/services/topic-clusterer.js, server/services/topic-clusterer-cron.js, server/services/topic-prompt.js
Automatic conversation clustering: backfill small projects with haiku-per-session tagging, cluster large projects (≥20 sessions) using Voyage embeddings + cosine-threshold single-linkage clustering, name clusters with haiku, and run nightly at 3am with manual override preservation; includes prompt templates and normalization rules.
Server Services — Utilities
server/services/embeddings-client.js, server/services/repo-grouper.js, server/database/topic-store.js, server/database/migrations/001_conversation_topics.sql
Supporting infrastructure: Voyage AI embeddings client with batch truncation, repository grouping service that walks git ancestry and caches origin-based metadata for sidebar tree structure, SQLite topic-assignment store with per-session/per-project lookups and accent round-robin, and schema migration for conversation_topics table.
Server Integration
server/index.js, server/projects.js, server/database/db.js
Server wiring updates: mount new MCP/preview/chrome/worktree/task/topic routes and WebSocket handlers; add async repo grouping transform to getProjects() response; extend applyCustomSessionNames() to track pendingTitle flag for Haiku-in-progress UI state.
Orchestration Scripts
scripts/lib.sh, scripts/auto-deploy.sh, scripts/orchestrator.sh, scripts/run-phase.sh, scripts/watchdog.sh, scripts/review-build.sh, scripts/morning-summary.sh, scripts/notify.sh
Multi-phase orchestration and monitoring: centralized logging/notification library, auto-deploy watcher with launchd service management and health checks, phase-parallel executor with PR merge guards and failure tracking, per-phase claude runner with retry/deadline logic, watchdog that monitors claude process CPU/file activity and kills stuck processes, post-merge review coordinator with autonomous PR merge flow, morning summary aggregator, and iMessage notification wrapper.
UI Components — Layout
src/components/layout/DesktopRail.tsx, src/components/layout/MobileTabBar.tsx, src/components/layout/MobileSidebarSheet.tsx, src/components/layout/useAppNavItems.ts, src/components/app/AppContent.tsx
Mobile-first layout overhaul: left-side desktop icon rail, bottom mobile tab bar with safe-area padding, swipe-dismissible mobile sidebar sheet, navigation slot resolution with accent support, and AppContent refactor to wire SessionActivityProvider and new layout primitives.
UI Components — Topics & Session Activity
src/components/sidebar/topics/SidebarProjectTree.tsx, src/components/sidebar/topics/SidebarTopicGroup.tsx, src/components/sidebar/topics/TopicChip.tsx, src/components/sidebar/topics/useServerTopics.ts, src/contexts/SessionActivityContext.tsx, src/components/sidebar/view/subcomponents/SessionFilesTouchedChips.tsx
Conversation organization layer: dnd-kit-powered tree component that groups projects by repo and filters sessions by topic with drag-drop assignment, topic chip row with create/rename/delete/filter, session file-touch chip display with lazy API loading and caching, server-synced topic storage hook with local ghost-topic support and localStorage persistence, and session activity context for worktree/processing/blocked state.
UI Components — Tasks & Preview
src/components/tasks/TasksPane.tsx, src/components/tasks/TasksModal.tsx, src/components/tasks/TaskCard.tsx, src/components/task-master/view/TaskCard.tsx, src/components/task-master/utils/taskKanban.ts, src/components/preview/PreviewPane.tsx, src/components/preview/PreviewModal.tsx
Kanban/task rendering and preview proxy: multi-column task pane with polling/WebSocket refresh and swipe navigation on mobile, modal wrapper, individual task card styling with status/priority visual tokens, and updated kanban column config to use design-system classes; preview pane with port/path controls, device presets, iframe reload, and modal variant.
UI Components — Browser & Settings
src/components/browser/BrowserPane.tsx, src/components/browser/BrowserModal.tsx, src/components/chat/view/subcomponents/SpawnSubAgentButton.tsx, src/components/settings/view/tabs/RecommendedMCPsTab.tsx
Live browser inspection and MCP management: chrome screencast canvas renderer with control toggle, WebSocket frame pooling, and input forwarding, modal wrapper, sub-agent spawning modal with SSE stdout/stderr streaming and output capture, and recommended MCP list/toggle UI with install/dismiss state tracking.
UI Components — Navigation & Sidebar
src/components/sidebar/worktrees/WorktreeList.tsx, src/components/sidebar/view/subcomponents/SidebarContent.tsx, src/components/sidebar/view/subcomponents/SidebarHeader.tsx, src/components/sidebar/view/subcomponents/SidebarProjectSessions.tsx, src/components/sidebar/view/subcomponents/SidebarSessionItem.tsx, src/components/main-content/view/MainContent.tsx
Parallel development and sidebar refactoring: worktree CRUD UI with activity indicators and creation/deletion, topic-tree swap in sidebar projects view, search UI with disabled scope toggle until input non-empty and placeholder/mode-specific text, per-session file-touch chips, dnd-kit drag state on session items with shimmer pending-title UI, and MainContent tasks drawer/modal/toggle wiring for desktop/mobile.
Settings & Auth UI
src/components/settings/types/types.ts, src/components/settings/view/Settings.tsx, src/components/settings/view/SettingsMainTabs.tsx, src/components/settings/view/SettingsSidebar.tsx, src/components/auth/view/LoginForm.tsx
MCP settings integration: SettingsMainTab union expanded to include 'mcp', Settings tab routing to RecommendedMCPsTab, new MCP tab entry in main/sidebar nav with Server icon, and LoginForm button re-skinned to btn-primary design-system class.
Internationalization
src/i18n/locales/{en,de,it,ja,ko,ru,tr,zh-CN}/sidebar.json, src/i18n/locales/en/settings.json
Multi-language UI text: added topics namespace (all, createTopic, renamePrompt, deleteConfirm, namePlaceholder, chipRowLabel) and search enhancements (typeToSearch, scopeLabel, explicit project/conversation labels) across 8 locales; added mainTabs.mcp entry to settings translations.
Types & Styling
src/types/app.ts, src/shared/view/ui/Queue.tsx, src/components/mcp/constants.ts
Type and style updates: AppTab union adds 'browser', ProjectSession adds pendingTitle?, Project adds repoGroup/repoDisplayName/isWorktree/gitBranch/gitRoot/gitOrigin fields; Queue item colors switch to text-mint/bg-sky tokens; MCP provider button classes updated to design-system primaries/card variants.

Sequence Diagram(s)

Not generated — the changes encompass diverse independent features (topic clustering, session titling, task polling, worktree management, preview proxy, chrome screencast) without a unified sequential control flow suitable for visualization. Each feature operates as a self-contained microservice or UI subsystem.

Possibly related PRs

Suggested reviewers

  • blackmammoth
  • viper151

🐰 Through fields of code a rabbit hops,
With topics, tasks, and preview props,
A midnight skin, a chrome-view glow,
Worktrees and MCP steal the show!
Fork-and-skin complete—let's go!

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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