Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 1 addition & 14 deletions frontend/design-system/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ entrance and scroll-reveal animation is allowed within these limits:

## Caveats

- **Faces are self-hosted from `@fontsource`, never Google Fonts.** Signal ships Bricolage Grotesque, IBM Plex Sans and IBM Plex Mono; Night Shift adds Recursive Variable (`@fontsource-variable/recursive/full.css`, all axes). Do not add a family outside an identity decision.
- **One webfont shipped.** Inter Variable is bundled from `@fontsource-variable/inter` (OFL); everything else is system. Do not add a second family, and never load a face from Google Fonts.
- **No Figma file** is committed. All visual decisions trace back to `tokens.css` and `frontend/src/v2/v2.css`.
- **Marketing site styling lives elsewhere.** Only the in-app experience is documented here.

Expand All @@ -237,16 +237,3 @@ Chosen on the Shell Parity canvas from three directions (Studio, Workshop, Signa
The canvas is the spec of record for every screen: https://claude.ai/code/artifact/3c07344a-86f2-4296-b3f0-a49a0b451503 (pages: Workspace in C · Front door · Directions · Coverage). A screen is done when it matches its artboard at 1200 and 390 in a real browser.

**The full system — state grammar, component grammar with exact values, copy rules, the process that made it land, and how to add a screen — is [`docs/design/signal-identity.md`](../../docs/design/signal-identity.md).** This section is the short form; when the two disagree, fix both in the same PR.

## Night Shift on the pod thread (D194, 2026-10-30)

Sam picked direction C from the v3 look sprint: "I do like night shift, also it is about to be Halloween" (ledger D194). It is the one exception to D169's fix-only rule, and it covers only the screens in the demo video: the pod thread (message list, composer, header) and its inspector. Every other screen stays Signal.

- **Scope is one class.** `.v2-nightshift` sits on the pod `<main>` (V2Thread) and the inspector `<aside>` (V2Inspector), and remaps the `--v2-*` tokens underneath it. The rail and pods list stay on the light Signal ground, so the dark pane reads as one inset card. Moving the class up to `.v2-shell` takes the whole app dark; that is a ruling, not a CSS change.
- **Walnut and parchment.** Ground walnut `#2c1e15`, side panel walnut-deep `#221610`, cards panel `#3a2a1e`, chips panel-2 `#483629`, borders seam `#544437`. Text parchment `#f0e7d6`, secondary `#cfc2af`, meta `#ab9c89` (5.1:1 on panel). The primary action is a parchment block with walnut-deep text.
- **Two signals, one job each.** Flare `#ff7c4c` means needs you: the pending ring (1.5px plus glow), an open decision's primary option, the inspector's attention mark. Green `#95dfa4` means an agent ran or is running: the 3px inset edge on an agent's message, the run receipt link, the working dot. Cobalt does not appear inside the Night Shift pane.
- **One face, Recursive, with its axes doing the work.** Body is `"CASL" 0,"MONO" 0`; names and the pod title are `"CASL" 1,"MONO" 0` at 750–800; times, runtime chips, code and receipts are `"CASL" 1,"MONO" 1`. Nothing under 11px.
- **Softer shape.** Avatars are circles (36px, 32 on phones). Radius 6 on chips, 9–10 on controls, 11 on inspector cards, 12 on the composer, 16 on an agent's message card.
- **How an agent message reads.** A panel card with a green inset edge; the author in casual 750; the runtime chip in casual mono on panel-2; inline code on panel-2, blocks on walnut-deep with a seam border; the run receipt is a punched "time card" on walnut-deep (three panel dots, green link, mono retention line). Every row carries the same 12px inset, so human and agent text share one column without the card bleeding past the pane.

Tokens: `--c-ns-*` in `tokens.css`, `--v2-ns-*` in the `.v2-nightshift` block at the end of `frontend/src/v2/v2.css`. Pinned in `frontend/src/v2/__tests__/v2-layout-invariants.test.ts`.
26 changes: 0 additions & 26 deletions frontend/design-system/tokens.css
Original file line number Diff line number Diff line change
Expand Up @@ -170,32 +170,6 @@
--c-platform-groupme: #00aff0;
--c-platform-groupme-soft: #e3f5fd;

/* ── Night Shift (D194, 2026-10-30) ─────────────────────────
The dark-first look for the pod thread and its inspector only;
every other screen stays Signal. Production: the `.v2-nightshift`
block at the end of frontend/src/v2/v2.css (--v2-ns-*), which
remaps the --v2-* tokens under that class. Hex from the source
oklch values in the D194 direction sheet. */
--c-ns-walnut: #2c1e15; /* pane ground oklch(25% .027 55) */
--c-ns-walnut-deep: #221610; /* inspector, receipt, pre oklch(21.5% .023 52) */
--c-ns-panel: #3a2a1e; /* agent card, cards oklch(30% .031 57) */
--c-ns-panel-2: #483629; /* chips, mentions, code oklch(35% .033 58) */
--c-ns-seam: #544437; /* borders oklch(40% .031 60) */
--c-ns-parch: #f0e7d6; /* text, primary action oklch(93% .024 82) */
--c-ns-parch-2: #cfc2af; /* secondary text oklch(82% .03 78) */
--c-ns-parch-3: #ab9c89; /* meta, placeholder oklch(70% .032 72); 5.1:1 on panel */
--c-ns-flare: #ff7c4c; /* needs you, nothing else oklch(73.5% .175 40) */
--c-ns-flare-ink: #2c1309; /* text on flare oklch(22% .045 42) */
--c-ns-green: #95dfa4; /* agent ran / running oklch(84% .11 150) */
--c-ns-danger: #f47b74;
--c-ns-font: "Recursive Variable", ui-sans-serif, system-ui, sans-serif;
/* Recursive axes: body "CASL" 0,"MONO" 0 · names "CASL" 1,"MONO" 0 ·
meta, code, chips "CASL" 1,"MONO" 1 */
--c-ns-radius-sm: 6px;
--c-ns-radius: 9px;
--c-ns-radius-lg: 14px;
--c-ns-radius-card: 16px;

}

