extract the bchen.dev fleet's shared CSS tokens + framework-less UI components #1
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
2026-05-11 — new project
bchen-ui: extract the bchen.dev fleet's shared CSS tokens + framework-less UI components into a public git-installable npm package — preserves the no-bundler / no-framework invariant; consumersnpm installfrom a Gitea git URL and serve assets statically; first extraction is the dark-mode semantic-token palette, with header/forms/SW-skeleton/manifest/login-redirect as later incrementsMotivation. Six apps (authd, buchinese, dashcam, inventory, movement, nanodrop) plus the new firehose project all share a monochrome house style and have converged on near-identical implementations of the same surfaces: the semantic-token CSS palette (just landed via the cross-project dark-mode roll-out), the universal
input,textarea,select{font-size:16px}iOS-focus-zoom rule, button/form/card primitives, header/menu chrome, the authd-redirect login boilerplate, and the PWA manifest + service-worker skeleton. The drift cost is real and growing — the dark-mode roll-out alone was 6 nearly-identical PRs, and the 16px-iOS rule was 5. Extracting these into a shared package converts that N× diff cost into one PR per change. The constraint is the framework-less invariant (no React/Vue/Svelte/bundler/JSX) — that's load-bearing for every project here and must stay intact.Pre-decisions (settled with user 2026-05-11):
bchen-ui(scoped:@bchen/uiinpackage.json). Repo atgitea.bchen.dev/brendan/bchen-ui."@bchen/ui": "git+ssh://git@gitea.bchen.dev:2222/brendan/bchen-ui.git#v0.1.0"(SSH per the global rule in~/.claude/CLAUDE.md). Version pinning happens via git tags + lockfile resolution. Picked over Verdaccio / Gitea's built-in npm registry because zero extra infra; can migrate to a registry later if/when this hurts.dist/. Consumers' HTML loads them directly via<link rel="stylesheet">and<script type="module">. No bundler. No tsc-emit-to-js (the package's.jsfiles are hand-authored, not transpiled from TypeScript — JSDoc types if any). This keeps the invariant tight: a future maintainer who only knows HTML/CSS/JS can edit the package.tests/unit/style-css.test.tspattern used in buchinese/inventory. No headless browser, no Playwright (those stay at the consumer level).Architecture.
Consumer integration shape (each consuming app does this once at port-in time; documented in the package README):
package.json:npm ci, before the final stage), copy assets intopublic/vendor/bchen-ui/:<link rel="stylesheet" href="/style.css">after the bchen-ui links).Scope of this PR (the bootstrap). This feature item creates the package and ports ONE thing — the dark-mode semantic-token palette — as the first extraction. That's the lowest-risk first move because (a) it's brand-new code from the just-completed cross-project dark-mode roll-out, (b) it's already standardized across all 6 apps, (c) it's CSS-only with no JS or DOM shape involved, (d) byte-equivalent diffs across apps make verification mechanical. After this lands, follow-up features port the next surfaces (header, forms, SW skeleton, etc.) one at a time, each as its own item.
Concrete extraction in this PR — the semantic-token palette:
:root { ... }light block + the@media (prefers-color-scheme: dark) { :root { ... } }block from one canonical source (recommend~/inventory/public/style.csssince inventory's was the reference for the most recent roll-out — confirm via diff against~/authd/public/style.cssand~/buchinese/public/style.cssthat they're token-name-identical and value-near-identical).bchen-ui/dist/tokens.cssas-is.input, textarea, select { font-size: 16px; }rule tobchen-ui/dist/base.css(it's the other universal rule that's already deployed everywhere).--fg/--bg,--fg-muted/--bg,--fg-subtle/--bg,--border/--bg); assert base.css contains the 16px form-input rule.v0.1.0, tag, publish via Gitea release (workflow attachesdist/tarball).Versioning + change-discipline (the part that makes this not become a footgun).
Added,Changed,Removed. The release workflow refuses to publish a tag whose version isn't already in the CHANGELOG.tokens.test.ts. Deletion = test removal = visible diff.Repo bootstrap (first-iteration variation, mirrors firehose's pattern). The implementer's normal flow assumes the repo exists. For this feature's first iteration:
~, runtea repo create --name bchen-ui --owner brendan --init(no--privateflag — this repo is public per the pre-decision above; verify post-creation withtea repo list | grep bchen-uiand confirm visibility is public, fix viatea repo editor the Gitea web UI if it defaulted private).tea clone brendan/bchen-ui.main: README + .gitignore (node_modules/,dist-tmp/,*.log) +.claude/settings.local.json(copy from~/nanodrop/.claude/settings.local.jsonas the canonical template per~/roles/_sub-claude-rules.md; allow network domainsregistry.npmjs.org,*.npmjs.org,nodejs.org,*.nodejs.org,gitea.bchen.dev,*.bchen.dev,api.anthropic.com,*.anthropic.com; allow write to/home/brendan/bchen-ui,/home/brendan/bchen-ui/.git,/home/brendan/.npm,/home/brendan/.config/tea).claude --worktree feat/v0.1.0-tokens-extraction -p --permission-mode planfrom~/bchen-ui/and proceed.Acceptance:
gitea.bchen.dev/brendan/bchen-uiexists, is public, has a v0.1.0 release tag with adist/tarball artifact.package.jsondeclaresname: "@bchen/ui",version: "0.1.0",type: "module",files: ["dist"], anexportsmap covering./tokens.css,./base.css,./components/*,./scripts/*. Nodependencies(the package has no runtime deps). Dev-onlyvitestfor the tests.dist/tokens.csscontains the canonical light + dark semantic-token palette.dist/base.csscontains the 16px form-input rule.tests/covers token presence in both modes, WCAG AA contrast for key pairs, and the 16px rule.npm testis green..gitea/workflows/release.ymlworkflow runs on tag push, executesnpm ci && npm test, validates the CHANGELOG entry exists for the tag, and attachesdist/as a release artifact.npm installs the package from the git URL and confirms the files resolve undernode_modules/@bchen/ui/dist/. Document the exactnpm installcommand in the PR description.Out of scope for this PR (filed as follow-up features after this merges):
.d.ts, hand-write a tinyindex.d.tsat that time rather than introducing a build step.Notes for the implementer:
feat/(new project, new capability).tokens.cssagainst the three most recently-resolved dark-mode PRs (nanodrop PR #8, dashcam PR #3, authd PR #13) and confirming byte-equivalent values.dist/scripts/*.jsfiles do NOT useeval,new Function,innerHTML-with-untrusted-data, ordangerouslySetInnerHTML-style escape hatches. The package is loaded by every consumer's HTML; an XSS bug here is an XSS bug everywhere.Source: spawn-host conversation with user (2026-05-11), discussed as the natural next step after the cross-project dark-mode roll-out highlighted how much duplicate work the framework-less convention costs across 6+ apps.
Resolved: PR #2 (merge_commit
68e6cc3b80)