Merge branch 'cw/review-tab-performance' into cw-merge-1772826318787

This commit is contained in:
Lukas May
2026-03-06 20:45:19 +01:00
40 changed files with 3594 additions and 320 deletions

View File

@@ -14,6 +14,7 @@
| Tiptap | Rich text editor (ProseMirror-based) |
| Lucide | Icon library |
| Geist Sans/Mono | Typography (variable fonts in `public/fonts/`) |
| react-window 2.x | Virtualized list rendering for large file trees in ReviewSidebar |
## Design System (v2)
@@ -114,15 +115,26 @@ The initiative detail page has three tabs managed via local state (not URL param
### Review Components (`src/components/review/`)
| Component | Purpose |
|-----------|---------|
| `ReviewTab` | Review tab container — orchestrates header, diff, sidebar, and preview. Phase-level review has threaded inline comments (with reply support) + Request Changes; initiative-level review has Request Changes (summary prompt) + Push Branch / Merge & Push |
| `ReviewHeader` | Consolidated toolbar: phase selector pills, branch info, stats, preview controls, approve/reject actions |
| `ReviewSidebar` | VSCode-style icon strip (Files/Commits views) with file list, root-only comment counts, and commit navigation |
| `DiffViewer` | Unified diff renderer with threaded inline comments (root + reply threads) |
| `ReviewTab` | Review tab container — orchestrates header, diff, sidebar, and preview. Phase-level review has threaded inline comments (with reply support) + Request Changes; initiative-level review has Request Changes (summary prompt) + Push Branch / Merge & Push. Phase diff uses metadata-only `FileDiff[]` from `getPhaseReviewDiff`; commit diff parses `rawDiff` via `parseUnifiedDiff``FileDiffDetail[]`. Passes `commitMode`, `phaseId`, `expandAll` to DiffViewer |
| `ReviewHeader` | Consolidated toolbar: phase selector pills, branch info, stats (uses `totalAdditions`/`totalDeletions` props when available, falls back to summing files), preview controls, Expand all button, approve/reject actions |
| `ReviewSidebar` | VSCode-style icon strip (Files/Commits views) with file list, root-only comment counts, and commit navigation. FilesView uses react-window 2.x `List` for virtualized rendering when the row count exceeds 50 (dir-headers + file rows). Scroll position is preserved across Files ↔ Commits tab switches. Directories are collapsible. Clicking a file scrolls the virtual list to that row. |
| `DiffViewer` | Unified diff renderer with threaded inline comments (root + reply threads). Accepts `FileDiff[] | FileDiffDetail[]`, `phaseId`, `commitMode`, `expandAll` props |
| `CommentThread` | Renders root comment with resolve/reopen + nested reply threads (agent replies styled with primary border). Inline reply form |
| `ConflictResolutionPanel` | Merge conflict detection + agent resolution in initiative review. Shows conflict files, spawns conflict agent, inline questions, re-check on completion |
| `PreviewPanel` | Docker preview status: building/running/failed with start/stop (legacy, now integrated into ReviewHeader) |
| `ProposalCard` | Individual proposal display |
#### Syntax Highlighting (`use-syntax-highlight.ts` + `highlight-worker.ts`)
`useHighlightedFile(filePath, allLines)` returns `LineTokenMap | null`. Tokenisation runs off the main thread:
- **Worker path** (default): a module-level pool of 2 ES module Web Workers (`highlight-worker.ts`) each import shiki's `codeToTokens` dynamically. Requests are round-robined by `requestCount % 2`. Responses are correlated by UUID. Late responses after unmount are silently discarded via the `pending` Map.
- **Fallback path** (CSP / browser-compat): if `Worker` construction throws, `createHighlighter` is used on the main thread but processes 200 lines per chunk, yielding between chunks via `scheduler.yield()` or `setTimeout(0)`.
Callers receive `null` while highlighting is in progress and a populated `Map<lineNumber, ThemedToken[]>` once it resolves. `LineWithComments` already renders plain text when `null`, so no caller changes are needed.
Vite must be configured with `worker.format: 'es'` (added to `vite.config.ts`) for the worker chunk to bundle correctly alongside code-split app chunks.
### UI Primitives (`src/components/ui/`)
shadcn/ui components: badge (6 status variants + xs size), button, card, dialog, dropdown-menu, input, label, select, sonner, textarea, tooltip.

View File

@@ -40,6 +40,8 @@ Worktrees stored in `.cw-worktrees/` subdirectory of the repo. Each agent gets a
| `ensureBranch(repoPath, branch, baseBranch)` | Create branch from base if it doesn't exist (idempotent) |
| `mergeBranch(repoPath, source, target)` | Merge via ephemeral worktree, returns conflict info |
| `diffBranches(repoPath, base, head)` | Three-dot diff between branches |
| `diffBranchesStat(repoPath, base, head)` | Per-file metadata (path, status, additions, deletions) — no hunk content. Binary files included with `status: 'binary'` and counts of 0. Returns `FileStatEntry[]`. |
| `diffFileSingle(repoPath, base, head, filePath)` | Raw unified diff for a single file (three-dot diff). `filePath` must be URL-decoded. Returns empty string for binary files. |
| `deleteBranch(repoPath, branch)` | Delete local branch (no-op if missing) |
| `branchExists(repoPath, branch)` | Check local branches |
| `remoteBranchExists(repoPath, branch)` | Check remote tracking branches (`origin/<branch>`) |

View File

@@ -122,7 +122,8 @@ Each procedure uses `require*Repository(ctx)` helpers that throw `TRPCError(INTE
| listInitiativePhaseDependencies | query | All dependency edges |
| getPhaseDependencies | query | What this phase depends on |
| getPhaseDependents | query | What depends on this phase |
| getPhaseReviewDiff | query | Full branch diff for pending_review phase |
| getPhaseReviewDiff | query | File-level metadata for pending_review phase: `{phaseName, sourceBranch, targetBranch, files: FileStatEntry[], totalAdditions, totalDeletions}` — no hunk content. Results are cached in-memory by `phaseId:headHash` (TTL: `REVIEW_DIFF_CACHE_TTL_MS`, default 5 min). Cache is invalidated when a task merges into the phase branch. |
| getFileDiff | query | Per-file unified diff on demand: `{phaseId, filePath, projectId?}``{binary: boolean, rawDiff: string}`; `filePath` must be URL-encoded; binary files return `{binary: true, rawDiff: ''}` |
| getPhaseReviewCommits | query | List commits between initiative and phase branch |
| getCommitDiff | query | Diff for a single commit (by hash) in a phase |
| approvePhaseReview | mutation | Approve and merge phase branch |