Files
hermes-ui/docs/chat-chain-changes/2026-10-17-phase006-chat-arco.md
toor effc29085c Phase 006: migrate the chat page from naive-ui to Arco Design Vue
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.
2026-10-17 22:17:32 +08:00

97 lines
4.6 KiB
Markdown

---
date: 2026-10-17
pr: pending
feature: Phase 006 - chat page naive-ui to Arco Design Vue rendering migration
impact: 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` — `NPopselect` reasoning-effort picker and `NDropdown`
settings menu became `a-dropdown` whose `#content` slot declares `a-doption`
items; `NModal` became `a-modal` (`:footer="false"` for informational
dialogs, custom `#footer` for confirm dialogs); `NInputNumber` became
`a-input-number hide-button`. The two menu buttons use a native `:title`
instead of `a-tooltip` because Arco Triggers cannot be nested.
`useKeyboard` wiring and the textarea height auto-resize logic were not
touched.
- `ChatPanel.vue` — session context menu moved from `NDropdown trigger="manual"`
to `a-dropdown trigger="contextMenu" align-point` wrapping `.session-items`
(the three-level export submenu became nested `a-dsubmenu` / `a-doption`);
4 dialogs, the new-chat drawer, 5 `NSelect`, 6 `NTooltip`, and the
`NPopconfirm` confirmations became Arco equivalents.
- `MarkdownRenderer.vue` — the text preview `NDrawer`/`NDrawerContent` pair
became a single `a-drawer`, and it is now lazily mounted behind
`v-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-change `NDrawer`/`NDrawerContent` became a
single `a-drawer`. It uses `:visible` + `@update:visible` rather than
`v-model:visible` so 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 to
`a-trigger trigger="contextMenu" align-point` with an `#content` slot;
`NModal preset="dialog"` became `a-modal` with 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
```bash
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.