/* ============================================================
Expand Down
13 changes: 0 additions & 13 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
"@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6",
"@fontsource-variable/bricolage-grotesque": "^5.3.0",
"@fontsource-variable/recursive": "^5.3.0",
"@fontsource/ibm-plex-mono": "^5.3.0",
"@fontsource/ibm-plex-sans": "^5.3.0",
"@mui/icons-material": "^5.11.16",
Expand Down
3 changes: 0 additions & 3 deletions frontend/src/v2/V2App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@ import '@fontsource/ibm-plex-sans/400.css';
import '@fontsource/ibm-plex-sans/500.css';
import '@fontsource/ibm-plex-sans/600.css';
import '@fontsource/ibm-plex-mono/500.css';
// Night Shift (D194): Recursive, all axes (CASL + MONO), self-hosted. The
// woff2 only downloads when a .v2-nightshift surface renders a glyph.
import '@fontsource-variable/recursive/full.css';
import './v2.css';

// TASK-145 — inside the shell, only the surfaces the composer path needs stay
Expand Down
49 changes: 0 additions & 49 deletions frontend/src/v2/__tests__/v2-layout-invariants.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5162,52 +5162,3 @@ describe('v2 board on a phone (TASK-576)', () => {
expect(page).toContain('<V2MobileTabs needsYouCount={attention.count ?? 0} />');
});
});

describe('v2 Night Shift on the pod thread (TASK-642, D194)', () => {
const NS = '.v2-root .v2-nightshift';

test('the scope class remaps the tokens to walnut, parchment, flare and Recursive', () => {
const block = ruleBody(read('../v2.css'), NS);
expect(block).toContain('--v2-bg: var(--v2-ns-walnut);');
expect(block).toContain('--v2-text-primary: var(--v2-ns-parch);');
expect(block).toContain('--v2-ink: var(--v2-ns-parch);');
expect(block).toContain('--v2-on-ink: var(--v2-ns-walnut-deep);');
expect(block).toContain('--v2-accent: var(--v2-ns-flare);');
expect(block).toContain('--v2-font: "Recursive Variable"');
expect(block).toContain('--v2-font-mono: "Recursive Variable"');
expect(block).toContain('color-scheme: dark;');
});

test('the --v2-ns-* hex values mirror --c-ns-* in the design-system tokens', () => {
const block = ruleBody(read('../v2.css'), NS);
const tokens = read('../../../design-system/tokens.css');
const pairs = [...block.matchAll(/--v2-ns-([a-z0-9-]+): (#[0-9a-f]{6});/g)];
expect(pairs.length).toBeGreaterThanOrEqual(11);
pairs.forEach(([, name, hex]) => {
expect(tokens).toContain(`--c-ns-${name}: ${hex};`);
});
});

test('only the pod thread and its inspector carry the scope class', () => {
expect(read('../components/V2Thread.tsx')).toContain('className="v2-pane v2-pane--main v2-nightshift"');
expect(read('../components/V2Inspector.tsx')).toContain('className="v2-pane v2-pane--inspector v2-nightshift"');
expect(read('../V2App.tsx')).toContain("import '@fontsource-variable/recursive/full.css';");
});

test('an agent message is a panel card with a green edge, and every row shares its inset', () => {
const css = read('../v2.css');
const card = ruleBody(css, `${NS} .v2-thread .v2-msg--bot`);
expect(card).toContain('background: var(--v2-ns-panel);');
expect(card).toContain('inset 3px 0 0 rgba(149, 223, 164, 0.75)');
// A bleeding card overflowed the phone pane by 12px; the inset lives on
// every row instead, so agent and human text share one column.
expect(card).not.toMatch(/margin[^;]*-\d/);
expect(ruleBody(css, `${NS} .v2-thread .v2-msg`)).toContain('padding: 10px 12px;');
expect(read('../components/V2MessageRow.tsx')).toContain("message.user?.isBot ? ' v2-msg--bot' : ''");
});

test('the system action line keeps its 16px face under the round 36px avatars', () => {
const css = read('../v2.css');
expect(ruleBody(css, `${NS} .v2-thread .v2-system-action-line .v2-avatar`)).toContain('width: 16px; height: 16px;');
});
});
2 changes: 1 addition & 1 deletion frontend/src/v2/components/V2Inspector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ const V2Inspector: React.FC<V2InspectorProps> = ({ detail, currentUserId, onPodC
: t('inspector.workspace.nothingOpen');

return (
<aside className="v2-pane v2-pane--inspector v2-nightshift" aria-label={t('inspector.workspace.ariaLabel')}>
<aside className="v2-pane v2-pane--inspector" aria-label={t('inspector.workspace.ariaLabel')}>
<div className="v2-workspace-inspector">
{onClose && (
<button type="button" className="v2-workspace-inspector__close" onClick={onClose} aria-label={t('inspector.workspace.close')}>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/v2/components/V2MessageRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -654,7 +654,7 @@ const V2MessageRow: React.FC<V2MessageRowProps> = ({ message, sourceMessageId, d
data-source-message-id={sourceMessageId != null ? String(sourceMessageId) : undefined}
tabIndex={-1}
data-testid={isDecisionRuling ? 'decision-ruling-row' : undefined}
className={`v2-msg v2-message-row${mentionsMe ? ' v2-msg--mention' : ''}${grouped ? ' v2-msg--grouped' : ''}${actionsRevealed ? ' v2-msg--reveal' : ''}${runtimeTag ? ' v2-msg--agent' : ''}${message.user?.isBot ? ' v2-msg--bot' : ''}`}
className={`v2-msg v2-message-row${mentionsMe ? ' v2-msg--mention' : ''}${grouped ? ' v2-msg--grouped' : ''}${actionsRevealed ? ' v2-msg--reveal' : ''}${runtimeTag ? ' v2-msg--agent' : ''}`}
onClick={onBubbleTap}
onPointerDown={onPressStart}
onPointerUp={onPressEnd}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/v2/components/V2Thread.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1863,7 +1863,7 @@ const V2Thread: React.FC<V2ThreadProps> = ({ detail, firstRunVisible = false, in
});

return (
<main className="v2-pane v2-pane--main v2-nightshift">
<main className="v2-pane v2-pane--main">
<div className="v2-chat v2-thread">
<header className="v2-thread__header v2-pod-header">
<div className="v2-thread__header-row">
Expand Down
Loading
Loading