From bbfef4270576bc08e2add2189fe6f099a2f7456d Mon Sep 17 00:00:00 2001 From: Brent Rager Date: Thu, 20 Aug 2026 14:03:10 -0400 Subject: [PATCH] Become the design-system source of truth: CI, generated tokens, no llm stub MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This repo had NO CI — `.github/` held only banner images — and it showed. **The monogram in production was broken.** SmooAI/ui fixed the smoo monogram in f230808 ("restore the inner 'S' curve and the dot"). client-shared, which declares itself ui's successor and is what the `th` CLI actually ships, still carried the pre-fix path: an outer ring with no S and no dot. `shared/styles.css` had likewise fallen ~60 lines behind (the whole `.input` family). The review that prompted this work recorded the two repos' `shared/` as byte-identical; they are not, and nothing would ever have said so. - Adopt SmooAI/ui's `shared/styles.css` + `monogram.svg`. This repo's `shared/` is now the declared source; SmooAI/ui gets a CI gate that fails if its copy diverges (separate PR there). - **`shared/tokens.json` was read by no code in any language** while the Rust constants hand-mirrored the same values — a "language-agnostic source of truth" that was inert. It is now the build input: `rust/build.rs` runs `shared/tokens_codegen.rs` to generate the whole `tokens` module, so a token cannot exist in the design system and be missing from Rust — nobody types the constants. Generation also surfaced six tokens the CSS had and Rust omitted (MUTED, MUTED_FOREGROUND, ACCENT, INPUT, RING, SIDEBAR), plus the radius/space/font scales. - **`tokens_match_css` could not fail in the direction that mattered.** It asserted each Rust constant appeared *somewhere* in the CSS as a substring: blind to a constant attached to the wrong property, and structurally incapable of noticing a token the CSS has and Rust lacks. Replaced by `shared/tokens_css_check.rs`, which parses `:root`, resolves `var()`, and checks both directions. Verified red by hand-drifting a token value and by adding an untracked `--surprise` colour to the CSS. - **Drop the `llm` feature.** `rust/src/llm/mod.rs` was six lines of doc comment; `--features llm` compiled to zero usable surface while Cargo.toml and the README advertised it. A feature that compiles to nothing is worse than an absent one — it reads as shipped. Pearl th-f7b20f still tracks the real implementation; the flag comes back with code behind it. No consumer used it (`th` builds `features = ["auth"]`). - **CI** (`.github/workflows/rust.yml`): fmt, clippy `--all-targets -D warnings` and the test suite in BOTH feature configurations — the default `no_std` `ui` build and `--all-features`. Running one would report green over half the crate. - **Module-tree guard** (`scripts/check-module-tree.py`): fails if any `.rs` file under `rust/src/` is unreachable from a `mod` declaration. This repo shipped `auth/refresh.rs` — 174 lines — that `mod.rs` never declared, so it never compiled and no other check could see it (fixed in a21c06b). Verified by planting an orphan in `rust/src/auth/`. Tests: 34 with `--all-features`, 6 on default features; fmt + clippy clean in both configurations. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC --- .github/workflows/rust.yml | 56 +++++++++ README.md | 29 ++--- rust/Cargo.toml | 18 ++- rust/build.rs | 17 +++ rust/src/lib.rs | 20 +-- rust/src/llm/mod.rs | 6 - rust/src/ui/mod.rs | 95 ++++---------- scripts/check-module-tree.py | 82 ++++++++++++ shared/monogram.svg | 2 +- shared/styles.css | 60 +++++++++ shared/tokens.json | 161 ++++++++++++++++++------ shared/tokens_codegen.rs | 234 +++++++++++++++++++++++++++++++++++ shared/tokens_css_check.rs | 140 +++++++++++++++++++++ 13 files changed, 773 insertions(+), 147 deletions(-) create mode 100644 .github/workflows/rust.yml create mode 100644 rust/build.rs delete mode 100644 rust/src/llm/mod.rs create mode 100644 scripts/check-module-tree.py create mode 100644 shared/tokens_codegen.rs create mode 100644 shared/tokens_css_check.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..55a4a63 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,56 @@ +name: Rust + +# This repo shipped with no CI at all — `.github/` held only banner images — +# so `rust/src/auth/refresh.rs` could sit for weeks as 174 lines that never +# compiled (no `mod refresh;`), and the design tokens could drift from the CSS +# unnoticed. Every check below exists because something got through without it. + +on: + pull_request: + branches: [main] + push: + branches: [main] + +env: + CARGO_TERM_COLOR: always + +jobs: + rust: + runs-on: ubuntu-latest + defaults: + run: + working-directory: rust + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + + - name: Format check + run: cargo fmt --check + + # `--all-targets` so tests and examples are linted too; `-p`-scoped or + # target-less clippy silently skips test code. + - name: Clippy (default features — the no_std `ui` build) + run: cargo clippy --all-targets -- -D warnings + + - name: Clippy (all features) + run: cargo clippy --all-targets --all-features -- -D warnings + + # Run BOTH feature sets: the `ui` tests are the only ones in the default + # build, and the `auth` tests only exist behind the feature — running one + # configuration would report a green suite that never touched half the crate. + - name: Test (default features) + run: cargo test + + - name: Test (all features) + run: cargo test --all-features + + module-tree: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + # Catches the `refresh.rs` class: a .rs file on disk that no `mod` + # declaration reaches, so it never compiles and no other check sees it. + - name: Every .rs file is reachable from the module tree + run: python3 scripts/check-module-tree.py diff --git a/README.md b/README.md index c988daa..374f6ca 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ --- -> **Every Smoo AI Rust client needs the same three things — so they live in one crate.** Design tokens + the smoo monogram (`ui`), the full Supabase auth story — browser OAuth with PKCE, email+password, session refresh, M2M `client_credentials` — with a shared 0600 on-disk credential store (`auth`), and an LLM session exchange (`llm`, **stub today**). One Rust crate, feature-gated so the bare `ui` build stays `no_std` with zero dependencies. Consumed in production by the [`th` CLI](https://github.com/SmooAI/smooth). **Rust-only today; not yet on crates.io** — npm / NuGet / PyPI siblings are planned, not built. +> **Every Smoo AI Rust client needs the same three things — so they live in one crate.** Design tokens + the smoo monogram (`ui`), the full Supabase auth story — browser OAuth with PKCE, email+password, session refresh, M2M `client_credentials` — with a shared 0600 on-disk credential store (`auth`), One Rust crate, feature-gated so the bare `ui` build stays `no_std` with zero dependencies. Consumed in production by the [`th` CLI](https://github.com/SmooAI/smooth). **Rust-only today; not yet on crates.io** — npm / NuGet / PyPI siblings are planned, not built. ## What is this? @@ -29,9 +29,9 @@ A Smoo AI Rust client (smooblue, observability-studio, `th`, `smoo admin`, …) 1. **Design tokens + monogram** — so the UI looks like Smoo AI. 2. **Auth** — Supabase user OAuth (browser login), email+password, session refresh, AND M2M `client_credentials` grant (service accounts), with one shared on-disk `CredentialsStore`. -3. **LLM access** — exchanging a user session JWT for an org-scoped `llm.smoo.ai` bearer. *(Not implemented yet — see [Honest status](#honest-status).)* +3. **LLM access** — exchanging a user session JWT for an org-scoped `llm.smoo.ai` bearer. *(Not built — there is deliberately no feature flag for it yet; see [Honest status](#honest-status).)* -Each of these has been re-implemented in every consumer at least once. This crate makes them one dependency. It absorbs the standalone [`SmooAI/ui`](https://github.com/SmooAI/ui) crate: `ui` is one module among siblings (`auth`, `llm`) — same constants, same paths, byte-identical `shared/` sources. +Each of these has been re-implemented in every consumer at least once. This crate makes them one dependency. It absorbs the standalone [`SmooAI/ui`](https://github.com/SmooAI/ui) crate: `ui` is one module alongside `auth` — same constants, same paths. This repo's `shared/` is the **source of truth** for the design system; SmooAI/ui carries a copy, and a CI gate there fails if the two ever diverge. ```mermaid %%{init: {'theme':'base','themeVariables':{ @@ -42,7 +42,6 @@ flowchart LR subgraph CRATE["smooai-client-shared"] UI["ui (default)
STYLES · MONOGRAM_SVG · tokens::*
zero deps · no_std"] AUTH["auth (feature)
oauth · password · refresh · m2m
CredentialsStore (0600)"] - LLM["llm (feature)
STUB — pending"] end AUTH -->|"PKCE localhost callback"| SB[("Supabase
/auth/v1")] AUTH -->|"client_credentials"| TOK[("auth.smoo.ai/token")] @@ -162,15 +161,16 @@ smooai-client-shared = { git = "https://github.com/SmooAI/client-shared.git", fe | --- | --- | --- | --- | | `ui` (default) | `STYLES`, `MONOGRAM_SVG`, `tokens::*` | nothing — `no_std` | ✅ working | | `auth` | Supabase OAuth + password + refresh, M2M, `CredentialsStore` | `tokio`, `reqwest`, `axum`, `serde`, … | ✅ working, 28 unit tests | -| `llm` | JWT → `llm.smoo.ai` org-session exchange | (implies `auth`) | 🚧 **stub — compiles, no usable surface** (pearl th-f7b20f) | -Run the tests yourself — 32 unit tests across `ui` + `auth` (OAuth callback/PKCE, token rotation, store round-trips, permission bits): +An `llm` feature (JWT → `llm.smoo.ai` org-session exchange, pearl th-f7b20f) is **planned and deliberately absent**. It previously existed as a flag over a six-line doc-comment module: `--features llm` compiled and produced nothing, which is worse than an honest gap. It returns when there is code behind it. + +Run the tests yourself — 34 unit tests across `ui` + `auth` (OAuth callback/PKCE, token rotation, store round-trips, permission bits, token/CSS drift): ```bash -cd rust && cargo test --features auth,llm +cd rust && cargo test --all-features ``` -There is no CI in this repo yet — run the tests locally before merging. +CI (`.github/workflows/rust.yml`) runs `cargo fmt --check`, `clippy --all-targets -D warnings` and the test suite in **both** feature configurations — the default `no_std` `ui` build and `--all-features` — plus a module-tree check that fails if any `.rs` file is unreachable from a `mod` declaration. --- @@ -178,9 +178,9 @@ There is no CI in this repo yet — run the tests locally before merging. | Surface | Status | | --- | --- | -| **Rust `ui`** | ✅ Working — byte-identical to the `smooai-ui` crate's surface; drift-tested against `shared/styles.css` | +| **Rust `ui`** | ✅ Working — the `tokens` constants are **generated** from `shared/tokens.json` at build time, and cross-checked against `shared/styles.css` in both directions (every token matches its custom property; every colour the CSS declares has a token) | | **Rust `auth`** | ✅ Working — OAuth PKCE localhost-callback (387 LOC), password grant, refresh with rotation, M2M, 0600 `CredentialsStore`; 28 unit tests; consumed by the `th` CLI in production | -| **Rust `llm`** | 🚧 **Stub** — the feature flag exists and compiles, but the module is a doc-comment placeholder. Building with `--features llm` gives you no usable API today | +| **Rust `llm`** | ❌ Not built — no module, no feature flag. Pearl th-f7b20f tracks it | | **crates.io** | ❌ Not published — git dependency is the only install path | | **npm / NuGet / PyPI** | 📦 Planned, no code — the `src/`, `dotnet/`, `python/` directories in the layout below don't exist yet | @@ -191,14 +191,15 @@ client-shared/ ├── shared/ # cross-language source of truth │ ├── styles.css # OKLCH tokens + base component CSS │ ├── monogram.svg # smoo monogram -│ └── tokens.json # tokens as plain JSON (no consumer yet) +│ ├── tokens.json # THE token source — the Rust `tokens` module is generated from it +│ ├── tokens_codegen.rs # generator, run from rust/build.rs +│ └── tokens_css_check.rs # asserts tokens.json <-> styles.css agree, both ways └── rust/ # smooai-client-shared (git dependency; crates.io planned) ├── Cargo.toml └── src/ ├── lib.rs ├── ui/ # lifted verbatim from smooai-ui - ├── auth/ # oauth · password · refresh · m2m · storage (feature = "auth") - └── llm/ # STUB — pending pearl th-f7b20f (feature = "llm") + └── auth/ # oauth · password · refresh · m2m · storage (feature = "auth") ``` npm (`src/`), NuGet (`dotnet/`), and PyPI (`python/`) packages are roadmap, not directories. @@ -240,7 +241,7 @@ The `ui` module is API-compatible with `smooai-ui`: same constants, same paths, ## 🤝 Contributing -PRs welcome. `cd rust && cargo test --features auth,llm` must pass; keep the bare `ui` build zero-dep and `no_std`, and gate anything heavier behind a feature flag. +PRs welcome. `cd rust && cargo test --all-features` must pass; keep the bare `ui` build zero-dep and `no_std`, and gate anything heavier behind a feature flag. ## 📄 License diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 94ae2a0..7de4482 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -3,10 +3,10 @@ name = "smooai-client-shared" version = "0.1.0" edition = "2021" license = "MIT" -description = "SmooAI's cross-runtime client shared library — design tokens, monogram, auth, and llm session primitives shared across every SmooAI Rust app (smooblue, observability-studio, th, th admin, …)." +description = "SmooAI's cross-runtime client shared library — design tokens, monogram, and auth primitives shared across every SmooAI Rust app (smooblue, observability-studio, th, th admin, …)." repository = "https://github.com/SmooAI/client-shared" homepage = "https://github.com/SmooAI/client-shared" -keywords = ["smooai", "design-system", "auth", "llm", "client"] +keywords = ["smooai", "design-system", "auth", "client"] categories = ["gui", "authentication", "config"] readme = "README.md" authors = ["SmooAI "] @@ -16,10 +16,13 @@ authors = ["SmooAI "] include = [ "Cargo.toml", "README.md", + "build.rs", "src/**/*.rs", "../shared/styles.css", "../shared/monogram.svg", "../shared/tokens.json", + "../shared/tokens_codegen.rs", + "../shared/tokens_css_check.rs", "../LICENSE", ] @@ -27,14 +30,19 @@ include = [ # Default to the lightweight `ui` module so existing smooai-ui # consumers (smooblue, observability-studio) get the same dependency # tree they had before — no tokio, no reqwest, still no_std-friendly. -# Opt into `auth` / `llm` only when needed. +# Opt into `auth` only when needed. default = ["ui"] ui = [] auth = ["dep:tokio", "dep:reqwest", "dep:serde", "dep:serde_json", "dep:url", "dep:axum", "dep:anyhow", "dep:chrono", "dep:dirs-next", "dep:webbrowser", "dep:rand", "dep:base64", "dep:sha2"] -llm = ["auth"] + +[build-dependencies] +# Build-script only: parses shared/tokens.json to generate the `tokens` +# module. Build dependencies are host-side and never reach consumers, so the +# bare `ui` build stays zero-dependency and `no_std`. +serde_json = "1" [dependencies] -# Heavy deps gated behind `auth` / `llm` features so the bare `ui` +# Heavy deps gated behind the `auth` feature so the bare `ui` # build stays no_std-compatible and zero-dep. tokio = { version = "1", features = ["macros", "rt-multi-thread", "fs", "net", "sync", "time"], optional = true } reqwest = { version = "0.12", features = ["json"], optional = true } diff --git a/rust/build.rs b/rust/build.rs new file mode 100644 index 0000000..a44518c --- /dev/null +++ b/rust/build.rs @@ -0,0 +1,17 @@ +//! Generate the `tokens` module from `shared/tokens.json`. +//! +//! The generator itself lives in `shared/tokens_codegen.rs` so SmooAI/ui and +//! SmooAI/client-shared generate identically from the identical input — the +//! `shared/**` drift gate keeps those files byte-for-byte equal. This build +//! script is only the shim that feeds it. + +include!("../shared/tokens_codegen.rs"); + +fn main() { + println!("cargo:rerun-if-changed=../shared/tokens.json"); + println!("cargo:rerun-if-changed=../shared/tokens_codegen.rs"); + + let json = std::fs::read_to_string("../shared/tokens.json").expect("read shared/tokens.json"); + let out = std::path::Path::new(&std::env::var("OUT_DIR").expect("OUT_DIR")).join("tokens.rs"); + std::fs::write(out, generate_tokens_rs(&json)).expect("write tokens.rs"); +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 67e7d8c..473713f 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -7,8 +7,7 @@ //! `smoo admin` (the Smooth CLI), and any future Rust client. //! //! Replaces the standalone `smooai-ui` crate (which only carried the -//! `ui` slice) by adding `auth` and `llm` modules behind feature -//! flags. The bare `default-features = ["ui"]` build stays +//! `ui` slice) by adding an `auth` module behind a feature flag. The bare `default-features = ["ui"]` build stays //! `no_std`-compatible with zero runtime dependencies — same shape as //! the old `smooai-ui` so existing consumers don't inherit any new //! tree. @@ -18,10 +17,14 @@ //! - `ui` (default) — design tokens, base CSS, monogram. Zero deps, //! `no_std`. //! - `auth` — Supabase user OAuth (localhost-callback flow), M2M -//! `client_credentials` grant, on-disk `CredentialsStore`. Pulls -//! in `tokio`, `reqwest`, `serde`, `axum`. -//! - `llm` — JWT → `llm.smoo.ai` org-scoped LLM session exchange. -//! Implies `auth`. +//! `client_credentials` grant, refresh-token rotation, on-disk +//! `CredentialsStore`. Pulls in `tokio`, `reqwest`, `serde`, `axum`. +//! +//! An `llm` feature (JWT → `llm.smoo.ai` org-scoped session exchange) +//! is planned under pearl th-f7b20f. It is deliberately **absent** +//! rather than stubbed: a feature flag that compiles to an empty +//! module advertises a capability that does not exist. It comes back +//! when there is something behind it. //! //! ## Migrating from `smooai-ui` //! @@ -42,7 +45,7 @@ //! path it lived at under `smooai_ui::` (e.g. `STYLES`, //! `MONOGRAM_SVG`, `tokens::*`). -#![cfg_attr(not(any(feature = "auth", feature = "llm")), no_std)] +#![cfg_attr(not(feature = "auth"), no_std)] #![doc(html_root_url = "https://docs.rs/smooai-client-shared/0.1.0")] #![warn(missing_docs)] @@ -51,6 +54,3 @@ pub mod ui; #[cfg(feature = "auth")] pub mod auth; - -#[cfg(feature = "llm")] -pub mod llm; diff --git a/rust/src/llm/mod.rs b/rust/src/llm/mod.rs deleted file mode 100644 index 0d463c5..0000000 --- a/rust/src/llm/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! # llm — JWT → llm.smoo.ai org-scoped LLM session exchange. -//! -//! **PENDING (pearl th-f7b20f)**. This module is stubbed so the -//! `#[cfg(feature = "llm")] pub mod llm;` declaration in `lib.rs` -//! resolves cleanly — building with `--features llm` today produces -//! no usable surface; landing pearl th-f7b20f fills it in. diff --git a/rust/src/ui/mod.rs b/rust/src/ui/mod.rs index f5ad7e5..a86cfde 100644 --- a/rust/src/ui/mod.rs +++ b/rust/src/ui/mod.rs @@ -15,52 +15,23 @@ pub const STYLES: &str = include_str!("../../../shared/styles.css"); /// backdrop. pub const MONOGRAM_SVG: &str = include_str!("../../../shared/monogram.svg"); -/// Brand + semantic token *values* as `&'static str`, for code paths -/// that need a colour outside of CSS (custom-painted egui widgets, -/// native menu chrome, chart libraries, etc.). The single source of -/// truth is the CSS in [`STYLES`]; these constants are mirrored from -/// it and validated by the `tokens_match_css` test below. +/// Brand + semantic token *values*, for code paths that need a colour, +/// radius, spacing step, or font stack outside of CSS (custom-painted egui +/// widgets, native menu chrome, chart libraries, etc.). +/// +/// **Generated** at build time from +/// [`shared/tokens.json`](https://github.com/SmooAI/client-shared/blob/main/shared/tokens.json) +/// by `shared/tokens_codegen.rs` — nobody hand-writes these, so a token cannot +/// exist in the design system and be missing here. `shared/tokens_css_check.rs` +/// then asserts each one equals the resolved value of its custom property in +/// [`STYLES`], and that every colour the CSS declares has a token. pub mod tokens { - /// Brand orange — primary CTA stop, accent. - pub const SMOOAI_ORANGE: &str = "oklch(0.769 0.164 71)"; - /// Brand red — destructive, "like" affordance. - pub const SMOOAI_RED: &str = "oklch(0.712 0.181 22.4)"; - /// Brand green — confirm / "repost" affordance / focus ring. - pub const SMOOAI_GREEN: &str = "oklch(0.657 0.112 194.8)"; - /// Lightest brand blue — reply hover. - pub const SMOOAI_BLUE_300: &str = "oklch(0.803 0.074 230.9)"; - /// Mid brand blue. - pub const SMOOAI_BLUE_400: &str = "oklch(0.725 0.102 233.4)"; - /// Active nav-rail item background. - pub const SMOOAI_BLUE_500: &str = "oklch(0.55 0.13 233)"; - /// Brand dark blue — base background. - pub const SMOOAI_DARK_BLUE: &str = "oklch(0.13 0.043 265.1)"; - /// Sidebar tint. - pub const SMOOAI_DARK_BLUE_850: &str = "oklch(0.177 0.074 266)"; - /// Hover on rail items. - pub const SMOOAI_DARK_BLUE_700: &str = "oklch(0.303 0.154 265.8)"; - /// Brand white. - pub const SMOOAI_WHITE: &str = "oklch(0.984 0.003 247.9)"; - /// Muted text. - pub const SMOOAI_GRAY_400: &str = "oklch(0.715 0 89.9)"; + include!(concat!(env!("OUT_DIR"), "/tokens.rs")); - /// Default page background. - pub const BACKGROUND: &str = "oklch(0.145 0.014 265)"; - /// Default text color. - pub const FOREGROUND: &str = SMOOAI_WHITE; - /// Card surface background. - pub const CARD: &str = "oklch(0.205 0.015 265)"; - /// Border / divider. - pub const BORDER: &str = "oklch(0.3 0.008 260)"; - - /// Signature brand gradient — gradient-as-string for `background: …;`. - /// Note that CSS doesn't accept gradients in inline `color:` — this is - /// for `background` and SVG paint only. - pub const GRADIENT_BRAND: &str = - "linear-gradient(135deg, oklch(0.769 0.164 71) 0%, oklch(0.712 0.181 22.4) 100%)"; - - /// Default radius for cards + buttons (matches `--radius` in CSS). - pub const RADIUS_PX: u16 = 10; + /// The default corner radius, as used by cards and buttons. Retained as an + /// alias of [`RADIUS_MD_PX`] so consumers pinned to the pre-codegen name + /// keep compiling. + pub const RADIUS_PX: u16 = RADIUS_MD_PX; } #[cfg(test)] @@ -81,35 +52,9 @@ mod tests { assert!(MONOGRAM_SVG.contains("fill=\"currentColor\"")); } - /// Validate that every token value in the `tokens` module is also - /// present in the CSS, so the two sources of truth can't silently - /// drift. - #[test] - fn tokens_match_css() { - let css = STYLES; - for (name, value) in [ - ("SMOOAI_ORANGE", tokens::SMOOAI_ORANGE), - ("SMOOAI_RED", tokens::SMOOAI_RED), - ("SMOOAI_GREEN", tokens::SMOOAI_GREEN), - ("SMOOAI_BLUE_300", tokens::SMOOAI_BLUE_300), - ("SMOOAI_BLUE_400", tokens::SMOOAI_BLUE_400), - ("SMOOAI_BLUE_500", tokens::SMOOAI_BLUE_500), - ("SMOOAI_DARK_BLUE", tokens::SMOOAI_DARK_BLUE), - ("SMOOAI_DARK_BLUE_850", tokens::SMOOAI_DARK_BLUE_850), - ("SMOOAI_DARK_BLUE_700", tokens::SMOOAI_DARK_BLUE_700), - ("SMOOAI_WHITE", tokens::SMOOAI_WHITE), - ("SMOOAI_GRAY_400", tokens::SMOOAI_GRAY_400), - ("BACKGROUND", tokens::BACKGROUND), - ("CARD", tokens::CARD), - ("BORDER", tokens::BORDER), - ] { - assert!( - css.contains(value), - "token {name} = {value:?} not found in shared/styles.css — Rust \ - constants and CSS have drifted", - ); - } - } + // The token <-> CSS cross-check (both directions) lives in `shared/`, so + // SmooAI/client-shared and SmooAI/ui run the identical assertions. + include!("../../../shared/tokens_css_check.rs"); #[test] fn semantic_classes_exist() { @@ -123,6 +68,10 @@ mod tests { ".rail", ".rail__btn", ".brand-badge", + ".input", + ".input--lg", + ".input-error", + ".input-hint", ] { assert!(STYLES.contains(cls), "missing class {cls}"); } diff --git a/scripts/check-module-tree.py b/scripts/check-module-tree.py new file mode 100644 index 0000000..8cb8024 --- /dev/null +++ b/scripts/check-module-tree.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +"""Fail if any .rs file under rust/src/ is unreachable from the module tree. + +An orphaned file compiles to nothing and is invisible to every other check: +`cargo build`, `clippy`, `cargo test` and code coverage all skip it, because as +far as rustc is concerned it does not exist. Reviewers see a diff full of real +code and assume it ships. + +SmooAI/client-shared shipped exactly that: 174 lines of auth token-refresh code +in `rust/src/auth/refresh.rs` that `auth/mod.rs` never declared, so it never +compiled once (fixed upstream in a21c06b). This is the check that would have +caught it the same day. + +Deliberately textual rather than a real parse: a `mod foo;` behind a `#[cfg(...)]` +still counts as declared, which is what we want — the question is whether the +file is wired in at all, not whether it is wired in for every feature set. +""" + +import re +import sys +from pathlib import Path + +SRC = Path(__file__).resolve().parent.parent / "rust" / "src" + + +def declares(parent: Path, name: str) -> bool: + """Whether `parent` contains a `mod ;` / `mod { … }` declaration.""" + if not parent.is_file(): + return False + return re.search(rf"\bmod\s+{re.escape(name)}\s*[;{{]", parent.read_text()) is not None + + +ROOTS = [SRC / "lib.rs", SRC / "main.rs"] + + +def module_of(rs: Path) -> tuple[str, list[Path]]: + """The module name `rs` defines, and the file(s) that could declare it.""" + if rs.name == "mod.rs": + # `src/a/mod.rs` defines module `a`, declared one level up. + name, parent_dir = rs.parent.name, rs.parent.parent + else: + # `src/a/b.rs` defines module `b`, declared in `src/a`. + name, parent_dir = rs.stem, rs.parent + + if parent_dir == SRC: + return name, ROOTS + # Either module style: `src/a/mod.rs` or the 2018-edition `src/a.rs`. + return name, [parent_dir / "mod.rs", parent_dir.with_suffix(".rs")] + + +def main() -> int: + if not SRC.is_dir(): + print(f"no {SRC} — nothing to check") + return 0 + + repo = SRC.parent.parent + orphans = [] + + for rs in sorted(SRC.rglob("*.rs")): + if rs in ROOTS: + continue + name, candidates = module_of(rs) + if not any(declares(c, name) for c in candidates): + where = " or ".join(str(c.relative_to(repo)) for c in candidates) + orphans.append(f" {rs.relative_to(repo)} — no `mod {name};` in {where}") + + if orphans: + print("Orphaned module files (present on disk, never compiled):", file=sys.stderr) + print("\n".join(orphans), file=sys.stderr) + print( + "\nDeclare each with `mod ;` / `pub mod ;` in its parent, " + "or delete the file.", + file=sys.stderr, + ) + return 1 + + print(f"✓ every .rs file under {SRC.relative_to(repo)} is in the module tree") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/shared/monogram.svg b/shared/monogram.svg index 18965ef..7fd88a4 100644 --- a/shared/monogram.svg +++ b/shared/monogram.svg @@ -1,3 +1,3 @@ - + diff --git a/shared/styles.css b/shared/styles.css index 18cd013..65384c2 100644 --- a/shared/styles.css +++ b/shared/styles.css @@ -328,6 +328,66 @@ input { color: var(--color-smooai-white); } +/* =========================================================== + * Text inputs + textareas + * + * Single canonical form-field style so every consumer's auth screen, + * search bar, settings field, and compose box look like they came from + * the same product. Apply `.input` to ,