Minimal-color round 1 (owner): phase color retired — the braille spinner is the one working indicator
Nothing exists purely for decoration: phase color-coding is retired app-wide (phases only advance — every session ends Teaching — so phase-as-signal was decoration with a one-time function), and what state genuinely deserved — 'a turn is in flight' — is now carried by MOTION alone, in the terminal's own glyph language. - BrailleSpinner (components/braille-spinner.tsx): the canonical cli-spinner sequence (⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏) as exact dot bitmaps in a 2×4 braille cell, 80ms/frame (800ms cycle). Pure CSS: one shared keyframes, frames staggered via animation-delay; no JS timer, no re-render per frame — cheaper than the old breathing dot. Static contexts (SSR, prefers-reduced-motion) show frame 0 — a legible glyph, not a blank. - Sidebar rows: spinner ONLY while a teach turn is in flight; idle and question-pending render NOTHING (a resting state has no scan value — the meta line's 'Waiting for your answer' text carries it). The three-state phase dot (pulse/solid/hollow) and PHASE_FILL/RING maps are gone. - Top bar PhaseChip: label-only pill; the old dot slot becomes the spinner's home while a turn is in flight (busy prop wired from ChatView's busy = localStream || turnInFlight). Transcript PhaseDividers drop phase colors for monochrome rules + labels. - API narrowing (app/api/chats/route.ts + ChatListItem contract): busy = isTurnRunning ONLY — background jobs no longer count (the sidebar's Background agents section is their channel; a per-row spinner would double-signal them). - globals.css: --phase-* tokens + lh-dot-breathe removed (dead after the sweep — no remaining users). Live-verified: idle/pending rows render no indicator, /chat/new chip is label-only, 10 CSSAnimations at 800ms with correct 80ms stagger, static frame 0 under reduced motion. Gates: typecheck, lint, 470/470, build. Lane crossings to flag at merge: app/api/chats/route.ts + the ChatListItem contract comment (src/core) — the busy-semantics narrowing; components/chat.tsx one-line busy prop.
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
/**
|
||||
* Phase presentation render tests — the in-session face of the picker's
|
||||
* color strips (components/phase-divider.tsx): the header chip and the
|
||||
* dotted transcript boundary rules. Same tsx classic-JSX shim as
|
||||
* render-cards.test.tsx. (Replaces the old part-level PlanDivider tests.)
|
||||
* Phase presentation render tests — the in-session face (components/
|
||||
* phase-divider.tsx), monochrome since the minimal-color round: the header
|
||||
* chip (label + the braille spinner in the old dot slot while a turn is in
|
||||
* flight) and the dotted transcript boundary rules. Same tsx classic-JSX
|
||||
* shim as render-cards.test.tsx. (Replaces the old part-level PlanDivider
|
||||
* tests.)
|
||||
*/
|
||||
import React from "react";
|
||||
(globalThis as Record<string, unknown>).React = React;
|
||||
@@ -13,7 +15,7 @@ import { renderToStaticMarkup } from "react-dom/server";
|
||||
import { PhaseChip, PhaseDivider, planNodeCount } from "../components/phase-divider.tsx";
|
||||
|
||||
describe("PhaseChip (header)", () => {
|
||||
it("renders a phase-colored dot + the learner-facing label", () => {
|
||||
it("renders the learner-facing label — no colored dot (phase color retired)", () => {
|
||||
for (const [phase, label] of [
|
||||
["probe", "Probing"],
|
||||
["plan", "Planning"],
|
||||
@@ -23,25 +25,40 @@ describe("PhaseChip (header)", () => {
|
||||
assert.ok(html.includes('data-testid="phase-chip"'));
|
||||
assert.ok(html.includes(`data-phase="${phase}"`));
|
||||
assert.ok(html.includes(label), `${phase} label`);
|
||||
assert.ok(html.includes(`bg-phase-${phase}`), `${phase} dot color`);
|
||||
assert.ok(!html.includes("bg-phase-"), `${phase} dot color retired`);
|
||||
assert.ok(!html.includes("rounded-full bg-"), "no dot of any color — label only");
|
||||
}
|
||||
});
|
||||
|
||||
it("busy: the old dot slot becomes the braille spinner (motion = turn in flight)", () => {
|
||||
const html = renderToStaticMarkup(<PhaseChip phase="teach" busy />);
|
||||
assert.ok(html.includes('data-testid="braille-spinner"'), "spinner in the chip");
|
||||
assert.ok(html.includes("Teaching"), "label stays");
|
||||
assert.ok((html.match(/class="braille-frame"/g) ?? []).length === 10, "all ten frames");
|
||||
});
|
||||
|
||||
it("idle: no spinner — the chip is the label alone", () => {
|
||||
const html = renderToStaticMarkup(<PhaseChip phase="teach" />);
|
||||
assert.ok(!html.includes("braille-spinner"));
|
||||
});
|
||||
});
|
||||
|
||||
describe("PhaseDivider (transcript boundary)", () => {
|
||||
it("the plan seam: dotted rules in the plan color + node count", () => {
|
||||
it("the plan seam: monochrome dotted rules + node count", () => {
|
||||
const html = renderToStaticMarkup(<PhaseDivider phase="plan" nodeCount={3} />);
|
||||
assert.ok(html.includes('data-testid="phase-divider-plan"'));
|
||||
assert.match(html, /Plan · 3 nodes/);
|
||||
assert.ok(html.includes("border-dotted"), "dotted rule");
|
||||
assert.ok(html.includes("border-phase-plan"), "plan-colored rule");
|
||||
assert.ok(html.includes("border-border"), "monochrome rule");
|
||||
assert.ok(!html.includes("border-phase-"), "phase-colored rules are retired");
|
||||
});
|
||||
|
||||
it("the teach seam: dotted rules in the teach color, no count", () => {
|
||||
it("the teach seam: monochrome rule, no count", () => {
|
||||
const html = renderToStaticMarkup(<PhaseDivider phase="teach" />);
|
||||
assert.ok(html.includes('data-testid="phase-divider-teach"'));
|
||||
assert.match(html, /Teaching/);
|
||||
assert.ok(html.includes("border-phase-teach"));
|
||||
assert.ok(!html.includes("border-phase-"));
|
||||
assert.ok(!html.includes("text-phase-"));
|
||||
assert.ok(!html.includes("nodes"));
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user