extract the bchen.dev fleet's shared CSS tokens + framework-less UI components #1

Closed
opened 2026-05-13 19:02:17 +00:00 by brendan · 1 comment
Owner

Originally filed: 2026-05-11 in ~/features.md, block #17.

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; consumers npm install from 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 increments

Motivation. 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):

  • Package name: bchen-ui (scoped: @bchen/ui in package.json). Repo at gitea.bchen.dev/brendan/bchen-ui.
  • Repo visibility: PUBLIC. All other bchen.dev app repos are private; this one is the exception, mostly for simplicity (no auth fiddling for npm/git installs, no token-juggling in CI, no permission gymnastics if the user later open-sources a consumer or shares a snippet externally). Nothing sensitive lives in the package — it's CSS tokens, plain ES modules, and skeleton HTML/JS. Public is the correct default here.
  • Distribution mechanism: git-URL npm install, not a Gitea-hosted npm registry. Consumers add a dep line like "@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.
  • No build step in the package. Ship plain CSS files and plain ES modules under dist/. Consumers' HTML loads them directly via <link rel="stylesheet"> and <script type="module">. No bundler. No tsc-emit-to-js (the package's .js files 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.
  • No framework primitives, no stateful components. Static markup + CSS + small DOM-attachment scripts only. Anything that needs state stays in the consuming app.
  • Tests in the package are CSS-shape + plain-DOM assertions, mirroring the existing tests/unit/style-css.test.ts pattern used in buchinese/inventory. No headless browser, no Playwright (those stay at the consumer level).

Architecture.

bchen-ui/
├── dist/
│   ├── tokens.css              # :root semantic-token palette + dark @media block (light + dark)
│   ├── base.css                # reset-lite, typography, the universal 16px form-control rule
│   ├── components/
│   │   ├── buttons.css         # .btn, .btn-primary, .btn-ghost, .btn-danger
│   │   ├── forms.css           # input/textarea/select/label/fieldset styling on top of base.css
│   │   ├── cards.css           # .card primitive
│   │   └── header.css          # the chrome shared by all apps
│   ├── scripts/
│   │   ├── header.js           # ES module: attaches mobile-menu toggle, active-link logic
│   │   └── login-redirect.js   # ES module: the authd-redirect boilerplate (read URL params, hit /auth/start)
│   ├── pwa/
│   │   ├── manifest.template.webmanifest  # placeholder fields filled at consumer build time
│   │   └── sw-skeleton.mjs     # the file-manifest-inlined service worker shape (buchinese/inventory/dashcam all run a near-identical SW today)
│   └── html/
│       └── login.template.html # the login page shape used across apps
├── tests/
│   ├── tokens.test.ts          # asserts token names + WCAG AA contrast for light + dark pairs
│   ├── base.test.ts            # asserts the 16px form-input rule is present
│   └── components.test.ts      # per-component CSS-shape assertions
├── package.json                # name=@bchen/ui, version, files=["dist"], exports map, no build script
├── README.md                   # consumer install instructions + asset-copy snippet for Dockerfile
├── CHANGELOG.md                # semver + per-version notes (the package is shared, drift is the enemy)
├── .gitea/workflows/release.yml  # tag-triggered: runs vitest, asserts CHANGELOG has an entry for the tag, attaches dist/ as release artifact
└── .claude/settings.local.json # sandbox config copied from nanodrop's per the bootstrap-rule in ~/roles/_sub-claude-rules.md

