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

286 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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