Skip to content

Light mode with a two-theme contrast gate, and generated architecture diagrams - #70

Merged
CodexManvik merged 2 commits into
masterfrom
feat/light-mode-and-diagrams
Sep 17, 2026
Merged

CodexManvik merged 2 commits into
masterfrom
feat/light-mode-and-diagrams

Conversation

@CodexManvik

Copy link
Copy Markdown
Owner

Two things the capstone needs: a console that is not dark-only, and the architecture and flow diagrams the mentor asked for.

Light mode

A three-state control: follow system, light, dark. Three rather than two because "follow system" is the one most people want, a binary switch cannot express it, and silently pinning a theme on first visit is what sends people looking for a setting. A segmented control rather than the usual sun/moon switch, for the same reason.

How it works. Every colour moves out of tailwind.config.js and into CSS custom properties holding RGB channels, so rgb(var(--signal) / <alpha-value>) keeps Tailwind's opacity modifiers composing. Two concrete theme blocks and no media query in the stylesheet: a pre-paint script in index.html resolves "system" and stamps data-theme on <html> before React mounts, which is what stops the page flashing the wrong theme on load. useTheme keeps following the OS while the preference is "system".

Light is a real inversion, not the dark palette lightened. The surface step flips so raised still means "this lifts": on dark a card is lighter than the page, on light it is white against off-white. Every ink tone and severity colour is re-picked, because #62C073 on white is 2.1:1 and a 1.6px chart stroke in it simply disappears.

The 67 bg-white/[0.0x] washes were the actual blocker. White at 3% over a near-black card is a subtle lift; over a white card it is nothing at all. They collapse onto three semantic surface tokens that invert with the theme.

The contrast gate

frontend/scripts/check-contrast.mjs reads the palette straight out of index.css — so it cannot drift from what ships — and checks every foreground tone against every surface in both themes, plus the chart series at the 3:1 non-text minimum. It runs as part of npm run build.

It immediately found twelve failures, three of them in the dark theme that already shipped:

dark:  ink-4 #7d7d7d on surface-3 #242424 = 3.77:1 (need 4.5)
light: sev-warn #ab6400 on surface-3      = 3.76:1
...

ink-2, ink-3 and ink-4 only ever cleared AA against the card. A nested block lifts the background enough to sink all three, and nothing was checking. Both palettes are adjusted until all 112 pairs pass.

Diagrams

Five, each in a dark and a light variant, in docs/diagrams/ with an index.html contact sheet:

File What it answers
01-architecture The services, what flows between them, and which call blocks
02-incident-flow Alert storm to verified fix, with every exit the system can take
03-recovery-ladder What happens when the first fix does not work, and where we stop
04-data-sources The three stores and which question each answers
05-earned-autonomy How a playbook reaches unattended execution, and how it loses it

Generated by scripts/make-diagrams.mjs, hand-built rather than Mermaid because these sit next to deck slides that were designed and have to carry the product's own palette and type scale. The palette is lifted from index.css, so the diagrams, the console and the deck are one visual system.

The geometry gate

SVG has no layout engine, so a coordinate that is 80px wrong does not raise an error — it produces a picture that is quietly unreadable. Every solid box and every arrow label registers its rectangle, and the generator exits non-zero on any overlap or overflow.

It earned its place immediately:

  • action rendering underneath read model
  • two boxes running off the right edge of the incident-flow canvas
  • situations.diagnosed being wider than the 74px gap it sat in, so its white-out rect overprinted the node on either side

Two things a gate cannot check were found by looking at the output: the execute arrow pointed at empty space (now an explicit Kubernetes API node), and a 30px title collided with its own tag because node() assumed a 17px one.

Topology is read, not remembered

ingestion    publishes  telemetry.raw
correlation  consumes   telemetry.raw         publishes  situations.detected, situations.suppressed
rca          consumes   situations.detected   publishes  situations.diagnosed
action       consumes   situations.diagnosed  publishes  remediation.outcomes
feedback     consumes   remediation.outcomes
read         consumes   all four
governance   called by action over HTTP, and that call blocks

