Skip to content

Add CI, generate tokens from tokens.json, gate shared/ against client-shared - #7

Merged
brentrager merged 1 commit into
mainfrom
fix/parity
Aug 20, 2026
Merged

Add CI, generate tokens from tokens.json, gate shared/ against client-shared#7
brentrager merged 1 commit into
mainfrom
fix/parity

Conversation

@brentrager

Copy link
Copy Markdown
Contributor

Why

This repo had no CI. .github/ held two banner images and nothing else — no build, no test, no lint. A change could land unbuilt, and shared/ could diverge from SmooAI/client-shared with nothing to say so.

It already had diverged. The review that prompted this recorded the two repos' shared/ as byte-identical; on origin/main this morning they were not:

shared/styles.css     ui=26ee714d…  client-shared=c23abd72…  DIFFER
shared/monogram.svg   ui=a2db97f8…  client-shared=565d6108…  DIFFER

The monogram fix in f230808"restore the inner 'S' curve and the dot" — never crossed. client-shared, which is what the th CLI ships in production, served a monogram with no S and no dot. Its styles.css had separately lost the entire .input family, and its class-existence test had drifted along with it so nothing was red. Both repos were green the whole time, because neither ran anything.

The upstream half is fixed in SmooAI/client-shared#7. This PR is the gate that stops it recurring.

What changed

CI exists.github/workflows/rust.yml: cargo fmt --check, clippy --all-targets -D warnings, tests, the module-tree guard, and the shared/ drift gate.

shared/ drift gate. client-shared owns the design system; this repo keeps a copy for observability-studio and smooblue, and CI fails if they differ. It compares git blob SHAs from the GitHub contents API against git ls-tree, so added and deleted files are caught as well as edited ones — there is no hand-maintained filename list to fall behind, which is the failure mode a for f in a b c loop would have.

shared/tokens.json is code input now. It was read by no code in any language while the Rust constants hand-mirrored the same values. rust/build.rs generates the whole tokens module from it via shared/tokens_codegen.rs, so a token cannot exist in the design system and be missing from Rust — nobody types the constants. That immediately surfaced six tokens the CSS declared and Rust omitted (MUTED, MUTED_FOREGROUND, ACCENT, INPUT, RING, SIDEBAR) plus the radius / space / font scales. RADIUS_PX is kept as an alias of the generated RADIUS_MD_PX so observability-studio (pinned at tag = v0.1.0) keeps compiling when it retags.

tokens_match_css could not fail in the direction that mattered. It asserted each constant appeared somewhere in the CSS as a substring — it passes with a constant bound to the wrong custom property, and nothing walked the CSS, so a token the CSS had and Rust lacked was structurally invisible. shared/tokens_css_check.rs parses :root, resolves var() to a fixed point, and asserts both directions.

Module-tree guard. scripts/check-module-tree.py fails if any .rs under rust/src/ is unreachable from a mod declaration — the class that let client-shared ship 174 lines of auth/refresh.rs that never compiled once, invisible to build, clippy, test and coverage alike.

Verification — every new check confirmed red before green

Check Broken how Result
tokens_match_css drifted color.brand.orange in tokens.json FAILED
css_colors_are_all_tokens added --surprise: oklch(…) to styles.css FAILED
check-module-tree.py planted an orphan .rs exit 1, named the file

shared/ verified byte-identical to SmooAI/client-shared@main (7c5b948) — all five blob SHAs equal. Tests 6/6, fmt and clippy --all-targets -D warnings clean.

Judgment calls

  • Gate, not a cargo dependency on client-shared. ui could have become a thin re-export, deleting shared/ outright — both crates are git deps rather than crates.io publishes, so it is possible. But it would put two independently rev-pinned git deps in one graph for any consumer that ever wants both, and ui's live consumer is tag-pinned. The gate closes the silent-divergence defect without the coupling.
  • The gate is one-directional. Bidirectional deadlocks: neither repo's PR could go green until the other merged. client-shared is the source and is ungated.
  • This repo is not archived. See the note in the report — whether ui should eventually fold into client-shared is a call for its owner, not for this PR.

Pearls: th-42c460, th-ea969b

🤖 Generated with Claude Code

https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC

…ient-shared

This repo had NO CI — `.github/` held only banner images — so a change could
land unbuilt and `shared/` could diverge from SmooAI/client-shared with nothing
to say so. It already had: the monogram fix in f230808 ("restore the inner 'S'
curve and the dot") never crossed, so client-shared served a monogram with no S
and no dot, and its styles.css lost the whole `.input` family. Both repos were
green the entire time, because neither ran anything.

- **CI** (`.github/workflows/rust.yml`): fmt, clippy `--all-targets -D warnings`,
  tests, the module-tree guard, and the shared/ drift gate.

- **`shared/` drift gate.** SmooAI/client-shared owns the design system (it is
  the declared successor and what `th` ships); this repo keeps a copy for
  observability-studio and smooblue, and CI fails if the copies differ. The gate
  compares git blob SHAs from the contents API against `git ls-tree`, so adds and
  deletes are caught too — no filename list to fall behind. Deliberately
  one-directional: client-shared is ungated so a change lands there and this repo
  follows; a bidirectional gate deadlocks, with neither PR able to green until
  the other merges. Chosen over a cargo dependency on client-shared because both
  crates are git deps, and depending across would put two independently
  rev-pinned git deps in one graph for any consumer wanting both.

- **`shared/tokens.json` was inert** — read by no code in any language while the
  Rust constants hand-mirrored the same values. `rust/build.rs` now generates the
  whole `tokens` module from it via `shared/tokens_codegen.rs`, so a token cannot
  exist in the design system and be missing from Rust. That surfaced six tokens
  the CSS had and Rust omitted (MUTED, MUTED_FOREGROUND, ACCENT, INPUT, RING,
  SIDEBAR) plus the radius/space/font scales. `RADIUS_PX` stays as an alias of
  the generated `RADIUS_MD_PX` so observability-studio keeps compiling.

- **`tokens_match_css` could not fail in the direction that mattered.** It
  asserted each constant appeared *somewhere* in the CSS as a substring: it
  passes with a constant bound to the wrong property, and nothing walked the CSS,
  so a token the CSS had and Rust lacked was invisible. `shared/tokens_css_check.rs`
  parses `:root`, resolves `var()`, and checks both directions. Verified red by
  drifting a token value and by adding an untracked `--surprise` colour.

- **Module-tree guard** (`scripts/check-module-tree.py`): fails if any `.rs` file
  under `rust/src/` is unreachable from a `mod` declaration — the class that let
  client-shared ship 174 lines of `auth/refresh.rs` that never compiled.

shared/ is byte-identical to SmooAI/client-shared@main (7c5b948); all five blob
SHAs verified equal. Tests 6/6, fmt + clippy clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
@changeset-bot

changeset-bot Bot commented Aug 20, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 9305d85

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@brentrager
brentrager merged commit 7b71767 into main Aug 20, 2026
3 checks passed
@brentrager
brentrager deleted the fix/parity branch August 20, 2026 18:30
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.

1 participant