Consumer integration shape (each consuming app does this once at port-in time; documented in the package README):

  1. Add to package.json:
    "dependencies": {
      "@bchen/ui": "git+ssh://git@gitea.bchen.dev:2222/brendan/bchen-ui.git#v0.1.0"
    }
    
  2. In the Dockerfile (after npm ci, before the final stage), copy assets into public/vendor/bchen-ui/:
    RUN cp -r node_modules/@bchen/ui/dist /app/public/vendor/bchen-ui
    
  3. HTML pages link them directly:
    <link rel="stylesheet" href="/vendor/bchen-ui/tokens.css">
    <link rel="stylesheet" href="/vendor/bchen-ui/base.css">
    <link rel="stylesheet" href="/vendor/bchen-ui/components/buttons.css">
    <script type="module" src="/vendor/bchen-ui/scripts/header.js"></script>
    
  4. App-local CSS goes in a separate file loaded after the vendor files so cascade overrides work the conventional way (<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:

  • Take the current :root { ... } light block + the @media (prefers-color-scheme: dark) { :root { ... } } block from one canonical source (recommend ~/inventory/public/style.css since inventory's was the reference for the most recent roll-out — confirm via diff against ~/authd/public/style.css and ~/buchinese/public/style.css that they're token-name-identical and value-near-identical).
  • Drop those two blocks into bchen-ui/dist/tokens.css as-is.
  • Add the universal input, textarea, select { font-size: 16px; } rule to bchen-ui/dist/base.css (it's the other universal rule that's already deployed everywhere).
  • Tests: assert each token name is defined in both light and dark scopes; assert WCAG AA contrast (≥ 4.5:1 for body text, ≥ 3.0:1 for UI borders) for the key foreground/background pairs in both modes (--fg/--bg, --fg-muted/--bg, --fg-subtle/--bg, --border/--bg); assert base.css contains the 16px form-input rule.
  • Cut version v0.1.0, tag, publish via Gitea release (workflow attaches dist/ tarball).
  • Do NOT migrate any consumer in this PR. Consumer migrations are separate follow-up features (one per app), so each one can be reviewed and deployed independently and rolled back independently if anything goes wrong.

Versioning + change-discipline (the part that makes this not become a footgun).

  • Strict semver. Pre-1.0 anything-goes is fine for the first few months, but breaking changes within 0.x still get a minor version bump and a CHANGELOG entry. After the second consumer is on board, switch to 1.0.0 and treat breaking changes as full majors.
  • CHANGELOG.md is mandatory, one entry per release, with sections: Added, Changed, Removed. The release workflow refuses to publish a tag whose version isn't already in the CHANGELOG.
  • Token deletions/renames are breaking. New tokens are non-breaking. Value tweaks to existing tokens are non-breaking but should be called out in the CHANGELOG.
  • The package's tests are the contract — if a consumer relies on a token name, that reliance is implicit in 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:

  1. From ~, run tea repo create --name bchen-ui --owner brendan --init (no --private flag — this repo is public per the pre-decision above; verify post-creation with tea repo list | grep bchen-ui and confirm visibility is public, fix via tea repo edit or the Gitea web UI if it defaulted private).
  2. Clone via SSH: tea clone brendan/bchen-ui.
  3. Initial commit on main: README + .gitignore (node_modules/, dist-tmp/, *.log) + .claude/settings.local.json (copy from ~/nanodrop/.claude/settings.local.json as the canonical template per ~/roles/_sub-claude-rules.md; allow network domains registry.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).
  4. Then standard claude --worktree feat/v0.1.0-tokens-extraction -p --permission-mode plan from ~/bchen-ui/ and proceed.

Acceptance:

  • gitea.bchen.dev/brendan/bchen-ui exists, is public, has a v0.1.0 release tag with a dist/ tarball artifact.
  • package.json declares name: "@bchen/ui", version: "0.1.0", type: "module", files: ["dist"], an exports map covering ./tokens.css, ./base.css, ./components/*, ./scripts/*. No dependencies (the package has no runtime deps). Dev-only vitest for the tests.
  • dist/tokens.css contains the canonical light + dark semantic-token palette. dist/base.css contains the 16px form-input rule.
  • tests/ covers token presence in both modes, WCAG AA contrast for key pairs, and the 16px rule. npm test is green.
  • README documents the four-step consumer integration shape with copy-pasteable snippets.
  • CHANGELOG.md has a v0.1.0 entry describing the initial extraction.
  • The .gitea/workflows/release.yml workflow runs on tag push, executes npm ci && npm test, validates the CHANGELOG entry exists for the tag, and attaches dist/ as a release artifact.
  • A smoke-test consumer (just temporarily, inside the feature's own iteration sandbox — NOT a permanent consumer) npm installs the package from the git URL and confirms the files resolve under node_modules/@bchen/ui/dist/. Document the exact npm install command in the PR description.

Out of scope for this PR (filed as follow-up features after this merges):

  • Migrating any of the 6 consumers (authd, buchinese, dashcam, inventory, movement, nanodrop) or firehose to consume the package. Each is its own follow-up feature.
  • Extracting the header chrome, form components, button primitives, card primitive, SW skeleton, manifest template, login-redirect script, or any HTML templates. Each is its own follow-up feature, picked up one at a time in priority order after at least one consumer is happily on v0.1.0.
  • Migrating to a real Gitea npm registry (vs. git-URL install). Defer until either lockfile resolution against git URLs becomes painful or a non-bchen.dev consumer wants the package.
  • A "playground" / docs site for the package (Storybook etc.). Out of scope — the package's tests + the README are the documentation.
  • TypeScript declarations. The package is plain JS + CSS; JSDoc on exported functions is sufficient. If a consumer ever wants .d.ts, hand-write a tiny index.d.ts at that time rather than introducing a build step.

Notes for the implementer:

  • Branch prefix feat/ (new project, new capability).
  • This is greenfield, so there's no migration risk and no existing-data concerns. The principal risks are (a) accidentally introducing a bundler/build step that creeps in via "just one tiny thing" (resist — every dev-dep should be justified), and (b) shipping CSS that drifts from what's currently deployed in the apps. Mitigate (b) by diffing the proposed tokens.css against the three most recently-resolved dark-mode PRs (nanodrop PR #8, dashcam PR #3, authd PR #13) and confirming byte-equivalent values.
  • Security audit pass is light (public package, no auth surface, no env vars). One thing for the auditor to flag: confirm dist/scripts/*.js files do NOT use eval, new Function, innerHTML-with-untrusted-data, or dangerouslySetInnerHTML-style escape hatches. The package is loaded by every consumer's HTML; an XSS bug here is an XSS bug everywhere.
  • After this merges, file the next two extraction features (recommend: header chrome second, since it's the next-most-duplicated surface across apps; form/button primitives third). Each follow-up should itself follow the same "bootstrap once, port one thing" pattern — small PRs, one new surface per release, CHANGELOG entry per version.
  • Do NOT pre-extract surfaces "while you're in there." The discipline of one-surface-per-PR is what keeps this from becoming a sprawling design-system project.

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.

> **Originally filed:** 2026-05-11 in ~/features.md, block #17. **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; consumers `npm install` from 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 increments** **Motivation.** 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):** - **Package name: `bchen-ui`** (scoped: `@bchen/ui` in `package.json`). Repo at `gitea.bchen.dev/brendan/bchen-ui`. - **Repo visibility: PUBLIC.** All other bchen.dev app repos are private; this one is the exception, mostly for simplicity (no auth fiddling for npm/git installs, no token-juggling in CI, no permission gymnastics if the user later open-sources a consumer or shares a snippet externally). Nothing sensitive lives in the package — it's CSS tokens, plain ES modules, and skeleton HTML/JS. Public is the correct default here. - **Distribution mechanism: git-URL npm install**, not a Gitea-hosted npm registry. Consumers add a dep line like `"@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. - **No build step in the package.** Ship plain CSS files and plain ES modules under `dist/`. Consumers' HTML loads them directly via `<link rel="stylesheet">` and `<script type="module">`. No bundler. No tsc-emit-to-js (the package's `.js` files 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. - **No framework primitives, no stateful components.** Static markup + CSS + small DOM-attachment scripts only. Anything that needs state stays in the consuming app. - **Tests in the package are CSS-shape + plain-DOM assertions**, mirroring the existing `tests/unit/style-css.test.ts` pattern used in buchinese/inventory. No headless browser, no Playwright (those stay at the consumer level). **Architecture.** ``` bchen-ui/ ├── dist/ │ ├── tokens.css # :root semantic-token palette + dark @media block (light + dark) │ ├── base.css # reset-lite, typography, the universal 16px form-control rule │ ├── components/ │ │ ├── buttons.css # .btn, .btn-primary, .btn-ghost, .btn-danger │ │ ├── forms.css # input/textarea/select/label/fieldset styling on top of base.css │ │ ├── cards.css # .card primitive │ │ └── header.css # the chrome shared by all apps │ ├── scripts/ │ │ ├── header.js # ES module: attaches mobile-menu toggle, active-link logic │ │ └── login-redirect.js # ES module: the authd-redirect boilerplate (read URL params, hit /auth/start) │ ├── pwa/ │ │ ├── manifest.template.webmanifest # placeholder fields filled at consumer build time │ │ └── sw-skeleton.mjs # the file-manifest-inlined service worker shape (buchinese/inventory/dashcam all run a near-identical SW today) │ └── html/ │ └── login.template.html # the login page shape used across apps ├── tests/ │ ├── tokens.test.ts # asserts token names + WCAG AA contrast for light + dark pairs │ ├── base.test.ts # asserts the 16px form-input rule is present │ └── components.test.ts # per-component CSS-shape assertions ├── package.json # name=@bchen/ui, version, files=["dist"], exports map, no build script ├── README.md # consumer install instructions + asset-copy snippet for Dockerfile ├── CHANGELOG.md # semver + per-version notes (the package is shared, drift is the enemy) ├── .gitea/workflows/release.yml # tag-triggered: runs vitest, asserts CHANGELOG has an entry for the tag, attaches dist/ as release artifact └── .claude/settings.local.json # sandbox config copied from nanodrop's per the bootstrap-rule in ~/roles/_sub-claude-rules.md ``` **Consumer integration shape** (each consuming app does this once at port-in time; documented in the package README): 1. Add to `package.json`: ```json "dependencies": { "@bchen/ui": "git+ssh://git@gitea.bchen.dev:2222/brendan/bchen-ui.git#v0.1.0" } ``` 2. In the Dockerfile (after `npm ci`, before the final stage), copy assets into `public/vendor/bchen-ui/`: ```dockerfile RUN cp -r node_modules/@bchen/ui/dist /app/public/vendor/bchen-ui ``` 3. HTML pages link them directly: ```html <link rel="stylesheet" href="/vendor/bchen-ui/tokens.css"> <link rel="stylesheet" href="/vendor/bchen-ui/base.css"> <link rel="stylesheet" href="/vendor/bchen-ui/components/buttons.css"> <script type="module" src="/vendor/bchen-ui/scripts/header.js"></script> ``` 4. App-local CSS goes in a separate file loaded *after* the vendor files so cascade overrides work the conventional way (`<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:** - Take the current `:root { ... }` light block + the `@media (prefers-color-scheme: dark) { :root { ... } }` block from one canonical source (recommend `~/inventory/public/style.css` since inventory's was the reference for the most recent roll-out — confirm via diff against `~/authd/public/style.css` and `~/buchinese/public/style.css` that they're token-name-identical and value-near-identical). - Drop those two blocks into `bchen-ui/dist/tokens.css` as-is. - Add the universal `input, textarea, select { font-size: 16px; }` rule to `bchen-ui/dist/base.css` (it's the other universal rule that's already deployed everywhere). - Tests: assert each token name is defined in both light and dark scopes; assert WCAG AA contrast (≥ 4.5:1 for body text, ≥ 3.0:1 for UI borders) for the key foreground/background pairs in both modes (`--fg`/`--bg`, `--fg-muted`/`--bg`, `--fg-subtle`/`--bg`, `--border`/`--bg`); assert base.css contains the 16px form-input rule. - Cut version `v0.1.0`, tag, publish via Gitea release (workflow attaches `dist/` tarball). - **Do NOT migrate any consumer in this PR.** Consumer migrations are separate follow-up features (one per app), so each one can be reviewed and deployed independently and rolled back independently if anything goes wrong. **Versioning + change-discipline (the part that makes this not become a footgun).** - Strict semver. **Pre-1.0 anything-goes** is fine for the first few months, but breaking changes within 0.x still get a minor version bump and a CHANGELOG entry. After the second consumer is on board, switch to 1.0.0 and treat breaking changes as full majors. - CHANGELOG.md is mandatory, one entry per release, with sections: `Added`, `Changed`, `Removed`. The release workflow refuses to publish a tag whose version isn't already in the CHANGELOG. - Token deletions/renames are breaking. New tokens are non-breaking. Value tweaks to existing tokens are non-breaking but should be called out in the CHANGELOG. - The package's tests are the contract — if a consumer relies on a token name, that reliance is implicit in `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: 1. From `~`, run `tea repo create --name bchen-ui --owner brendan --init` (no `--private` flag — this repo is public per the pre-decision above; verify post-creation with `tea repo list | grep bchen-ui` and confirm visibility is public, fix via `tea repo edit` or the Gitea web UI if it defaulted private). 2. Clone via SSH: `tea clone brendan/bchen-ui`. 3. Initial commit on `main`: README + .gitignore (`node_modules/`, `dist-tmp/`, `*.log`) + `.claude/settings.local.json` (copy from `~/nanodrop/.claude/settings.local.json` as the canonical template per `~/roles/_sub-claude-rules.md`; allow network domains `registry.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`). 4. Then standard `claude --worktree feat/v0.1.0-tokens-extraction -p --permission-mode plan` from `~/bchen-ui/` and proceed. **Acceptance:** - `gitea.bchen.dev/brendan/bchen-ui` exists, is **public**, has a v0.1.0 release tag with a `dist/` tarball artifact. - `package.json` declares `name: "@bchen/ui"`, `version: "0.1.0"`, `type: "module"`, `files: ["dist"]`, an `exports` map covering `./tokens.css`, `./base.css`, `./components/*`, `./scripts/*`. No `dependencies` (the package has no runtime deps). Dev-only `vitest` for the tests. - `dist/tokens.css` contains the canonical light + dark semantic-token palette. `dist/base.css` contains the 16px form-input rule. - `tests/` covers token presence in both modes, WCAG AA contrast for key pairs, and the 16px rule. `npm test` is green. - README documents the four-step consumer integration shape with copy-pasteable snippets. - CHANGELOG.md has a v0.1.0 entry describing the initial extraction. - The `.gitea/workflows/release.yml` workflow runs on tag push, executes `npm ci && npm test`, validates the CHANGELOG entry exists for the tag, and attaches `dist/` as a release artifact. - A smoke-test consumer (just temporarily, inside the feature's own iteration sandbox — NOT a permanent consumer) `npm install`s the package from the git URL and confirms the files resolve under `node_modules/@bchen/ui/dist/`. Document the exact `npm install` command in the PR description. **Out of scope for this PR (filed as follow-up features after this merges):** - Migrating any of the 6 consumers (authd, buchinese, dashcam, inventory, movement, nanodrop) or firehose to consume the package. Each is its own follow-up feature. - Extracting the header chrome, form components, button primitives, card primitive, SW skeleton, manifest template, login-redirect script, or any HTML templates. Each is its own follow-up feature, picked up one at a time in priority order after at least one consumer is happily on v0.1.0. - Migrating to a real Gitea npm registry (vs. git-URL install). Defer until either lockfile resolution against git URLs becomes painful or a non-bchen.dev consumer wants the package. - A "playground" / docs site for the package (Storybook etc.). Out of scope — the package's tests + the README are the documentation. - TypeScript declarations. The package is plain JS + CSS; JSDoc on exported functions is sufficient. If a consumer ever wants `.d.ts`, hand-write a tiny `index.d.ts` at that time rather than introducing a build step. **Notes for the implementer:** - Branch prefix `feat/` (new project, new capability). - This is greenfield, so there's no migration risk and no existing-data concerns. The principal risks are (a) accidentally introducing a bundler/build step that creeps in via "just one tiny thing" (resist — every dev-dep should be justified), and (b) shipping CSS that drifts from what's currently deployed in the apps. Mitigate (b) by diffing the proposed `tokens.css` against the three most recently-resolved dark-mode PRs (nanodrop PR #8, dashcam PR #3, authd PR #13) and confirming byte-equivalent values. - Security audit pass is light (public package, no auth surface, no env vars). One thing for the auditor to flag: confirm `dist/scripts/*.js` files do NOT use `eval`, `new Function`, `innerHTML`-with-untrusted-data, or `dangerouslySetInnerHTML`-style escape hatches. The package is loaded by every consumer's HTML; an XSS bug here is an XSS bug everywhere. - After this merges, file the next two extraction features (recommend: header chrome second, since it's the next-most-duplicated surface across apps; form/button primitives third). Each follow-up should itself follow the same "bootstrap once, port one thing" pattern — small PRs, one new surface per release, CHANGELOG entry per version. - Do NOT pre-extract surfaces "while you're in there." The discipline of one-surface-per-PR is what keeps this from becoming a sprawling design-system project. 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.
brendan added the feature label 2026-05-13 19:02:17 +00:00
Author
Owner

Resolved: PR #2 (merge_commit 68e6cc3b80)

Resolved: PR https://gitea.bchen.dev/brendan/bchen-ui/pulls/2 (merge_commit 68e6cc3b8004c94ef4dbdc4b4ee3f801409e56ed)
Sign in to join this conversation.