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 ,