Testing

  • npm run build passes, which now includes the contrast gate (112/112).
  • node scripts/make-diagrams.mjs passes the geometry gate for all five.
  • tsc --noEmit clean.
  • Light mode checked by eye on all five views against the live cluster: Overview, Incidents, Governance, Agent Activity, Settings. The theme control, the pre-paint stamp and localStorage persistence were verified in the browser rather than assumed.
  • No backend changes, so the Python suite is untouched by this branch.

Note

frontend/scripts/check-contrast.mjs is worth keeping in CI. The three dark-theme failures it found had been live since the redesign, and nothing else would have caught them.

🤖 Generated with Claude Code

CodexManvik and others added 2 commits September 17, 2026 15:49
Adds a three-state theme control: follow system, light, dark. Three rather than
two because "follow system" is the one most people want and a binary switch
cannot express it, and because silently pinning a theme on first visit is what
sends people looking for a setting. It is a segmented control rather than the
usual sun/moon switch for the same reason.

How it works: every colour moves out of tailwind.config.js and into CSS custom
properties holding RGB channels, so `rgb(var(--signal) / <alpha-value>)` keeps
Tailwind's opacity modifiers composing. Two concrete theme blocks, no media
query in the stylesheet: a pre-paint script in index.html resolves "system" and
stamps data-theme on <html> before React mounts, which is what stops the page
flashing the wrong theme on load. useTheme keeps following the OS while the
preference is "system".

Light is a real inversion rather than the dark palette lightened. The surface
step flips, so `raised` still means "this lifts": on dark a card is lighter than
the page, on light it is white against off-white. Every ink tone and severity
colour is re-picked, because #62C073 on white is 2.1:1 and a 1.6px chart stroke
in it simply disappears.

The 67 `bg-white/[0.0x]` washes are the thing that actually blocked this. White
at 3% over a near-black card is a subtle lift; over a white card it is nothing.
They collapse onto three semantic surface tokens that invert with the theme.

scripts/check-contrast.mjs reads the palette straight out of index.css and
checks every foreground tone against every surface in both themes, plus the
chart series at the 3:1 non-text minimum. It runs as part of `npm run build`.

It immediately found twelve failures, three of them in the DARK theme that
already shipped: ink-2, ink-3 and ink-4 only ever cleared AA against the card,
and a nested block lifts the background enough to sink all three. ink-4 on
bg-surface-3 was 3.77:1. Both palettes are adjusted until all 112 pairs pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Five diagrams the capstone review asked for, each in a dark and a light variant
so they drop into the deck and into a README without looking borrowed:
system architecture, incident lifecycle, the recovery ladder for when the first
fix does not work, the three data sources, and how autonomy is earned.

Generated by scripts/make-diagrams.mjs rather than drawn, and hand-built rather
than Mermaid: these sit next to slides that were designed, so they have to carry
the product's own palette and type scale, which Mermaid's auto-layout cannot be
made to do. The palette is lifted from frontend/src/styles/index.css so the
diagrams, the console and the deck are one visual system.

The cost of manual layout is that SVG has no layout engine, so a coordinate that
is 80px wrong does not raise an error, it produces a picture that is quietly
unreadable. Every solid box and every arrow label therefore registers its
rectangle and the generator exits non-zero on any overlap or overflow. It earned
its place immediately, catching:

- `action` rendering underneath `read model`
- two boxes running off the right edge of the incident-flow canvas
- the `situations.diagnosed` label being wider than the 74px gap it sat in, so
  it overprinted the node on either side

Two things it cannot check were found by looking: the execute arrow pointed at
empty space, which is now an explicit Kubernetes API node, and a 30px title
collided with its own tag because the node layout assumed 17px.

The bus topology is read out of the services rather than from memory:
ingestion publishes telemetry.raw, correlation consumes it and publishes
situations.detected plus situations.suppressed, rca publishes
situations.diagnosed, action publishes remediation.outcomes, and feedback, read
and governance all consume that last one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 17, 2026 10:30
@coderabbitai

coderabbitai Bot commented Sep 17, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 9bb77359-1aea-4cac-aa0b-02f863c22443


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@CodexManvik
CodexManvik merged commit 0ea53e1 into master Sep 17, 2026
6 checks passed
@CodexManvik
CodexManvik deleted the feat/light-mode-and-diagrams branch September 17, 2026 10:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants