Swaps the rendering layer only. Chat store logic, keyboard handling, and the /chat-run Socket.IO contract are untouched, so a mixed UI cannot hide a chat regression behind a library swap. Arco's dropdowns take their items from a #content slot of a-doption children rather than a droplist prop, and Arco Triggers cannot be nested, so the two toolbar menus lost their a-tooltip in favour of a native title. Both rules are recorded in docs/arco-mapping.md, and tests/e2e/chat-input-dropdown.spec.ts plus tests/e2e/chat-rendering-smoke.spec.ts keep the menus, the streaming markdown rendering, and the no-Arco-inside-the-bubble red line from regressing silently.
4.6 KiB
date, pr, feature, impact
| date | pr | feature | impact |
|---|---|---|---|
| 2026-10-17 | pending | Phase 006 - chat page naive-ui to Arco Design Vue rendering migration | No chat-chain behavior change; chat components only swap naive-ui components for Arco equivalents. Chat run flow, chat store logic, keyboard handling, and the /chat-run Socket.IO contract are untouched. |
Phase 006 — Chat page Arco migration
Phase: docs/phases/phase_006.md
What Changed
Rendering-layer only, across packages/client/src/components/hermes/chat/:
ChatInput.vue—NPopselectreasoning-effort picker andNDropdownsettings menu becamea-dropdownwhose#contentslot declaresa-doptionitems;NModalbecamea-modal(:footer="false"for informational dialogs, custom#footerfor confirm dialogs);NInputNumberbecamea-input-number hide-button. The two menu buttons use a native:titleinstead ofa-tooltipbecause Arco Triggers cannot be nested.useKeyboardwiring and the textarea height auto-resize logic were not touched.ChatPanel.vue— session context menu moved fromNDropdown trigger="manual"toa-dropdown trigger="contextMenu" align-pointwrapping.session-items(the three-level export submenu became nesteda-dsubmenu/a-doption); 4 dialogs, the new-chat drawer, 5NSelect, 6NTooltip, and theNPopconfirmconfirmations became Arco equivalents.MarkdownRenderer.vue— the text previewNDrawer/NDrawerContentpair became a singlea-drawer, and it is now lazily mounted behindv-if="textPreviewVisible". The component's public interface is unchanged because it is reused by 8 call sites (including Phase 007's GroupMessageItem).MessageItem.vue— the tool-changeNDrawer/NDrawerContentbecame a singlea-drawer. It uses:visible+@update:visiblerather thanv-model:visibleso the existing "refuse to close with unsaved changes" guard still runs.MessageList.vue— approval / clarification overlay buttons and inputs.FolderPicker.vue— folder tree context menu moved toa-trigger trigger="contextMenu" align-pointwith an#contentslot;NModal preset="dialog"becamea-modalwith a custom#footer.SessionListItem.vue,SessionSearchModal.vue,TerminalPanel.vue,FilesPanel.vue— remaining buttons, checkboxes, tooltips, popconfirms, selects, and spinners.
VirtualMessageList.vue already had no NScrollbar and keeps
vue-virtual-scroller as the sole scroll owner (recorded as an exception in
docs/arco-mapping.md).
stores/hermes/chat.ts, stores/hermes/files.ts, and ChatView.vue needed no
changes.
Contract Check
/chat-run event names, field names, field types, and emission timing are
unchanged. No file under packages/server/, services/hermes/group-chat/,
lib/context-compressor/, or services/hermes/context-engine/ was modified,
so no bridge, compressor, or context-engine behavior changed.
The §1 red line (no Arco components inside the message bubble) is enforced by
tests/client/message-item-arko.test.ts, which registers the real Arco plugin
and asserts that no .arco-* element appears in the rendered bubble for
assistant, user, and tool messages, including across streaming re-renders.
Verification
grep -cE "from ['\"]naive-ui['\"]" packages/client/src/components/hermes/chat/*.vue \
packages/client/src/views/hermes/ChatView.vue \
packages/client/src/stores/hermes/chat.ts
npm run harness:check
npm run test
npm run test:e2e
npm run build
Result: grep 0 across all chat files; 274 test files / 2026 tests passing; 35 e2e tests passing; harness check and build clean.
Follow-up Fix
Arco's a-dropdown has no droplist prop — its menu content must be declared
in the #content slot with a-doption / a-dsubmenu children. The first
migration pass used :droplist, which Arco silently ignored (unknown props
fall through as plain attributes), so all three dropdowns opened an empty
popup. Fixed by declaring the options in the #content slot, and locked down
by tests/e2e/chat-input-dropdown.spec.ts, which asserts the reasoning
effort menu (7 options, selection applied), the input settings menu
(2 options, toggle applied), and the session context menu plus its export
submenu.
The §9.2 manual checklist rows that need a live run are covered by
tests/e2e/chat-rendering-smoke.spec.ts: streamed deltas rendering bold text,
inline code, a highlighted code block, KaTeX math, and a Mermaid diagram; the
Stop button appearing on run start and clearing on run.completed; the
"no Arco inside the bubble" red line; and the session model dialog plus session
search opening from the chat UI.