Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .github/workflows/auto-merge.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Auto-merge green PRs

on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review, labeled]
check_suite:
types: [completed]

jobs:
auto-merge:
if: github.actor == '4Gaige' || github.event.pull_request.user.login == '4Gaige'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Enable auto-merge on this PR
uses: peter-evans/enable-pull-request-automerge@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
pull-request-number: ${{ github.event.pull_request.number }}
merge-method: squash
continue-on-error: true
35 changes: 35 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: CI

on:
pull_request:
branches: [main]
push:
branches: [main]

jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- run: npm ci
- run: npm run build
- name: Test (if tests exist)
run: |
if grep -q '"test":' package.json && ! grep -q '"test": *"[^"]*exit [^0]"' package.json; then
npm test -- --passWithNoTests || true
fi
- name: TypeScript check
run: |
if [ -f tsconfig.json ]; then
npx tsc --noEmit || echo "::warning::tsc reported errors"
fi
- name: Lint
run: |
if grep -q '"lint":' package.json; then
npm run lint || echo "::warning::lint reported issues"
fi
56 changes: 56 additions & 0 deletions .github/workflows/sync-upstream.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Sync with upstream

on:
schedule:
- cron: '0 12 * * 1' # Mondays at noon UTC
workflow_dispatch: {}

jobs:
sync:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
ref: main
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}

- name: Ensure upstream-sync branch exists
run: |
if ! git ls-remote --exit-code --heads origin upstream-sync >/dev/null 2>&1; then
echo "Creating upstream-sync branch from main."
git branch upstream-sync main
git push origin upstream-sync
else
echo "upstream-sync branch already exists on origin."
fi

- name: Sync upstream
id: sync
uses: aormsby/Fork-Sync-With-Upstream-action@v3.4
with:
target_sync_branch: upstream-sync
target_repo_token: ${{ secrets.GITHUB_TOKEN }}
upstream_sync_branch: main
upstream_sync_repo: siteboon/claudecodeui
upstream_pull_args: --no-ff
test_mode: false

- name: Open PR if sync had new commits
if: steps.sync.outputs.has_new_commits == 'true'
uses: peter-evans/create-pull-request@v6
with:
base: main
branch: upstream-sync
title: 'chore: sync with upstream'
body: |
Automated upstream sync from siteboon/claudecodeui.

Review diffs carefully — Dispatch's customizations are meant to be merge-forever-safe
(additive wrappers, new files), but if upstream touched something we overlap, resolve
conflicts keeping **their** code for shared files and **ours** for the wrapper file.
labels: upstream
draft: true
8 changes: 6 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ temp/
.cline/
.windsurf/
.serena/
CLAUDE.md
.mcp.json
.gemini/

Expand All @@ -130,10 +129,15 @@ dev-debug.log

# Task files
tasks.json
tasks/
tasks/

# Translations
!src/i18n/locales/en/tasks.json

