Files
hermes-desktop/docs/chat-chain-changes/2026-10-19-chat-cyber-regression-guard.md
toor 01d9e301fc
Build / build (push) Canceled after 0s
Playwright / browser (push) Canceled after 0s
initialize Electron desktop app and remove unused files
2026-10-25 01:09:01 +08:00

5.5 KiB

date, pr, feature, impact
date pr feature impact
2026-10-19 pending Regression guards for the chat CYBER treatment, plus a real fix for the ModelSelector provider jump Adds a Playwright spec that fails if `live-ring` is duplicated, if a multi-hue cycle returns, if the send key is flattened back to a grey block, or if either new breakpoint is lost; and removes a side effect from `providerOptions` that was silently resetting the user's provider choice. No `/chat-run` payload, no store logic, no markup semantics changed.

Chat CYBER regression guards

The CYBER pass in 2026-10-19-chat-cyber-skeleton-pass.md was rendering-layer only, which meant nothing in the test suite could see it. Two of its three deliberate decisions were structural rather than visual, and structural regressions are exactly what code review misses:

  1. live-ring must have one home. It had three copies of rainbow-glow before, and a copy is easy to paste back. The test scans the source tree instead of the DOM — a duplicate keyframe is invisible to any runtime assertion, because the last definition silently wins.
  2. The send key must not be flattened. border: 0 !important plus box-shadow: none !important had erased the global neon tube, and the disabled state was a hardcoded #9f9f9f outside the palette. The test asserts the computed shadow is present and that the border is not that grey.

The same spec also pins the computed values that carry meaning: the LED's dimensions and its breathe animation, the stop button's amber border (stopping is a "needs you" action), the header's radial wash, the Outline overlay at ≤1180px, and the workspace badge hiding at ≤900px.

The live-ring test also asserts the keyframe body contains no raw hex. The original cycle was #ff6b6b → #feca57 → #48dbfb → #ff9ff3, so any hex literal in that block means a multi-hue cycle has crept back in.

ModelSelector provider jump

providerOptions assigned to customProvider inside its computed getter. Any recomputation — a model list refresh, a profile switch — overwrote whatever the user had just picked in the dropdown, which is the "provider 选择自己跳" symptom in known-issues.md #1.

The getter is now a pure read and the synchronisation moved to watch(() => appStore.selectedProvider, ...). openModal() keeps its original assignment because watch is lazy and does not fire on the first render.

Covered by tests/client/model-selector-provider-sync.test.ts, which also asserts the watch is anchored to selectedProvider rather than to providerOptions — anchoring it to the computed would just relocate the same side effect.

known-issues.md #2 (ProfileSelector i18n keys) was investigated and found to be a misreport: t() returns translated text, all 33 keys used by that component exist in both locales, and no call site anywhere passes a bare key to message.*. That entry is marked as a misreport rather than left to be "fixed" into a real bug.

A second flattening site, found by the guard

Writing the source-level scan immediately paid for itself: it flagged group-chat/GroupChatInput.vue, which carried the identical border: 0 !important + box-shadow: none !important pair and the same hardcoded #9f9f9f disabled block. The CYBER pass had only fixed the chat/ twin, so the group chat input was still a flat grey block. Fixed the same way — the overrides are gone and the disabled state falls back to the global .arco-btn-disabled rule, which extinguishes the tube rather than painting it grey.

Verification

npx vitest run tests/client tests/shared     109 files, 662 tests passed
npm run test:e2e                             46 passed (was 35)
npm run harness:check                        passed
npm run build                                passed

The e2e count moved from 35 to 46: eleven new cases in chat-cyber-semantics.spec.ts.

Four of those eleven failed on the first run and each failure was a wrong assumption in the test rather than a product bug, which is worth recording because two of them would have quietly become false-passing tests:

  • Asserting the send key's tube in its empty state. Empty input is the disabled state, and global.scss deliberately sets box-shadow: none there — the tube going dark is the intended "nothing to send" signal. The assertion now checks the dark disabled look first, then the lit tube after typing.
  • Reaching the stop state by clicking send. The mocked socket in fixtures.ts records emits but never acknowledges a run, so isStreaming never became true and the button stayed in its lit state. The test now triggers run.started through __PW_CHAT_SOCKET__, the same way chat-streaming.spec.ts does.
  • Comparing rendered colours against the palette. This cannot work here and the errors explain why: the palette declares each hue twice — an OKLCH value (--success: oklch(0.860 0.190 128)) and an RGB triple (--success-rgb: 107, 247, 84) — and the two forms sit about 30° apart, so a correct colour reports as a mismatch. Colour semantics are now pinned in the source-level tests, which can see which token a rule references. The browser tests assert what rendering can answer unambiguously: which class is active, which keyframe runs, whether a glow exists, and that nothing has been flattened to grey.
  • Looking for the workspace badge after a send. The badge is behind v-if="chatStore.activeSession?.workspace", and the default mock returns no sessions, so it never rendered. The fixture now supplies a session with a workspace.