Files
clawtap/docs/superpowers/specs/2026-03-26-cross-ai-review-v2-design.md
T
kuannnn 42861ea7fa feat: ClawTap v0.1.0 — initial release
Multi-adapter mobile UI for AI coding assistants.
Supports Claude Code, Codex CLI, and Gemini CLI through one interface.

Features:
- Real-time bidirectional sync via tmux + WebSocket
- Cross-AI review (send one AI's output to another for review)
- Multi-review tabs with minimize/expand
- Push notifications (PWA) with smart session-aware filtering
- Three-channel event system (hooks, file watcher, pane monitor)
- Voice input, image paste, draft persistence
- Terminal-native design (JetBrains Mono, dark theme, pixel art claw)
- CLI with --adapter flag on every command
- Zero-overhead fire-and-forget hooks
2026-03-26 10:40:26 +08:00

131 lines
6.7 KiB
Markdown

# Cross-AI Review v2 — Multi-Review, Marker Position, Send-To UX
**Date**: 2026-03-26
**Status**: Approved
## Problem
Three issues with the current cross-AI review system:
1. **"Review ended" marker position** — rendered at the anchor message (where "Send to" was clicked), not at the bottom of the chat where the user actually pressed End. Misleading timeline.
2. **"Send to" ignores active reviews** — always opens the full adapter/model selection flow, even when there's already an active review that should receive the message.
3. **Single active review limit** — frontend state (`activeReview`) is a single object. Cannot run multiple reviews simultaneously (e.g., send one message to Codex and another to Claude).
Additionally: **textarea placeholder 16px override** — global CSS `input, textarea, select { font-size: 16px }` (iOS zoom prevention) overrides Tailwind `text-sm` in the review panel, making the placeholder disproportionately large.
## Design
### 1. Review Ended Marker Position
**Current**: "started", "in progress", and "ended" markers are all rendered by `renderReviewMarkers`, keyed by `anchor_message_id`. They all appear after the anchor message.
**New**: Split markers into two locations:
- **"started" + CollapsedReviewCard** — at anchor message (shows where the review was initiated; card lets user tap to view the review conversation)
- **"in progress"** — at anchor message (only for active reviews, replaces CollapsedReviewCard)
- **"ended"** — rendered after the last message in parent chat at the time End was pressed (shows where in the timeline the review concluded)
**Implementation**:
- Add `end_anchor_message_id TEXT` column to `session_reviews` table
- When `endReview()` is called, set `end_anchor_message_id` to the ID of the last message currently in the parent session's message history
- Server: `GET /api/reviews` response already returns all review columns — no API change needed
- Frontend: `renderReviewMarkers` uses two maps:
- `startMarkersByAnchor` — keyed by `anchor_message_id`:
- Active review: "started" marker + "in progress" marker
- Ended review: "started" marker + CollapsedReviewCard (tap to view)
- `endMarkersByAnchor` — keyed by `end_anchor_message_id`:
- Ended review only: "Review ended" marker
### 2. Send-To with Active Review
**Current**: Clicking "↗ Send to" always sets `reviewMenuMessageId` → opens `ReviewActionMenu` bottom sheet → full adapter/model flow.
**New**: Two paths based on whether active reviews exist:
**Path A — No active reviews**: Same as current. Full adapter/model selection → create new child session.
**Path B — Active review(s) exist**: Show a simplified bottom sheet with options:
- One button per active review: **"Send to {Adapter} review"** (with adapter badge + color). Clicking sends the message text directly to that child session as a new prompt.
- A divider line
- **"Start new review..."** button at the bottom → opens the current full flow
**Sending to existing review**:
- Extract text from the clicked message
- Call `childChat.sendMessage(text)` on the corresponding review's `useChat` instance
- Auto-expand and switch tab to that review's tab
- No new DB row, no new session — just a follow-up message in the existing child session
### 3. Multi-Review UI (Design D)
**State change**: `activeReview` (single object) → `activeReviews` (array of review objects). Each entry has: `{ reviewId, childSessionId, childCliSessionId, childAdapter, anchorMessageId, reviewTitle }`.
**Minimized state** (all reviews collapsed):
- Single compact bar above the input: colored dots for each review + "{N} reviews: Codex · Claude" + "▲ Expand"
- Clicking the bar expands to the tabbed panel
**Expanded state** (panel visible):
- 50% height bottom panel with:
- **Handle bar** at top (drag/click to minimize)
- **Tab bar**: one tab per active review, each showing adapter color dot + name. Active tab underlined with adapter color. Each tab has ✕ to end that review. Right side has ▼ minimize button.
- **Chat area**: messages for the focused tab's child session
- **Input**: "Reply to {Adapter} review..." placeholder
**Single review special case**: When only 1 active review, show header (badge + title + ▼ + End) instead of tab bar. Same as current design.
**Each tab is an independent `useChat` hook**. The `FloatingReviewPanel` component manages an array of child chat instances, renders only the active tab's messages, but keeps all hooks alive for background message receipt.
**Tab lifecycle**:
- New review → push to `activeReviews`, add tab, auto-focus it
- End review (✕ or "End" button) → call `api.endReview(reviewId)`, remove from `activeReviews`, focus adjacent tab
- All reviews ended → panel disappears, minimized bar disappears
### 4. Placeholder Font Size Fix
**Root cause**: `src/index.css` line 83: `input, textarea, select { font-size: 16px }` overrides `text-sm` (14px).
**Fix**: Keep the 16px rule for iOS zoom prevention, but add a specific override for the review panel textarea:
```css
.review-panel-input textarea { font-size: 14px !important; }
```
Or use Tailwind's `!text-sm` on the textarea in FloatingReviewPanel. The main chat input stays at 16px (looks fine at full width); only the cramped review panel gets the smaller size.
## Data Flow Changes
### End Review (updated)
```
User taps "End" on tab / End button
→ Frontend: get last message ID from parent chat messages array
→ api.endReview(reviewId, { endAnchorMessageId: lastMsgId })
→ Server: UPDATE session_reviews SET ended_at=NOW(), end_anchor_message_id=?
→ Server: broadcast WS REVIEW_ENDED { reviewId }
→ Server: destroySession(childCliSessionId)
→ Frontend: remove from activeReviews array
→ Frontend: reviews re-fetched → endMarkersByAnchor updated
→ "ended" marker + CollapsedReviewCard appear after the correct message
```
### Send-To Existing Review
```
User taps "↗ Send to" on assistant message (with active reviews present)
→ Simplified bottom sheet: [Send to Codex review] [Send to Claude review] [Start new...]
→ User taps "Send to Codex review"
→ Extract text from the anchor message
→ Find the Codex review's useChat sendMessage function
→ sendMessage(text) → message sent to child session
→ Auto-expand panel, switch to Codex tab
```
## Migration
- New column `end_anchor_message_id` on `session_reviews`: nullable, no migration needed for existing rows (they will show "ended" at anchor position as fallback)
## Scope Exclusions
- Drag-to-reorder tabs: not needed
- Resize panel height: not needed (fixed 50%)
- Review notifications/badges on minimized bar: nice-to-have, not in v2
- Persist expanded/minimized state across page refreshes: not needed