Files
clawtap/docs/superpowers/plans/2026-03-25-review-state-separation.md
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

9.1 KiB

Review State Separation + Session List Cleanup Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Separate active review and history review states so viewing historical reviews doesn't conflict with active reviews, fix marker in session list, hide child sessions from session list.

Spec: docs/superpowers/specs/2026-03-25-review-state-separation-design.md


Task 1: Separate activeReview and historyReview states

Files:

  • src/hooks/useChat.ts

  • src/components/ChatView.tsx

  • src/components/FloatingReviewPanel.tsx

  • Step 1: Add historyReview state to useChat, rename reviewPanelStateactiveReviewPanel

In src/hooks/useChat.ts:

Change state declarations (around line 147):

// OLD:
const [reviewPanelState, setReviewPanelState] = useState<'expanded' | 'minimized'>('expanded');

// NEW:
const [activeReviewPanel, setActiveReviewPanel] = useState<'expanded' | 'minimized'>('expanded');
const [historyReview, setHistoryReview] = useState<any>(null);

Export historyReview, setHistoryReview, activeReviewPanel, setActiveReviewPanel in the return value. Remove old reviewPanelState, setReviewPanelState exports.

  • Step 2: Remove readOnlyReview state from ChatView

In src/components/ChatView.tsx, remove:

const [readOnlyReview, setReadOnlyReview] = useState(false);

Replace all readOnlyReview references with !!historyReview (from useChat).

Replace all setReadOnlyReview(...) calls — remove them (historyReview existence replaces the boolean).

  • Step 3: Update handleOpenReadOnlyReview to use historyReview

Change from:

setActiveReview({ ...review data... });
setReviewPanelState('expanded');
setReadOnlyReview(true);

To:

setHistoryReview({ ...review data... });
if (activeReview) setActiveReviewPanel('minimized');  // minimize active if exists
  • Step 4: Update closeReview to clear both states
const closeReview = useCallback(async () => {
  if (activeReview?.reviewId) {
    try { await api.endReview(activeReview.reviewId); } catch {}
  }
  setActiveReview(null);
  setHistoryReview(null);
  setReviewInitialPrompt(null);
  setReviewCwd(null);
}, [activeReview]);

No more readOnlyReview check — closeReview always ends the active review.

Add a separate closeHistoryPanel:

const closeHistoryPanel = useCallback(() => {
  setHistoryReview(null);
}, []);
  • Step 5: Update handleReviewSelect (start new review)

Add: setHistoryReview(null) to clear any open history panel. Change: setReviewPanelState('expanded')setActiveReviewPanel('expanded')

  • Step 6: Update FloatingReviewPanel rendering in ChatView

Compute panel review outside JSX (not in IIFE):

// Near other memos/derived state
const panelReview = historyReview || (activeReviewPanel === 'expanded' ? activeReview : null);
const isHistoryPanel = !!historyReview;

Replace the current conditional rendering with:

{panelReview && (
  <FloatingReviewPanel
    reviewId={panelReview.reviewId || panelReview.id || undefined}
    childSessionId={panelReview.childSessionId || panelReview.child_cli_session_id || undefined}
    childAdapter={panelReview.childAdapter || panelReview.child_adapter}
    reviewTitle={panelReview.reviewTitle || panelReview.review_title}
    onEnd={isHistoryPanel ? closeHistoryPanel : closeReview}
    onMinimize={isHistoryPanel ? undefined : () => setActiveReviewPanel('minimized')}
    readOnly={isHistoryPanel}
    initialPrompt={!isHistoryPanel ? (reviewInitialPrompt || undefined) : undefined}
    cwd={!isHistoryPanel ? (reviewCwd || undefined) : undefined}
    onSessionCreated={!isHistoryPanel ? onSessionCreatedCallback : undefined}
  />
)}

Note: panelState and onPanelStateChange props removed (Step 8).

  • Step 7: Update minimized bar in renderAboveInput

Show minimized bar when: activeReview && (activeReviewPanel === 'minimized' || historyReview)

Update ▲ Expand button:

onClick={() => { setHistoryReview(null); setActiveReviewPanel('expanded'); }}

The minimized bar is for the ACTIVE review only. It always shows active review info, never history info.

