Files
DeepSeek-TUI/.github/workflows/web.yml
Hunter Bown de001f3c4b design: export the TUI whale palette instead of re-typing it (#5797)
* design: export the TUI whale palette instead of re-typing it

crates/tui/src/palette/tokens.rs is the whale palette. The web app repeated
its hexes by hand, the desktop shell ran a separate neutral-gray palette, and
the Android theme a fourth one — four palettes sharing exactly one value
(#08111C). Make the Rust file the source and generate the rest.

scripts/export-design-tokens.py parses the 47 WHALE_*_RGB consts (aliases
included: INFO = ACTION = ACCENT_PRIMARY, SUCCESS = WORKING_GREEN,
ERROR_BORDER = ERROR, ...) and emits web/app/tokens.css, plus — when a
codewhale-apps checkout sits beside this repo — the desktop CSS token file
and a Compose WhaleTokens object. Aliases are emitted as var()/val
references, so the alias structure survives the export instead of flattening
into duplicate literals. --check fails when a generated file is stale; it is
wired in as `npm run check:tokens` and runs in the web workflow.

globals.css keeps its own variable names (--paper, --ink, --indigo — the
component rules and the docs light sheet consume them) and now binds them to
--whale-* rather than re-typing the hex. Only byte-identical values were
rebound; --paper-card, --indigo-deep, --stage-ambient and --stage-hint are
not whale tokens and stayed literal. No rendered color changes.

The two contract tests read hexes straight out of globals.css, so they now
resolve one hop through the generated file (lib/whale-tokens.ts). They still
catch a wrong mapping: pointing --paper at --whale-panel fails with
"expected '#0e1729' to be '#03070d'".

Evidence, in web/:
  npm test          -> Test Files 42 passed (42), Tests 364 passed (364)
  npm run lint      -> clean; npx tsc --noEmit -> clean
  npm run build     -> succeeded; built CSS carries --whale-bg:#03070d
                       and --paper:var(--whale-bg)
  npm run check:tokens -> design tokens up to date (47 tokens)
  check:facts, check:docs -> PASS
Perturbing tokens.css makes --check exit 1 with "stale: web/app/tokens.css".

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AdbuqwHAXSDcikPiS6L6Qw

* design tokens: the generator targets the web app only

The desktop and Android targets were rendered and rejected on sight; the
script now writes nothing outside this repository and has no --apps-root.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AdbuqwHAXSDcikPiS6L6Qw

---------

Co-authored-by: CodeWhale Bot <bot@codewhale.net>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-01 13:42:57 -07:00

147 lines
6.3 KiB
YAML

name: Web Frontend
on:
push:
branches: [master, main]
pull_request:
branches: [master, main]
workflow_dispatch:
permissions:
contents: read
jobs:
lint:
name: Lint & Type Check
runs-on: ubuntu-latest
defaults:
run:
working-directory: web
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22
cache: 'npm'
cache-dependency-path: web/package-lock.json
- name: Install dependencies
run: npm ci
- name: Check facts drift
# facts.generated.ts is TRACKED (committed), so verify the committed
# copy matches the workspace BEFORE regenerating. Running prebuild first
# would self-heal the working tree and let a stale committed file pass
# (#3771). check:facts ignores the volatile generatedAt/latestRelease
# fields by design, so it is safe to run against the committed copy that
# exists at checkout.
run: npm run check:facts
- name: Check published-release fact is current
# web/data/latest-published-release.json is hand-maintained and feeds
# facts.generated.ts. Nothing wrote it, so it drifted to v0.9.10 while
# v0.9.11 was live, and the post-deploy comparison below failed on
# latestPublishedRelease.tag AFTER the site had already shipped. Gate it
# here so the mismatch is caught before deploying, not after.
env:
GITHUB_TOKEN: ${{ github.token }}
run: npm run check:latest-release
- name: Generate derived facts
# Regenerate after the drift gate so tsc --noEmit (TS2307 without it) and
# the build use a current facts.generated.ts. When the gate passes this
# only refreshes the generatedAt timestamp.
run: npm run prebuild
- name: Check docs parity
# Fails CI when docs-map.ts references non-existent repo files or
# when website version / command snippets are stale.
run: npm run check:docs
- name: Check design tokens
# app/tokens.css is generated from crates/tui/src/palette/tokens.rs.
# Fails CI when the palette moved and the export was not re-run.
run: npm run check:tokens
- name: Run tests
run: npm test
- name: Run ESLint
run: npm run lint
- name: TypeScript type check
run: npx tsc --noEmit
- name: Build production site
run: npm run build
deploy-reminder:
name: Deployment approval needed
runs-on: ubuntu-latest
needs: lint
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- name: Surface the manual deployment gate
env:
REVISION: ${{ github.sha }}
run: |
echo "::notice title=Web deployment approval needed::Revision ${REVISION} passed the web gates but is not deployed. Dispatch web.yml on main to publish it."
{
echo "## Web deployment approval needed"
echo
echo "Revision \`${REVISION}\` passed the web gates but has **not** been deployed."
echo
echo "A maintainer can publish it with \`gh workflow run web.yml --repo Hmbown/CodeWhale --ref main\`."
} >> "$GITHUB_STEP_SUMMARY"
deploy:
name: Deploy to Cloudflare
runs-on: ubuntu-latest
needs: lint
# Deploy is MANUAL ONLY: a human dispatches this workflow on main. Pushes
# and pull requests still run `lint` above, but they never reach Cloudflare.
# This mirrors scripts/check-cloudflare-deploy-env.mjs, which fails closed
# unless GITHUB_EVENT_NAME is workflow_dispatch, GITHUB_REF is
# refs/heads/main, and GITHUB_SHA is an exact 40-hex revision — a push
# trigger here would only produce a red job after `lint` had already run.
# lib/deploy-preflight.test.ts asserts both halves of that contract.
# `needs: lint` is the gate: facts drift, docs parity, tests, ESLint, tsc,
# and a production build all pass before anything reaches Cloudflare.
if: >-
github.event_name == 'workflow_dispatch'
&& github.ref == 'refs/heads/main'
# Serialize deploys so two dispatches landing close together cannot race and
# leave Cloudflare serving the older bundle. Never cancel in progress: a
# half-finished OpenNext upload is worse than a queued one.
concurrency:
group: deploy-codewhale-web
cancel-in-progress: false
defaults:
run:
working-directory: web
env:
CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
steps:
- uses: actions/checkout@v7
# Pin the checkout to the exact revision this dispatch resolved, so the
# SHA reported to compare:deployed-facts and asserted on the public
# receipt below is the SHA that was actually built, even if main moves
# while the run is queued behind the concurrency group.
with:
ref: ${{ github.sha }}
- uses: actions/setup-node@v7
with:
node-version: 22
cache: 'npm'
cache-dependency-path: web/package-lock.json
- name: Install dependencies
run: npm ci
- name: Record deployed/source drift
# Read-only and credential-free. A mismatch is the normal state here —
# it is the gap this run is about to close — so this step reports
# without gating. The real assertion is the post-deploy verification
# below, which must observe this exact revision on the public receipt.
run: npm run compare:deployed-facts -- --expected-revision "$GITHUB_SHA"
- name: Check Cloudflare deploy environment
run: npm run check:deploy-env
# npm's deploy script performs one OpenNext build, then deploys that exact
# bundle. Wrangler must not run a custom post-cache build: OpenNext
# populates the remote cache before it hands the bundle to Wrangler.
- name: Build and deploy exact OpenNext bundle
run: npm run deploy
- name: Verify exact deployed revision
# The public /api/facts receipt must identify this workflow's exact
# checkout before the manual deployment run can finish green.
run: npm run check:deployed-facts -- --expected-revision "$GITHUB_SHA" --attempts 10 --retry-delay-ms 3000