- Move side panel toggle from floating button to chat header right side
(Trae Solo style) via new PanelToggleButton component
- Add px-6 py-4 padding to message list container
- Add mb-5 gap between messages for readable vertical spacing
BUG-012: Reposition side panel toggle button (top-[52px]→top-20) to
avoid overlap with header buttons in ResizableChatLayout.
BUG-013: Install @tailwindcss/typography plugin and import in index.css
to enable prose-* Markdown rendering classes in StreamingText.
BUG-007: Rewrite authStore tests to match HttpOnly cookie auth model
(login takes 1 arg, no token/refreshToken in state). Rewrite request
interceptor tests for cookie-based auth. Update bug-tracker status.
BUG-009 (P1): Add frontend DataMasking in saas-relay-client.ts
- Masks ID cards, phones, emails, money, company names before relay
- Unmasks tokens in AI response so user sees original data
- Mirrors Rust DataMasking middleware patterns
BUG-010 (P3): Send button transforms to Stop during streaming
- Shows square icon when isStreaming, calls cancelStream()
- Normal arrow icon when idle, calls handleSend()
BUG-011 (P2): Add ::timestamptz casts for old TEXT timestamp columns
- account/handlers.rs: dashboard stats query
- telemetry/service.rs: reported_at comparisons
- workers/aggregate_usage.rs: usage aggregation query
SaaS Relay was sending only the current message without conversation
history, giving LLM no context from previous turns. Root cause:
streamStore passed only `content` string to chatStream(), and
saas-relay-client hard-coded a single-element messages array.
Fix:
- GatewayClient.chatStream() opts: add `history` field
- streamStore: extract last 20 messages as history before calling chatStream
- saas-relay-client: build messages array from history + current message
- saasStore.ts: replace require('./chat/conversationStore') with await import()
to fix ReferenceError in Vite ESM environment (P1)
- main.rs: fix health check pool usage formula from max_connections - num_idle
to pool.size() - num_idle, preventing false "degraded" status (P1)
- error.rs: show detailed error messages in ZCLAW_SAAS_DEV=true mode
- Update bug tracker with BUG-003 through BUG-007
Addresses findings from deep code audit:
H-1: Pass abortController.signal to saasClient.chatCompletion() so
user-cancelled streams actually abort the HTTP connection (was only
stopping the read loop, leaving server-side SSE connection open).
H-2: ModelSelector now shows only when (!isTauriRuntime() || isLoggedIn).
Prevents decorative model list in Tauri local kernel mode where model
selection has no effect (violates CLAUDE.md §5.2).
M-1: Normalize CRLF to LF before SSE event boundary parsing (\n\n).
Prevents buffer overflow when behind nginx/CDN with CRLF line endings.
M-2: SQL window_minute comparison uses to_char(NOW()-interval, format)
instead of (NOW()-interval)::TEXT, matching the stored format exactly.
M-3: sort_candidates_by_quota uses same sliding 60s window as select_best_key.
LOW: Fix misleading invalidate_cache doc comment.
P1: onError callback now sets content to error message instead of empty string.
Previously API errors (404/429) produced empty assistant messages with only
a visual error badge — now the error text is persisted in message content.
P3: Retry button now re-sends the preceding user message via sendToGateway
instead of copying to input. Works for both virtualized and non-virtualized
message lists. Removed unused setInput prop from MessageBubble.
Also hides model selector in Tauri runtime (SaaS token pool routes models).
Root cause: conversationStore hardcoded 'glm-4-flash' as default model,
which may not exist in SaaS admin config, causing 404 on all chat requests.
- conversationStore: default currentModel to empty string (runtime-resolved)
- saasStore: after fetching available models, auto-switch currentModel
to first available if the stored model is not in the list
- SaaS relay getModel() already had fallback to first available model
Model selector was cosmetic-only in desktop mode: chatStream never passes
model param to backend. Hiding prevents user confusion and 404 errors when
selecting models not in SaaS token pool.
Also adds E2E test report covering 168 messages, 4 bugs found (P0 fixed).
Adapt ChatArea for compact/butler mode:
- Add onOpenDetail prop for expanding to full view
- Remove inline export dialog (moved to detail view)
- Replace SquarePen with ClipboardList icon
Add SimpleSidebar component for butler simple mode:
- Two tabs: 对话 / 行业资讯
- Quick suggestion buttons
- Minimal navigation
RightPanel refactoring for dual-mode support:
- Detect simple vs professional mode
- Conditional rendering based on butler mode state
- Add ?mode=rwc to pain.db SQLite URL so it creates the file on first run
- Move useUIModeStore hook before conditional returns in App.tsx to fix
React "Rendered more hooks than during the previous render" error
- Call init_pain_storage() in Tauri .setup() so pain persistence activates on boot
- Integrate useColdStart hook into FirstConversationPrompt for auto-greeting
- Add UI mode toggle section to Settings/General (already had imports)
- Add "简洁" mode switch-back button to TopBar in professional layout
- Update SemanticSkillRouter @reserved annotation to reflect active status
The TopBar had a gradient badge with 'Z' letter followed by the title
'ZCLAW', creating visual 'ZZCLAW'. Replaced badge with a solid gradient
square as a brand indicator without the duplicate letter.
The framer-motion AnimatePresence with mode="wait" caused the sidebar
content to get stuck on the conversations list when switching to the
agents tab. The React state updated correctly but the DOM did not
re-render. Replaced with simple conditional rendering which is more
reliable and removes the framer-motion dependency from this component.
P1: identity.rs get_identity() returns empty soul/instructions for agents
without explicit identity files. This prevents the default ZCLAW personality
from overriding agent_config.system_prompt. New get_identity_or_default()
method added for the DEFAULT agent.
P2: messaging.rs now uses agent_config.model.model when available, falling
back to global Kernel config. This allows per-agent model selection.
P2: agentStore.ts loadClones retries up to 3 times (300ms interval) when
getClient() returns null, handling the coordinator initialization race.
P2: agent_create Tauri command auto-populates identity files (soul +
instructions) from creation parameters, ensuring build_system_prompt()
has content for new agents.
Also fixes conversationStore upsertActiveConversation to persist generated
conversation IDs, preventing duplicate entries on new conversations.
- nextStep() was allowing currentStep to reach steps.length (6), past
the last step index (5), showing "步骤 7/6:" with empty content area
- On the last step, nextStep now triggers handleSubmit() directly
instead of navigating to a phantom step 6
- Footer button condition changed: "完成" shows on last step instead
of after it, keeping error/success messages visible
- Added error logging in catch block (was silently swallowing errors)
Root cause: Each LLM delta (text/thinking) triggered a synchronous
setState via updateMessages → chatStore.setState. With Kimi thinking
model emitting many deltas per frame, this caused a React render storm
that hit the maximum update depth limit.
Fix (two-layer approach):
1. streamStore: Buffer text/thinking deltas locally and flush to store
via setTimeout(0), batching multiple deltas per frame
2. chatStore: Microtask batching in injectChatStore.updateMessages to
coalesce rapid successive updates
Verified: 2-round conversation (4 messages) with Kimi thinking model
completes without crash. Previously crashed 100% on 2nd message.
h-full = 100% of parent height, but TopBar already occupies 56px above.
This caused ChatArea to overflow by 56px, pushing the input box below
the visible viewport. flex-1 + min-h-0 correctly fills remaining space
in the flex column layout.
The input area at the bottom of the chat panel lacked flex-shrink-0,
causing the flex column layout to compress it when message content filled
the Conversation area. This made the textarea only partially visible and
the scrollbar unable to reach the bottom of the input area.
HIGH fixes:
- H-NS-1: Non-streaming agent_chat now builds ChatModeConfig and calls
send_message_with_chat_mode(), matching the streaming path
- H-FE-1: ClarificationCard component renders structured clarification
questions with type badge, question text, and numbered options
- H-SEM-1: SemanticSkillRouter annotated as @reserved (Phase 3 wiring)
MEDIUM fixes:
- M-SETTINGS-1: Settings menu restructured with "高级" section separator;
skills/audit/tasks/heartbeat/semantic-memory grouped under advanced
- M-MAN-1: LoopEvent→StreamChatEvent mapping completeness checklist
added as documentation comment in agent_chat_stream loop
- M-ORPH-1: Deleted orphaned Automation/ and SkillMarket/ files,
plus transitively orphaned types, hooks, and adapters
CRITICAL:
- ask_clarification now terminates Agent Loop in both run() and run_streaming()
paths, preventing the LLM from continuing after requesting user clarification
HIGH:
- SaaS relay now forwards plan_mode and subagent_enabled to backend
- GatewayClient.chatStream now supports onThinkingDelta, onSubtaskStatus,
and token-bearing onComplete — aligned with kernel-types StreamCallbacks
- ZclawStreamEvent type extended with thinking_delta, subtask_status variants
and input_tokens/output_tokens fields for token tracking via Gateway path
The connectZclawStream call in the synchronous code path (common case
when agentId is already known) was missing the subagent_enabled field,
causing Gateway-connected clients to never send the flag to the server.
When the agent writes files via the file_write tool, artifacts are now
automatically created in the artifact panel. The file extension determines
the artifact type (code/markdown/text) and syntax highlighting language.
This connects the existing ArtifactPanel UI to actual tool output data.
Phase 1.0 — Butler Mode UI:
- Hide "自动化" and "技能市场" entries from sidebar navigation
- Remove AutomationPanel and SkillMarket view rendering from App.tsx
- Simplify MainViewType to only 'chat'
- Main interface is now: chat + conversation list + detail panel only
Phase 1.1 — Mode Differentiation:
- Add subagent_enabled field to ChatModeConfig (Rust), StreamChatRequest (Tauri),
gateway-client, kernel-client, saas-relay-client, and streamStore
- TaskTool is now only registered when subagent_enabled=true (Ultra mode)
- System prompt includes sub-agent delegation instructions only in Ultra mode
- Frontend transmits subagent_enabled from ChatMode config through the full stack
This connects the 4-tier mode selector (Flash/Thinking/Pro/Ultra) to actual
backend behavioral differences — Ultra mode now truly enables sub-agent delegation.
Previously hardcoded to false in Tauri bridge. Now checks whether
kernel provided a driver before building classroom, correctly flagging
placeholder content when LLM is unavailable.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace inline SVG whiteboard rendering in SceneRenderer with the
dedicated WhiteboardCanvas component, gaining chart/latex support
and eliminating 27 lines of duplicated rendering logic.
- P2-18: TOTP QR code local generation via qrcode lib (no external service)
- P2-21: Suspend foreign LLM providers (OpenAI/Anthropic/Gemini) for early stage
- P3-04: get_progress() now calculates actual percentage from completed/total steps
- P3-05: saveSaaSSession calls now have .catch() error logging
- P3-06: SaaS relay chatStream passes session_key/agent_id to backend
- P3-02: Whiteboard unification plan document created
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1. Fix sidebar tab switching: replace containerVariants (staggerChildren
without motion children) with simple fade variants. The previous
staggerChildren:0.05 caused the container to stay at opacity:0 when
switching to CloneManager because non-motion children couldn't
participate in stagger animation.
2. Fix memory deduplication: add content+agentId based dedup check
in fallbackMemory.store(). Previously same content was stored 4x
with different IDs. Now updates importance/accessCount instead.
The tauri-plugin-mcp was registered in Rust but the frontend never called
setupPluginListeners(), causing all DOM-related MCP operations (execute_js,
query_page, type_text) to timeout. This fix enables proper dev debugging
via tauri-mcp tools.
Register 15 structured tools via navigator.modelContext (Chrome 146+)
for direct state queries without DOM scraping. Reduces token consumption
~67% vs DevTools MCP snapshot-based debugging. Dev mode only.
Tools: get_zclaw_state, check_connection, send_message, cancel_stream,
get_streaming_state, list_conversations, get_current_conversation,
switch_conversation, get_token_usage, get_offline_queue,
get_saas_account, get_available_models, get_current_agent,
list_agents, get_console_errors
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Unify ConnectionState: kernel-types.ts now canonical source
with 'handshaking', gateway-types.ts re-exports
- PromptTemplateInfo source/status → union literals
- PromptVariable.type → union literal
- CreateRoleRequest id/permissions → optional
- PropertyPanel: replace 13 as any with typed accessor pattern
- chatStore: window cast via as unknown as Record
- index.ts: add _storesInitialized guard to prevent triple initialization
- offlineStore.ts: add isRetrying mutex for retryAllMessages concurrency
- PropertyPanel.tsx: replace 13x (data as any) with typed d accessor
- chatStore.ts: replace window as any with Record<string, unknown>
- kernel-*.ts: replace prototype as any with Record<string, unknown>
- gateway-heartbeat.ts: delete dead code (9 as any, zero imports)