The project shipped a black-and-white theme that was only half converted:
the login page looked right, the message page was halfway, and the
settings page had not been started. This finishes the move to the CYBER
reference in prototype_reference/.
Colors now come from a single token source (styles/variables.scss), where
every semantic color is declared twice: once as OKLCH for the CSS, and
once as an RGB triple so translucent variants can be built as
rgba(var(--*-rgb), alpha). Cyan is the system itself, amber means the user
has to intervene, green means the machine is working, and red only ever
means something broke. The RGB triples are derived from the OKLCH values
so the two declarations cannot drift apart.
Pages adopt the .cy-* shell classes from the reference (view, panel,
kpis, tabs, grid, table, field) instead of ad-hoc markup, which is what
makes 19 of 26 views consistent. The remaining seven are deliberately
bespoke: chat streams, the kanban board, the workflow canvas, the
terminal, and the missing-agent screen.
The chart palette and the kanban column colors move into single-source
modules so they cannot drift either. A new guard test rejects raw hex
colors, named colors, and literal RGB triples anywhere in the client
source, with an explicit exemption list for the places that genuinely
cannot read a CSS variable. The existing chat semantics spec keeps the
chat chain honest.
No behavior changes: no logic, no i18n keys, no data structures, and the
/chat-run event contract is untouched.
The terminal was the last surface in the chat tool panel still wearing
someone else's design system, and it had two defects underneath that.
The font stack named no Nerd Font, so the private-use glyphs a default
zsh prompt draws its separators and branch marks from rendered as notdef
boxes. The ~ and the path after it drew fine, which is what made it read
as a font bug rather than a missing-glyph one.
Both components also carried their own copy of the palette -- four themes
in one, fifteen in the other, drifting, defaulting to a near-black that
appears nowhere in the token set. Changing --code-bg did nothing to the
terminal because the terminal never read a token. They now share one
module built from the tokens, so an error in the terminal is the same
red as an error anywhere else.
TerminalView reconnected on a bare setTimeout with no attempt cap and no
error surface, retrying invisibly every three seconds forever against an
unreachable backend. It now shares the panel's cap of three and renders
the same retry affordance, counting down again once a session is created.
Also drops a dead ws.onopen assignment and a 21px padding that was on no
spacing scale, and records what the chat page still owes the prototype.
The token swap made every control dark, but the pages themselves kept
their Arco defaults — bare `padding: 20px` with components laid straight
on the background. That is why the running app still read as "not the
prototype" no matter how many variables were replaced: the prototype's
page structure (panel / panel-h / panel-b / kv / tabs) was never built.
Rather than restate that shell in each of the 25 views, it now lives in
global.scss as .cy-view / .cy-panel / .cy-panel-h / .cy-panel-b /
.cy-kv / .cy-tabs / .cy-tab / .cy-stack / .cy-sec-t, and the settings
components share a global .settings-section + .section-title so all nine
panes pick up the mono section headers at once. The per-component
duplicates of those two rules were deleted.
Settings is converted as the reference case: Arco's a-tabs/a-tab-pane
became the underline tab strip, and the pane body sits in a panel. The
URL-query sync moved to a watcher on activeTab so pane order and deep
links still hold. Pane order is a contract, so it is asserted.
Two tests lock the shell in place — one for the global classes and the
cyan underline selected state, one for the settings structure and pane
order. Without them the next edit easily slides back to bare Arco.
Dark is now the only theme, so every `.dark` selector matched
unconditionally while each non-dark branch was unreachable. That left
fourteen `.dark &` blocks sitting on top of token-based base rules and
silently overriding them with hardcoded grays from the previous palette
(#333333, #999999, #cccccc, #f2f2f2, #1f1f1f, #2a2a2a). Merged each value
down into its base rule and deleted the wrapper, then replaced fifteen
more legacy grays with the CYBER tokens.
Three bugs surfaced along the way:
- FileEditor read `documentElement.classList.contains('dark')`, but the
`dark` class is bound on `.app-root`, so Monaco always loaded the
light `vs` theme inside the dark editor surface.
- CodexLoginModal, CopilotLoginModal and NousLoginModal wrote
`border-color: var(--arcoblue-6, #165dff)` and VoiceApiFormModal wrote
`color: var(--primary-6, #18a058)`. Those Arco variables hold bare
RGB triplets, not colors, so each declaration was invalid and
discarded — the hover border and link color stayed stock Arco
blue/green instead of the cyan accent.
- JourneyView drew the pinned-node ring in amber, a leftover from the
old palette.
Rendering-layer only: no store logic, no /chat-run payload, no message
rendering path.
The canvas renderer branched on isDark for every color, but isDark has
been a constant true since the theme switch was removed, so half of each
ternary was unreachable. Resolve the values directly and shift the
hues from the old pale blue to the accent cyan, keeping alpha as the only
thing that varies between idle and focused edges.
useTheme is no longer referenced here and is dropped from the imports.
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
- Vendor ArkScope decorative layers (ScrambleTitle / IsometricWave) with provenance notes; decorative-only per AGENTS.md red lines
- Migrate theme tokens to Arco, register the Arco theme, and add hook adapter composables (useMessage / useDialog / useNotification) with matching signatures
- Sample pages: LoginView and AppSidebar on the new baseline; docs/arco-mapping.md and docs/ui-baseline.md land
- agent-bridge: prefer the PM-selected venv from facts.json over legacy in-tree venvs (legacy lacks the mcp extra, so every MCP server showed 0 tools / not connected)
- agent-bridge python (vendored, deliberate deviation): MCP discovery import fallback and pyyaml -> hermes_yaml so plugins and MCP work under the PM env; install the mcp extra via `hermes pm install --extra mcp`
- Chat-chain fragment: docs/chat-chain-changes/2026-10-17-phase005-arkscope-baseline.md
Verified: harness:check, test (2020 passed), test:e2e (30 passed), build all green; manual: plugins 100/0 warnings, MCP 6/6 servers connected with 104 tools.
- Drop all desktop/Electron branches, scripts, and UI (title bar, pets,
desktop bridge); LAN endpoint kind is now web/custom only
- Replace the bundled hermes CLI with user-side installation: add
GET /api/hermes/cli-status plus a HermesMissingView onboarding guide
- Remove the version-management UI (snapshots handled by git) and prune
dead website/mcu/claude scripts and lockfile entries
Every chat session now runs through the Hermes bridge, so the three chat
chains become feature-equivalent instead of three partially-maintained
dispatch paths. Keeping the coding-agent and in-house agent runtimes alive
meant every bridge change had to be mirrored into two more runtimes that
had already drifted.
Historical rows are read, not migrated: sessions stored with the retired
source normalize to cli on load, workflow graphs naming the retired agents
keep loading and run on the bridge, and the visible-source check plus
SOURCE_LABELS still recognize the old value so those sessions stay listed.
The public surface follows the code: the OpenAPI generator and the MCP
chat-run tool schema drop the six retired entry fields so external callers
are not told about runtimes that no longer exist.
harness-check gains a RETIRED_RUNTIME scan so the residual check is a
mechanical gate instead of a manual grep, with the two surviving read-side
compat sites recorded with reasons.
Refs: docs/phases/phase_003.md
Removes the ekko / hermes-web-ui branding from the whole tree and drops
eight locale files, so the project presents one name and one language pair.
Data compatibility is deliberate, not accidental: DB filenames, the JWT
audience, and skill manifest ownership all still recognise their pre-rename
values so existing deployments under ~/.hermes-web-ui keep working without a
re-login or a re-injection.
scripts/harness-check.mjs now scans for the legacy strings and fails the
build on any new occurrence, with an explicit allow-list (and reasons) for
the compatibility paths that must keep them.
Import the client, server, skills, tests, and build scripts as the starting
point for the rebuild, then trim the surfaces this project does not ship:
Electron desktop packaging, the marketing website, and the ESP32 firmware
tree. CI keeps the web build, e2e, lockfile, release, and Docker workflows.
Rename the package and CLI entry points to hermes-ui, drop the prepare hook so
npm ci no longer triggers a build, and drop the removed tsconfig and nodemon
references. Rewrite AGENTS.md, ARCHITECTURE.md, DEVELOPMENT.md, and the READMEs
so the documented package map and hard rules match this repository.
reference_code/ is ignored: it is a read-only snapshot, not an input to the build.