Files
hermes-ui/docs/chat-chain-changes/2026-10-18-phase007-arco-full-migration.md
toor 836d0228d2 Phase 007: migrate every remaining view from naive-ui to Arco Design Vue
The project now has exactly one UI library. Every naive-ui component in
packages/client/src is replaced by its Arco equivalent across 24 views and
roughly 80 components, and naive-ui is dropped from package.json along with
its transitive vueuc / @css-render / vdirs / seemly dependencies.

The migration is rendering-layer only. No store logic, no API contract, and
no /chat-run payload was touched.

Three classes of leftover were invisible to `grep naive-ui` and are now
covered by tests/client/no-naive-ui.test.ts, which asserts all three:
  - dead .n-* class names in style blocks
  - dead --n-* custom properties
  - naive-ui in package.json

Notable behavioural traps handled along the way, recorded in
docs/arco-migration-rules.md:
  - a-input-password defaults to masked; passing default-visibility="false"
    would have exposed API keys in clear text
  - a-upload fires a real XHR unless auto-upload is false
  - nested Trigger components fight over the first element child, so the
    group-chat settings dropdown was flattened
  - Arco renders tooltip default-slot text inline, which was silently
    collapsing the chat and group-chat toolbars
2026-10-18 22:57:25 +08:00

4.1 KiB

date, pr, feature, impact
date pr feature impact
2026-10-18 pending Phase 007 - full naive-ui to Arco Design Vue migration across all remaining views Rendering-layer only. Every naive-ui component in packages/client/src is replaced by its Arco Design Vue equivalent across 24 views and roughly 80 components, and the naive-ui dependency is removed from package.json and package-lock.json. No chat store logic, no /chat-run payload, and no message rendering path is touched. Three chat-chain files changed as a consequence of that rendering-only sweep, listed below with the specific reason each needed a change.

Phase 007 — full Arco migration

Phase 006 moved the chat page. Phase 007 finished the job: grep -rn "naive-ui\|vueuc\|@css-render" packages/client/src is now empty, and the project has exactly one UI library.

Batches 7a-7g each shipped independently per docs/phases/phase_007.md. docs/arco-migration-rules.md holds the component mapping table the whole sweep followed.

Why chat-chain files changed

Four files under the chat chain were caught by the sweep. None of the changes touch logic.

chat/ChatPanel.vue

  • Three leftover naive CSS custom properties were still referenced: --n-text-color-3 (twice) and --n-color-hover (once). These were dead after Phase 006 — the variables no longer resolve, so the rules silently did nothing. Rewritten to the project's own tokens (--text-muted, --bg-card-hover).
  • :native-scrollbar="false" on the workspace dropdown had no Arco counterpart and was dropped.

group-chat/GroupChatInput.vue

  • Eighteen dead naive rules under .tool-trace-toggle, .input-settings-button and .send-button targeted .n-button__content, .n-button__icon, .n-button__border, .n-button__state-border, .n-button__ripple and .n-base-wave. Arco's a-button has no content wrapper and none of the ripple internals, so gap now lives on the button itself and the ripple rules were deleted rather than renamed.
  • The settings button was NDropdown > NTooltip > NButton — a three-deep Trigger nest. Arco's Trigger components each claim the first element child via mergeFirstChild, so nested popups fight over it and the menu never opens. Flattened to a-dropdown > a-button with the description moved to a native :title, which is the same approach Phase 006 already took in ChatInput.vue.
  • The attach tooltip carried its label as trailing default-slot text. Arco renders the default slot inline, so that text became a zero-width flex item in the toolbar. Moved to the content prop.

group-chat/CreateRoomForm.vue

  • Straight component swap. InputLikeInstance = { focus: () => void } was kept because Arco's a-input still exposes focus() (es/input/input.js:315), so the existing nextTick focus calls still work.

group-chat/GroupChatPanel.vue

  • The room context menu was NDropdown in trigger="manual" mode driven by :x / :y / :show / @clickoutside. Arco has no manual trigger, so it became a-trigger trigger="contextMenu" with :popup-style for coordinates, matching the pattern already used by FileContextMenu.vue and WorkflowView.vue. Arco's clickOutsideToClose defaults to true, which replaces the hand-written clickoutside handler.
  • NPopover's placement="bottom-end" maps to Arco position="br" (bottom-right). Arco's a-popover has no width prop and its Trigger sets inheritAttrs: false, so a :width would be dropped silently; the width moved to :content-style.

Verification

packages/client/src has zero naive references by three independent checks — naive-ui imports, .n-* class names in style blocks, and --n-* custom properties. The last two matter because grep naive-ui cannot see them; tests/client/no-naive-ui.test.ts now guards all three so the patterns cannot come back.

Behavior impact

Rendering-layer only. No chat store logic, no /chat-run payload, and no message rendering path was touched. The group-chat message bubbles already used plain HTML and were left untouched, so the "no Arco components inside bubbles" rule still holds — a-tree, a-select and the other components appear only outside the bubble.