# 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.