Bar shows when: activeReview !== null AND (activeReviewPanel === 'minimized' OR historyReview !== null)
Bar content: always shows activeReview info
Bar label: always "active" (not "ended")
Bar buttons: ▲ Expand (closes history + expands active) | End (ends active review)

Remove all readOnlyReview / historyReview checks from the bar rendering — bar is purely about active review.

  • Step 8: Update FloatingReviewPanel type — remove panelState prop

Since FloatingReviewPanel is only rendered when it should be visible (expanded), the panelState prop is no longer needed. The parent (ChatView) controls visibility.

Remove from interface:

panelState: 'expanded' | 'minimized';
onPanelStateChange: (state: 'expanded' | 'minimized') => void;

Remove the if (panelState === 'minimized') return null; check.

Keep the ▼ minimize button in the header — it calls a new onMinimize prop:

onMinimize?: () => void;  // only for active (non-readOnly) panel

ChatView passes: onMinimize={() => setActiveReviewPanel('minimized')}

  • Step 9: Verify + commit
npx tsc --noEmit
git add src/hooks/useChat.ts src/components/ChatView.tsx src/components/FloatingReviewPanel.tsx
git commit -m "refactor: separate activeReview and historyReview states, mutual exclusion"

Task 2: Fix marker in session list (Codex getSessions)

Files:

  • server/adapters/codex/jsonl-store.ts

  • Step 1: Strip marker in getSessions (line 204)

Change:

firstPrompt: entry.text ? entry.text.slice(0, 200) : null,

To:

firstPrompt: entry.text
  ? entry.text.replace(/^\[CODETAP_REF:[^\]]+\](?:\\n|\n)?/, '').slice(0, 200)
  : null,
  • Step 2: Verify + commit
npx tsc --noEmit
git add server/adapters/codex/jsonl-store.ts
git commit -m "fix: strip CODETAP_REF marker from Codex getSessions firstPrompt"

Task 3: Hide child sessions from session list

Files:

  • server/index.ts

  • Step 1: Filter child sessions from project session list

Find the GET endpoint that returns sessions for a project (search for getSessions calls in server/index.ts). After getting the sessions array, filter out child session IDs:

const childIds = sessionReviews.getAllChildIds();
const filtered = sessions.filter(s => !childIds.has(s.sessionId));

getAllChildIds() already exists in server/db.ts — it returns a Set<string> of child CLI session IDs. Verify it includes ALL child IDs (both active and ended reviews), not just active ones. Ended child sessions should also be hidden from the session list.

  • Step 2: Filter child sessions from active sessions list

Find the GET endpoint for active sessions. Apply the same filter:

const childIds = sessionReviews.getAllChildIds();
const filtered = activeSessions.filter(s => !childIds.has(s.sessionId));
  • Step 3: Verify + commit
npx tsc --noEmit
git add server/index.ts
git commit -m "fix: hide child review sessions from project and active session lists"

Task 4: E2E Verification

  • Step 1: Start server, create Claude session, send message
  • Step 2: Send to Codex → verify panel opens with response
  • Step 3: Click ▼ minimize → verify thin bar appears, parent input usable
  • Step 4: Click ▲ expand → verify panel opens again
  • Step 5: Click End → verify panel closes, review markers appear
  • Step 6: Click collapsed review card → verify read-only panel opens (history)
  • Step 7: Verify minimized bar still shows active review info (if active review exists)
  • Step 8: Close read-only panel → verify return to normal
  • Step 9: Check session list → verify no CODETAP_REF marker, no child sessions visible

Self-Review

State model after Task 1

activeReview      — ongoing review (null if none)
historyReview     — historical review being viewed (null if none)
activeReviewPanel — 'expanded' | 'minimized'

Panel: historyReview || (expanded activeReview) || nothing
Bar: activeReview && (minimized || historyReview)

Mutual exclusion

  • Open history → minimize active
  • Expand active → close history
  • End active → close both
  • Start new → close history + expand

Files changed

File Changes
src/hooks/useChat.ts Add historyReview state, rename reviewPanelState → activeReviewPanel
src/components/ChatView.tsx Remove readOnlyReview, use historyReview, update closeReview/bar/panel rendering
src/components/FloatingReviewPanel.tsx Remove panelState prop, add onMinimize
server/adapters/codex/jsonl-store.ts Strip marker in getSessions
server/index.ts Filter child sessions from session/active lists