105 lines
5.5 KiB
Markdown
105 lines
5.5 KiB
Markdown
---
|
|
date: 2026-10-19
|
|
pr: pending
|
|
feature: Regression guards for the chat CYBER treatment, plus a real fix for the ModelSelector provider jump
|
|
impact: 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.
|