Files
zclaw_openfang/docs/superpowers/specs/2026-04-11-detail-panel-7-fixes-design.md
iven 449768bee9
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
docs(spec): 详情面板7问题修复设计文档
7个问题根因分析+修复方案:
P0: 聊天路由竞态/记忆查询缺陷/hand_trigger硬编码
P1: Agent画像断链/反思持久化多重缺陷
P2: 演化差异视图/管家Tab上下文混淆
路径B: 系统桥接修复,扩展已有命令而非新增
2026-04-11 10:50:25 +08:00

14 KiB
Raw Permalink Blame History

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: nullhand_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.