# Phase 5 kanban UI lives under src/components/tasks/ — not the TaskMaster
# task-store directory ignored above.
!src/components/tasks/
!src/components/tasks/**
!src/i18n/locales/ja/tasks.json
!src/i18n/locales/ru/tasks.json
!src/i18n/locales/de/tasks.json
Expand Down
118 changes: 118 additions & 0 deletions docs/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Dispatch — Agent Playbook

You are working on a fork of `siteboon/claudecodeui` called **Dispatch**. Dispatch adds:
a Midnight design system skin, automatic session naming (Haiku-titled), repo-based project grouping,
topic clustering, a live-preview tab, a live Chrome viewport (CDP screencast), git-worktree parallel
sessions, and selective MCP integrations.

## Golden rules — read before touching anything

### 1. Additive patches only on churny files
These three files rebase-conflict every week from upstream. **Never edit them in place.**
- `server/projects.js` (85 KB, top-2 churn)
- `server/index.js` (95 KB, god-file)
- `src/components/sidebar/subcomponents/SidebarProjectItem.tsx`

Instead: create new modules under `server/services/`, new routes under `server/routes/`, new
components under `src/components/**/topics/` (etc). Touch the shared file with ONE new
`require`/`import` line, nothing more. Wrapper composition beats in-place edits.

### 2. Mobile-first, always
Every layout decision targets **375×812 iPhone** first. Grow with `md:` `lg:` `xl:` breakpoints.
- Touch targets ≥44×44px everywhere
- Bottom tab bar on mobile (`.ds-tabbar`), persistent left rail at `lg:` (≥1024px)
- Sidebar = `.ds-sheet` bottom sheet on mobile, persistent second column on desktop
- Preview + Browser = fullscreen modals on mobile, resizable right panels on desktop
- Sticky bottom elements use `padding-bottom: env(safe-area-inset-bottom)`

### 3. Midnight design system is the law
Every new class MUST come from the Midnight catalog. See `docs/midnight/README.md` and `docs/midnight/demo.html`.
- Buttons: `.btn-primary` (white pill) or `.btn.btn-secondary|-ghost|-danger|-pill|-pill-light`
- Cards/surfaces: `.ds-tile` (frosted glass), `.ds-tile-inset`, `.ds-tile-plain`, `.ds-pastel`
- Inputs: `.ds-input`
- Badges: `.badge` with pastel variants
- Chips: `.ds-chip` with pastel variants and `-active`
- Segmented: `.ds-segment` + `.ds-segment-item` + `-item-active`
- Tab bar: `.ds-tabbar` with `.ds-tabbar-item/-pill/-label`
- Bottom sheet: `.ds-sheet` + `.ds-sheet-backdrop` + `.ds-sheet-handle`
- Top bar: `.topbar-glass`

**Never raw Tailwind color classes** like `bg-blue-500`, `text-gray-400`. The only acceptable palette
is Midnight tokens via semantic Tailwind shadcn vars (`bg-background`, `text-muted-foreground`,
`bg-primary`, etc.) that Phase 1 maps to Midnight CSS variables.

### 4. Per-section accent discipline
Top-level containers set `data-accent`:
| Section | Accent |
|---|---|
| Chat | `sky` |
| Sidebar | `lavender` |
| Preview | `mint` |
| Browser viewport | `peach` |
| Tasks / Kanban | `butter` |
| Settings / More | `blush` |

Input focus borders + selections inherit `--midnight-accent` from the nearest `data-accent` ancestor.

### 5. Reuse existing data before adding schema
The `session_names` table already has `custom_name` (see `server/database/schema.js`). Auto-titles
reuse it — do not add a parallel table. New tables only for genuinely new data shapes (e.g., Topics).

### 6. New features live in new files
| Feature | Location |
|---|---|
| Auto-titler | `server/services/session-titler.js` |
| Repo grouper | `server/services/repo-grouper.js` (new fn called from `getProjects`) |
| Topic clusterer | `server/services/topic-clusterer.js` |
| Preview proxy | `server/routes/preview-proxy.js` |
| CDP screencast | `server/routes/chrome-screencast.js` |
| Topic UI | `src/components/sidebar/topics/` |
| Preview UI | `src/components/preview/` |
| Browser viewport UI | `src/components/browser/` |
| Kanban UI | `src/components/tasks/` |

### 7. Every PR ends with review
At the end of every phase, spawn a **fresh-eyes Opus reviewer** sub — no session history, reads
only the diff + this file + the phase brief. Reviewer answers the structured checklist below with
YES/NO. Fix findings until all YES (max 3 loops).

**Phases 2 and 5 additionally require a visual-review Opus sub** that:
1. Runs `npm run dev` in the worktree
2. Uses Playwright to screenshot the changed pages at 375×812 (iPhone 14) and 1440×900 (desktop)
3. Saves to `docs/screenshots/phase-N/`
4. Compares against `docs/midnight/demo.html` (open in browser via Playwright) for visual-language fidelity
5. Reports mismatches (wrong shadows, missing blur, off-palette, touch-targets too small, etc.)

## Review checklist (reviewer must answer YES to merge)
- [ ] Follows additive-patch rule (no edits to 3 churny files)
- [ ] Every new class is Midnight catalog or Midnight-mapped shadcn semantic var
- [ ] No raw Tailwind color classes in any new code (`grep -rE 'bg-(blue|red|green|gray|yellow|pink|purple|indigo|orange|slate|zinc|neutral|stone)-[0-9]'` on the diff returns nothing)
- [ ] Mobile layout renders cleanly at 375×812 (screenshot proves it)
- [ ] Desktop layout renders cleanly at 1440×900 (screenshot proves it)
- [ ] Touch targets ≥44×44px on mobile
- [ ] `npm run build` succeeds, bundle size delta ≤5% vs main
- [ ] `npm test` passes (or: no tests exist for touched files, noted in PR)
- [ ] No hardcoded secrets, no new network calls without cause
- [ ] Keyboard navigation works (Tab, Enter, arrow keys where appropriate)
- [ ] Empty / loading / error states handled
- [ ] Phase brief acceptance criteria all met

## Model tier discipline
- **Haiku** — grep, file search, simple edits, formatting, dependency bumps, test runners
- **Sonnet** — feature implementation, refactors, test writing, TypeScript fixes, docs
- **Opus** — architecture decisions, tricky refactors, design review, visual review, fresh-eyes review

Default to Sonnet. Escalate to Opus only for architecture/review. Use Haiku for mechanical ops.

## Architecture map (30-second version)
- Frontend: React 18 + Vite + Tailwind + shadcn-style tokens + CodeMirror
- Backend: Node/Express at `server/index.js`; routes split across `server/routes/`
- State: Context API for cross-cutting (`ThemeContext`, `AuthContext`, `WebSocketContext`, `PluginsContext`, `TaskMasterContext`, `TasksSettingsContext`, `PermissionContext`); `src/stores/useSessionStore.ts` for session messages
- Data: SQLite at `~/.cloudcli/auth.db`; Claude session JSONLs read from `~/.claude/projects/<slug>/`
- Sidebar entry: `src/components/sidebar/view/Sidebar.tsx`
- Tree rendering: `SidebarProjectList` → `SidebarProjectItem` (DO NOT edit, wrap) → `SidebarProjectSessions` → `SidebarSessionItem`

## If you get stuck
1. Retry up to 3 times with different approaches
2. If still stuck, log status + error to `/tmp/dispatch-build.log`
3. Exit nonzero; the orchestrator records the failure for the morning summary
52 changes: 52 additions & 0 deletions docs/build-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Dispatch — Master Build Plan

## Vision
Fork `siteboon/claudecodeui` (v1.30.0) into `4Gaige/Dispatch`. Apply Midnight design system as
mobile-first skin. Add automatic session naming, repo-based project grouping, Topic clustering,
live preview + live Chrome viewport, worktree parallelism, kanban task view, and selective MCP
integrations. Keep up with upstream via weekly auto-PR.

## Waves
```
Wave 0 (done by human, this session) Phase 0 — Setup / fork / docs / orchestrator
Wave 1 (solo, 1 session) Phase 1 — Midnight skin + mobile-first layout
Wave 2 (3 parallel sessions) Phase 2 — Sidebar tree + tab-bug fix [visual review]
Phase 5 — Preview + Chrome + worktrees + tasks [visual review]
Phase 6 — MCP integrations + auto-update verify
Wave 3 (1 session, sequential within) Phase 3 — Auto-naming
Phase 4 — Topic clustering
```

## Phase summaries (briefs in `docs/phase-N-brief.md`)
| # | Name | Time | Parallel? | Visual review? |
|---|---|---|---|---|
| 1 | Midnight skin + mobile layout | 2–3d | No | Yes |
| 2 | Sidebar tree + tab fix | 1.5d | Wave 2 | **Yes** |
| 3 | Auto-naming (Haiku titler) | 0.5d | No | No |
| 4 | Topic clustering (Haiku tags + HDBSCAN) | 1d | No | No |
| 5 | Preview + Chrome + worktrees + tasks | 3d | Wave 2 | **Yes** |
| 6 | MCP integrations + upstream tracking | 0.5d | Wave 2 | No |

## Ecosystem steals (distributed into phases)
| Source | Stars | Phase | What to borrow |
|---|---|---|---|
| [open-webui/open-webui](https://github.com/open-webui/open-webui) | 133k | 1, 2, 3, 4 | Mobile chat composer, draggable folders, title-gen prompt, tag-as-filter |
| [iOfficeAI/AionUi](https://github.com/iOfficeAI/AionUi) | 22k | 1, 2 | Swipe-between-sessions gesture, conversation search/filter UX |
| [yxwucq/CCUI](https://github.com/yxwucq/CCUI) | 30 | 5 | Git-worktree parallel session pattern |
| [Ark0N/Codeman](https://github.com/Ark0N/Codeman) | 329 | 5 | Session activity indicators (green/grey/red/pulse) |
| [guilhermexp/claudecodeui-kanban](https://github.com/guilhermexp/claudecodeui-kanban) | 11 | 5 | TodoWrite → kanban cards |
| [DeusData/codebase-memory-mcp](https://github.com/DeusData/codebase-memory-mcp) | 1.7k | 6 | Repo-aware sidebar signals |
| [steipete/claude-code-mcp](https://github.com/steipete/claude-code-mcp) | 1.2k | 6 | Spawn sub-agent button |

## Upstream tracking
`aormsby/Fork-Sync-With-Upstream-action@v3.4` on `.github/workflows/sync-upstream.yml`.
Monday noon UTC cron → PR `upstream-sync → main` → human review + merge.

## Running locally
`git pull && npm install && npm run build && launchctl kickstart -k gui/$UID/com.cloudcli.server`

Launchd plist at `~/Library/LaunchAgents/com.cloudcli.server.plist` runs
`node /Users/home/src/Dispatch/dist-server/server/index.js`.
72 changes: 72 additions & 0 deletions docs/follow-ups.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Post-merge follow-ups

Tracking nice-to-have items surfaced by the post-merge review pass on
`review/post-merge-fixes`. None block production; each can be picked up by
the next phase or a polish PR. Citations point at file:line in the merged
commit; line numbers may drift after the review PR lands.

## Phase 1 — Midnight skin + mobile-first

- **Tasks slot missing from primary nav** — `useAppNavItems.ts:18-24` defines
five slots (Chat / Sessions / Preview / Browser / More). `AppContent.tsx:202`
honors `activeTab === 'tasks'` for accent, but no rail/tab-bar entry surfaces
the kanban view. The floating "Tasks" button in `MainContent.tsx` is the
only entry point. Decide: surface as a sixth slot or accept floating-only.
- **Sheet initial focus** — `MobileSidebarSheet.tsx:65` opens with no initial
focus target, so screen readers land on the trigger. Move focus to the sheet
container or first interactive child on open.
- **Drag-handle hit area** — `MobileSidebarSheet.tsx:84-92` is `pt-3 pb-2`
(~28px). Below the 44×44 minimum the rest of the app respects.
- **`visualViewport` listener cleanup** — `AppContent.tsx:128-137` registers
once; consider also listening to `scroll` (iOS sometimes only fires that on
keyboard show) and clearing `--keyboard-height` if `vv` becomes undefined.

## Phase 2 — Sidebar tree + repo grouping

- **Native `prompt`/`confirm` for topic rename/delete** —
`SidebarTopicGroup.tsx:138`. Works but jarring vs. Midnight; replace with
inline editable chip + custom modal.
- **Mobile search-scope segment uses ad-hoc styles** — `SidebarHeader.tsx`
mobile branch (~line 240). Desktop got the `.ds-segment` polish; apply the
same to mobile.
- **Aria mismatch for single-project repos** — `SidebarProjectTree.tsx:189`:
when no group header renders, `aria-labelledby={groupId}` points at a
nonexistent button. Drop the attr in that branch.
- **`crypto.randomUUID()` fallback** — `useTopicStorage.ts:106`. Add a
`Math.random()`-based fallback for insecure-context resilience.
- **Repo-grouper cache invalidation** — `repo-grouper.js:333` keys by
`fullPath` only. Add a TTL or git-mtime check so origin URL changes are
picked up without manual cache deletion.

## Phase 5 — Preview + Chrome + Worktrees + Tasks

- **Worktree → session map is partial** — only the *currently selected*
worktree's dot can light up. Cross-worktree resolution requires the
server-side activity stream to know which sessionId is running in each
`<repo>/.claude/worktrees/<slug>` cwd. Add `/api/worktrees/active-sessions`
(or include `__cwd` on every session payload) and feed it into
`SessionActivityProvider` in `AppContent.tsx`.
- **Tasks aside is fixed-width on desktop** — `MainContent.tsx:194-208` uses
`w-[380px]`. The phase-5 brief asks for a resizable right panel.
- **Preview proxy CSP/X-Frame strip is unconditional** —
`preview-proxy.js:79-81` strips CSP and X-Frame-Options on every response.
In production deployments that's broader than needed — consider gating to
`process.env.NODE_ENV !== 'production'` or to the configured port allowlist.
- **`SpawnSubAgentButton` SSE event narrowing** — `SpawnSubAgentButton.tsx:127`
casts `evt.event` straight to `StreamEvent['type']`, so a malformed server
event would silently produce a garbage payload. Narrow with a `switch`.
- **`SessionFilesTouchedChips` layout shift** — `SessionFilesTouchedChips.tsx:88`
reserves no height before chips arrive; once the IntersectionObserver
resolves, the row jumps. Reserve a min-height.
- **`spawn-sub-agent` has no time/output cap** — `mcp-bootstrap.js:259`. A
runaway sub-agent could stream forever. Add a hard timeout + byte cap.

## Cross-cutting

- **Bundle size warning** — main client chunk is 2.5 MB minified (~760 KB
gzipped). Vite warns above 1 MB. Worth a code-split pass: lazy-load
Preview/Browser/Tasks panes, split CodeMirror/xterm vendor chunks further.
- **No `npm test` script** — Phase briefs reference test runs but
`package.json` has none. Add at minimum a Vitest harness with smoke tests
for the new server routes (preview-proxy port allowlist, mcp-bootstrap
workingDir validation, tasks path-traversal guard).
Loading