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

6.7 KiB

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:

.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