diff --git a/docs/superpowers/specs/2026-04-11-detail-panel-7-fixes-design.md b/docs/superpowers/specs/2026-04-11-detail-panel-7-fixes-design.md new file mode 100644 index 0000000..f330b87 --- /dev/null +++ b/docs/superpowers/specs/2026-04-11-detail-panel-7-fixes-design.md @@ -0,0 +1,285 @@ +# Detail Panel 7-Issue Fix Design + +> Date: 2026-04-11 +> Status: Draft +> Approach: Path B — System Bridge (fix bugs + bridge disconnected systems) +> Constraint: No new SaaS endpoints, no new stores, no new middleware (stabilization compliance) + +## Background + +User discovered 7 issues during real usage of ZCLAW desktop app's detail panel (RightPanel). Root cause analysis revealed a pattern: 5 of 7 issues stem from "backend has capability but frontend doesn't consume it" — disconnected systems that need bridging. + +## Issue Summary + +| # | Problem | Severity | Root Cause | Category | +|---|---------|----------|------------|----------| +| 1 | Can't chat after restart | P0 | Heartbeat degrades to 'tauri' in localStorage, restoreSession blindly trusts it | Route race | +| 2 | "About me / How I see you" never updates | P1 | Panel reads agentStore.Clone, memory extractor writes identity files — no bridge | Data disconnect | +| 3 | Memory 311 count but empty list/graph | P0 | memory_search empty query + min_similarity=None → FTS5 returns 0 | Query logic bug | +| 4 | Only 1 reflection, effects unknowable | P1 | History only persists latest (not array); pattern thresholds too high; state restore race | Multi-factor | +| 5 | hand_trigger always rejected | P0 | autonomy-manager.ts:268 maps hand_trigger to null — hardcoded bug | Hardcoded bug | +| 6 | Evolution has data but effects unknowable | P2 | HistoryItem only shows truncated reason, no expandable diff | UX gap | +| 7 | Butler "6 messages" incorrect + empty below | P2 | Shared chat stats header confused for butler data; no pain points detected | UX confusion | + +--- + +## Section 1: P0 Fixes + +### Fix 1 — Chat Routing After Restart + +**Root Cause**: `saasStore.restoreSession()` blindly trusts localStorage connectionMode written by heartbeat degrade. After restart, SaaS auth may still be valid but the persisted 'tauri' mode prevents SaaS relay usage. + +**Solution**: +1. In `restoreSession()` (saasStore.ts ~line 750): Before accepting persisted mode, validate SaaS token: + - Attempt refresh token → access token exchange + - If successful, restore to 'saas' mode regardless of persisted value + - Only accept 'tauri' mode if token refresh fails +2. In `loadConnectionMode()` (saas-session.ts ~line 170): Add staleness check: + - Persist timestamp alongside mode in localStorage + - On load, if persisted mode is 'tauri' and timestamp > 5 minutes old, re-validate +3. Heartbeat degrade writes timestamp for staleness tracking + +**Files**: +- `desktop/src/store/saasStore.ts` — restoreSession() ~line 750 (verify exact location before implementation) +- `desktop/src/lib/saas-session.ts` — loadConnectionMode() ~line 170 +- `desktop/src/store/connectionStore.ts` — connect() routing ~line 428 + +**Risk**: Low. Only changes startup validation logic, doesn't affect active session behavior. + +--- + +### Fix 3 — Memory List/Graph Empty Despite 311 Entries + +**Root Cause**: `memory_search` Tauri command calls `VikingStorage::find()` with empty query string and `min_similarity: None`. FTS5 requires non-empty query to match anything. `memory_stats` works because it sets `min_similarity: Some(0.0)` which triggers table scan path. + +**Solution**: +1. In `memory_commands.rs` `memory_search` (line 164): When query is empty or whitespace-only, set `min_similarity: Some(0.0)` as default fallback +2. This makes the empty-query behavior consistent with `memory_stats` — table scan returns all entries +3. No changes to VikingStorage internals — only the calling parameter + +**Files**: +- `desktop/src-tauri/src/memory_commands.rs` — memory_search at line 148-184 + +**Risk**: Minimal. Single-line parameter change. Performance concern mitigated by existing `limit: 50` default. + +--- + +### Fix 5 — hand_trigger Always Rejected + +**Root Cause**: `autonomy-manager.ts:268` maps `hand_trigger: null` in the actionMap. When configKey is null, `isActionAllowed()` immediately returns false. No `handAutoTrigger` field exists in AutonomyConfig.allowedActions. + +**Solution**: +1. Add `handAutoTrigger: boolean` to `AutonomyConfig.allowedActions` type (autonomy-manager.ts) +2. Change actionMap: `hand_trigger: null` → `hand_trigger: 'handAutoTrigger'` +3. Update default configs: + - `autonomous` level: `handAutoTrigger: true` + - `supervised` level: `handAutoTrigger: false` + - `guided` level: `handAutoTrigger: false` +4. Add toggle control in `AutonomyConfig.tsx` UI + +**Files**: +- `desktop/src/lib/autonomy-manager.ts` — line 268 actionMap + allowedActions type + default configs +- `desktop/src/components/AutonomyConfig.tsx` — add handAutoTrigger toggle + +**Risk**: Low. Pure frontend change. Backend already supports autonomous mode bypassing approval gate. + +--- + +## Section 2: P1 Fixes + +### Fix 2 — "About Me / How I See You" Auto-Update + +**Root Cause**: Panel reads from `agentStore.Clone` static fields. Memory extractor writes to identity files (soul/instructions/user_profile). Backend `UserProfileStore` (Rust) has structured user modeling (industry/role/expertise/communication_style/recent_topics) but no Tauri command exposes it to frontend. + +**Solution (System Bridge — Alternative Approach: Extend Existing Command)**: + +To comply with stabilization constraint "no new Tauri commands" (CLAUDE.md §1.3: "已有 189 个,70 个无前端调用"), we use Option (b): extend an existing command's return payload. + +1. **Extend `agent_get` command**: The existing `agent_get(agent_id)` command returns `AgentConfig`. Extend its return to include an optional `user_profile` field from `UserProfileStore::get()`. + - This avoids creating a new command while exposing existing backend data. + - File: `desktop/src-tauri/src/kernel_commands/agents.rs` — extend return type + +2. **Frontend bridge**: + - Use component-local state (`useState`) in RightPanel for UserProfile data (not Store — per CLAUDE.md §4.2 layered responsibilities) + - On mount and after conversation complete, re-fetch agent data which now includes profile + - "How I See You" section renders from two sources: + - Static: `Clone` fields (userName, workspaceDir, privacyOptIn) + - Dynamic: `UserProfile` fields (industry, role, expertise, communication_style, recent_topics) + - Fallback: if UserProfile empty, show static Clone data only + +3. **Refresh trigger**: In `streamStore` flow completion callback (~line 446), after memory extraction, trigger RightPanel's profile refresh via a lightweight event or re-fetch + +**Files**: +- `desktop/src-tauri/src/kernel_commands/agents.rs` — extend agent_get return type +- `desktop/src-tauri/src/lib.rs` — no change needed (command already registered) +- `desktop/src/components/RightPanel.tsx` — dual-source rendering (lines 523-557) +- `desktop/src/store/chat/streamStore.ts` — add profile refresh trigger ~line 446 + +**Risk**: Medium. Extends existing command rather than creating new one. UserProfileStore already exists. No new SaaS endpoints. No new stores. + +--- + +### Fix 4 — Reflection Only 1 Entry, Effects Unknowable + +**Root Cause**: Multi-factor — history only persists `reflection:latest` (single entry) not `reflection:history` (array); pattern detection thresholds too high (5+ same type); state restore race condition in post_conversation_hook. + +**Solution**: +1. **Fix history persistence** (reflection.rs): + - Ensure `reflect()` appends to `reflection:history:{agent_id}` array (not overwrite) + - Maintain cap at 20 entries (FIFO) + - Always write both `reflection:latest` AND `reflection:history` + +2. **Lower pattern detection thresholds** (reflection.rs `analyze_patterns()`): + - Pattern "many tasks": 5+ → 3+ (rationale: early pattern detection provides timely insights) + - Pattern "many preferences": 5+ → 3+ (rationale: user preferences accumulate quickly in normal use) + - Pattern "many experiences": 5+ → 3+ (rationale: experiences are high-value patterns worth detecting early) + - Pattern "high-visit memories": 3+ → 2+ (rationale: even 2 high-visit memories indicate recurring interest) + - Pattern "low-importance memories": 20+ → 15+ (rationale: conservative reduction from 20 — avoids noisy cleanup suggestions at 10) + +3. **Fix state restore race** (intelligence_hooks.rs): + - Change `post_conversation_hook` state restore from "pop (read + delete)" to "read only" + - Cleanup mechanism: old state is cleaned up on next conversation hook invocation (FIFO with max 1 entry per agent), preventing indefinite accumulation + - Let `getHistory` always find complete data in VikingStorage + +4. **Enhance reflection effect visibility** (ReflectionLog.tsx): + - Each history entry shows: pattern count + summary, improvement suggestion count, identity proposal count + - Timeline indicator: "Triggered after N conversations" + - Expandable detail view for patterns and suggestions + +**Files**: +- `desktop/src-tauri/src/intelligence/reflection.rs` — history persistence + threshold adjustment +- `desktop/src-tauri/src/intelligence_hooks.rs` — state restore race fix +- `desktop/src/components/ReflectionLog.tsx` — enhanced visibility UI + +**Risk**: Low-Medium. Reflection persistence fix is the most impactful change. Threshold changes are conservative (3+ is still meaningful). + +--- + +## Section 3: P2 Fixes + +### Fix 6 — Evolution Effects Unknowable + +**Root Cause**: `HistoryItem` component only renders truncated `reason` text and timestamp. Snapshot data contains full `files` object but UI never renders diff content. `ProposalCard` already has a `DiffView` component that could be reused. + +**Solution**: +1. **Expandable HistoryItem** (IdentityChangeProposal.tsx): + - Add click-to-expand behavior on history items + - When expanded, show which files changed (as tags) + - Show full reason text (no truncation) + - Reuse existing `DiffView` component for before/after comparison + +2. **Evolution summary metadata**: + - Each history item header shows file change count badge + - Color coding: soul changes = purple, instructions = blue, user_profile = green + +**Files**: +- `desktop/src/components/IdentityChangeProposal.tsx` — HistoryItem component (lines 269-304) + +**Risk**: Low. Pure UI enhancement. Reuses existing DiffView component. + +--- + +### Fix 7 — Butler Tab Confusion + Empty Data + +**Root Cause**: "6 messages" is shared chat statistics header displayed across ALL tabs (not butler-specific). Butler tab below is empty because: (a) no pain points detected in short conversations, (b) no proposals generated, (c) Viking memory path empty. + +**Solution**: +1. **Contextual header** (RightPanel.tsx): + - When butler tab is active, replace chat statistics with butler-specific summary + - Show: Butler name + pain points count + proposals count + memory count + - Other tabs keep existing chat statistics + +2. **Enhanced empty state** (ButlerPanel/index.tsx): + - When all sections empty, show friendly onboarding message + - "管家正在了解您,多轮对话后会自动发现您的需求" + - Add manual "analyze now" button that calls `analyze_for_pain_signals` with current conversation history + - Button disabled when conversation < 2 messages (prevents meaningless analysis) + - Show loading state during analysis + - Display "未发现痛点" message when analysis completes with no results + +3. **Lower pain point detection threshold** (pain_aggregator.rs): + - Relax keyword matching patterns + - Allow pain point extraction from shorter conversations (minimum 2 messages instead of 3+) + +**Files**: +- `desktop/src/components/RightPanel.tsx` — conditional header rendering (lines 374-386) +- `desktop/src/components/ButlerPanel/index.tsx` — empty state enhancement +- `desktop/src-tauri/src/intelligence/pain_aggregator.rs` — threshold adjustment + +**Risk**: Low. UX improvements. Pain point threshold change is conservative. + +--- + +## Implementation Priority + +| Batch | Issues | Severity | Est. Complexity | +|-------|--------|----------|-----------------| +| Batch 1 | 1, 3, 5 | P0 | Low (small targeted fixes) | +| Batch 2 | 2, 4 | P1 | Medium (system bridge + persistence fix) | +| Batch 3 | 6, 7 | P2 | Low (UX enhancements) | + +## Testing Plan + +### Automated Tests (after each batch) +- `tsc --noEmit` — TypeScript type check +- `cargo check` — Rust compilation +- `vitest run` — Frontend unit tests + +### Rust Unit Tests (Batch 2) +- `memory_search` empty query behavior: verify `min_similarity` defaults to `Some(0.0)` when query is empty +- Reflection history persistence: verify `reflect()` appends to history array, not overwrites + +### Manual Test Matrix + +**Fix 1 — Chat Routing (must test both modes)**: +- [ ] SaaS mode: restart app with valid SaaS session → should restore to 'saas' mode +- [ ] SaaS mode: restart app with expired SaaS session → should degrade to 'tauri' mode +- [ ] Tauri mode: restart app → should stay in 'tauri' mode +- [ ] Fresh install → should prompt login or default to local mode + +**Fix 2 — Agent Profile**: +- [ ] UserProfile empty → fallback to static Clone data +- [ ] UserProfile populated → dynamic fields shown alongside static +- [ ] After multi-turn conversation → profile fields refresh + +**Fix 3 — Memory Display**: +- [ ] Memory panel loads with entries visible (not just count) +- [ ] Memory graph renders with nodes and edges +- [ ] Pagination works for > 50 entries + +**Fix 5 — Hand Trigger**: +- [ ] Existing autonomy configs migrate correctly (no handAutoTrigger → default false) +- [ ] Autonomous level → hand_trigger auto-allowed +- [ ] Supervised/guided → hand_trigger requires approval + +**Fix 4 — Reflection**: +- [ ] After 3+ conversations → reflection triggers automatically +- [ ] History shows multiple entries (not just 1) +- [ ] Each entry shows pattern/improvement/proposal counts + +**Fix 6 — Evolution**: +- [ ] Click history item → expandable diff view appears +- [ ] Diff view shows before/after for changed files + +**Fix 7 — Butler**: +- [ ] Butler tab shows contextual header (not chat stats) +- [ ] "Analyze now" button disabled when < 2 messages +- [ ] Empty state shows onboarding message + +### Documentation Updates (per CLAUDE.md §8.3) +- Update `docs/TRUTH.md` if command count changes +- Update `wiki/log.md` with change record +- Update `wiki/module-status.md` if module status affected + +## Constraint Compliance + +| Constraint | Status | +|------------|--------| +| No new SaaS API endpoints | OK | +| No new SKILL.md files | OK | +| No new middleware | OK | +| No new stores | OK — using component-local state | +| No new admin pages | OK | +| No new Tauri commands | OK — Fix 2 extends existing `agent_get` command instead | + +Note: Fix 2 originally proposed a new Tauri command but was revised to extend existing `agent_get` to comply with stabilization constraint.