diff --git a/.claude/skills/streamplace-design/SKILL.md b/.claude/skills/streamplace-design/SKILL.md new file mode 100644 index 000000000..8a46829a1 --- /dev/null +++ b/.claude/skills/streamplace-design/SKILL.md @@ -0,0 +1,185 @@ +--- +name: streamplace-design +description: Streamplace design system and token discipline for UI work. Use when styling components under js/app, js/components, or js/web, picking colors/typography/spacing/radius/motion, hitting a token-ratchet or check-tokens failure, or deciding whether a raw literal needs a // token-ok exception. Covers the one rule (no raw style literals in component code), the useTheme()/tokens.ts contract, the canonical token scales, and ratchet enforcement. +version: 1.0.0 +--- + +# Streamplace design system + +Follow `AGENTS.md` for repository-wide rules. This skill covers the design-system +contract and the token reference. + +Every visual decision is a token in `js/components/src/lib/theme/tokens.ts`, +consumed through `useTheme()` (`js/components/src/lib/theme/theme.tsx`). + +## The one rule + +**Component code never contains a raw hex value, `rgba()`, numeric font size, +spacing, radius, or duration.** Read every value from the theme instead: +`theme.colors.*`, `theme.spacing[n]`, `theme.typeScale.*`, `theme.borderRadius.*`, +`theme.motion.*`. + +Intentional exceptions carry a `// token-ok` comment (or `/* token-ok */` inline +in JSX). Legitimate cases are narrow: a literal that must render when the theme +provider itself may have crashed (the whole-app crash screen), a transparent +overlay root composited over OBS content, or a brand-guideline swatch that +displays the raw palette by design. Do not use `token-ok` to silence a value +that has a token. Use the token. + +### The ratchet + +`js/scripts/check-tokens.mjs` counts hardcoded style literals (hex, `rgb()`/ +`rgba()`, raw palette-ramp indexing like `colors.primary[`) across +`js/app/src`, `js/app/components`, `js/app/hooks`, and `js/components/src`, +excluding the token definitions in `js/components/src/lib/theme`. Lines carrying +`token-ok` and comment lines are exempt. + +Run it directly: + +```sh +pnpm run check:tokens # or: node js/scripts/check-tokens.mjs +node js/scripts/check-tokens.mjs --list # print every offending line +``` + +Enforcement is automatic: `.husky/pre-commit` runs `pnpm run check:tokens`, +and `make check` runs it via the root `check` chain. The baseline lives at +`brand/token-count.json`; the check fails if the live count rises above it. +Removing literals lets you lower the baseline: +`node js/scripts/check-tokens.mjs --update` rewrites it to the current count. +Ratchet only down. Raising the baseline to absorb a violation defeats the +check. Prefer a token or a justified `token-ok`. + +## Color + +The neutral ramp (surfaces, text, borders) is a clean, untinted +near-black/white system. The accent colors (`primary`, `secondary`) are +aligned with the web app's CSS tokens (`js/web/src/styles.css`). + +### Surfaces (`theme.colors.surface0–3`, `surfaceHover`) + +Clean near-black, never pure black and never tinted. Surfaces separate with +hairline borders instead of shadows. + +| Token | Dark | Light | Use | +| -------------- | ---------- | ---------- | ------------------------------ | +| `surface0` | `#0a0a0b` | `#ffffff` | App background | +| `surface1` | `#111113` | `#fafafa` | Cards, panels, inputs | +| `surface2` | `#18181b` | `#f4f4f5` | Popovers, menus, sheets | +| `surface3` | `#1f1f23` | `#ececef` | Hovered overlay rows, tooltips | +| `surfaceHover` | = surface3 | = surface3 | Hover fill on interactive rows | + +Legacy aliases (kept working): `background`→surface0, `card`→surface1, +`popover`→surface2. + +### Text (`theme.colors.text1–4`) + +White (dark) / ink (light) at fixed alphas. + +| Token | Dark | Light | Use | +| ------- | ----------------------- | ------------------ | ---------------------------------- | +| `text1` | `rgba(255,255,255,.92)` | `rgba(9,9,11,.92)` | Primary: titles, body | +| `text2` | `rgba(255,255,255,.65)` | `rgba(9,9,11,.66)` | Secondary: metadata, descriptions | +| `text3` | `rgba(255,255,255,.45)` | `rgba(9,9,11,.46)` | Tertiary: placeholders, timestamps | +| `text4` | `rgba(255,255,255,.30)` | `rgba(9,9,11,.32)` | Disabled | + +Legacy aliases: `text`→text1, `textMuted`→text2, `textDisabled`→text4. + +### Borders (`borderSubtle` / `border` / `borderStrong`) + +1px hairlines: `rgba(255,255,255,0.06 / 0.08 / 0.10)` in dark. Subtle for +surface separation, default for controls at rest, strong for hover. + +### Accent and secondary + +One accent: pink/magenta `#e955c2` (`colors.primary` ramp, the web's +`--primary`). Use it sparingly: primary buttons, focus rings, active states, +links, the Go Live moment. Not for large fills or decoration. Broadcaster +branding may override `primary`/`ring`; `focus` follows `ring` automatically. + +Secondary: teal `#1abbc0` (`colors.secondary` ramp, the web's +`--secondary`/`--accent`). + +### Status + +| Token | Dark | Rule | +| ------------------------------------- | ----------------- | --------------------------------------------------------------------------------------------- | +| `live` / `liveDim` / `liveForeground` | `#f23041` | **Reserved for the LIVE state only**: badges, live avatar rings, on-air dots. Not for errors. | +| `success` | `#3dd68c` | Healthy ingest, confirmations | +| `warning` | `#ffb224` | Degraded states | +| `danger` / `destructive` | `#ff3b5c` | Errors, destructive actions | +| `overlay` | `rgba(0,0,0,0.6)` | Modal scrims | + +## Typography + +One typeface: **Geist** (and **Geist Mono**), weights 400/500/600 only, +static (no variable fonts). The canonical scale is `typeScale`: sizes +12/13/14/16/20/24/32, line heights in the token file, tight letter-spacing +from 20px up: + +| Key | Size/Line | Weight | Use | +| ------ | ---------- | ------ | ------------------------------------- | +| `xs` | 12/16 | 400 | Badges, timestamps, overlines | +| `sm` | 13/18 | 400 | Chat messages, dense metadata | +| `base` | 14/20 | 400 | Default UI text | +| `md` | 16/24 | 400 | Stream titles (rows), emphasized body | +| `lg` | 20/26 −0.2 | 500 | Section headings | +| `xl` | 24/30 −0.3 | 600 | Page titles | +| `xxl` | 32/38 −0.5 | 600 | Hero moments only | + +- **Counts, timers, and durations always use `tabularNums`** + (`fontVariant: ["tabular-nums"]`) so digits do not jitter. Long-form timers + use Geist Mono. +- `typography.mono.*` (Geist Mono) for stream keys, ingest URLs, diagnostics. +- `typography.ios` / `typography.android` / fontFamily keys outside + regular/medium/semiBold are **deprecated remaps**. Do not use them in new code. + +## Spacing & layout + +4px grid. Canonical steps and their token keys: + +| Key | 1 | 2 | 3 | 4 | 6 | 8 | 12 | 16 | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | +| px | 4 | 8 | 12 | 16 | 24 | 32 | 48 | 64 | + +All padding/margin/gap from this set (via `spacing[n]`, atoms `p`/`m`/`gap`). +Off-grid keys (5, 7, 9, 10, 11, 14, 20+) are deprecated. + +Radii (`borderRadius`): `sm` 4 (small controls), `md` 8 (cards, inputs, +buttons), `lg` 12 (thumbnails, modals), `full` (avatars, pills). `xl/2xl/3xl` +are deprecated aliases of `lg`. + +Hit targets: minimum 44px on touch platforms (`touchTargets.minimum`). + +## Motion (`motion`) + +| Token | Value | Use | +| -------------------- | -------------------------- | ----------------------------------------------- | +| `motion.fast` | 120ms | Micro: hover, press feedback | +| `motion.base` | 200ms | Standard: reveals, toggles, fades | +| `motion.slow` | 300ms | Structural: sheets, panels | +| `motion.bezier` | (0.25, 0.1, 0.25, 1) | `Easing.bezier(...motion.bezier)` in reanimated | +| `motion.easingCss` | same, as CSS string | Web transitions | +| `motion.sheetSpring` | damping 30 / stiffness 300 | **The only allowed spring**, sheets only | + +Everything that appears fades and translates 4–8px. Nothing pops. No bounce. + +## Elevation + +Use a raised surface and a hairline border instead of a shadow. Reserve +shadows (`shadows.sm` only, in practice) for floating layers: menus, toasts, +and popovers. + +## Focus (web) + +Every interactive element gets a 2px `focus`-colored ring with a 2px offset +(`outline` on web, border fallback on native). Keyboard navigation must work +correctly. Do not remove focus states. Style them. + +## Migration status + +Deprecated but still working during the redesign: platform typography scales, +fontFamily weight aliases, off-grid spacing keys, radius `xl/2xl/3xl`, +`animations` (use `motion`), and the unused Tailwind ramps. They still work, +but new code should not use them. Removal is tracked as redesign work. +`tokens.ts` and `theme.tsx` are the source of truth; where this skill and the +code disagree, the code wins. diff --git a/.husky/pre-commit b/.husky/pre-commit index 1e2076363..572dc4c11 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,6 +1,7 @@ make golangci-lint ./node_modules/.bin/lint-staged pnpm run knip +pnpm run check:tokens cd js/app && pnpm run check unformatted=$(gofmt -l $(find . -name "*.go")) diff --git a/AGENTS.md b/AGENTS.md index b63362958..82c57d3f1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,126 +1,136 @@ # Streamplace -Streamplace is a Go application with web/mobile frontend code under `js/`, media -and API packages under `pkg/`, command entry points under `cmd/`, generated -lexicon bindings, and end-to-end test infrastructure. +Streamplace is a Go application. The web and mobile frontend code lives under +`js/`, the media and API packages under `pkg/`, and the command entry points +under `cmd/`. The repo also contains generated lexicon bindings and end-to-end +test infrastructure. -Keep changes focused on the requested task. Read the relevant code before -editing, and prefer the repository's existing abstractions and scripts over -recreating their behaviour manually. +This document is meant to be easy to parse for everyone, but agents should note that they must follow all guidelines, especially the ones marked as such. + +Agents: you will want to keep your changes focused on the task. Read the relevant code before you edit it. +Use the repository's existing abstractions and scripts instead of recreating +their behaviour by hand. ## Repository layout -Useful areas of the repository include: +Useful areas of the repository: -- `pkg/` — Go packages, including API, media, RPC, and application logic. -- `cmd/` — Go command entry points. -- `js/app/` — Expo application. -- `js/web/` — web frontend. -- `js/components/` — shared frontend components. -- `js/e2e-web/` — Playwright end-to-end tests. -- `lexicons/` — source lexicons used to generate Go, JS, documentation, and API +- `pkg/`: Go packages, including API, media, RPC, and application logic. +- `cmd/`: Go command entry points. +- `js/app/`: Expo application. +- `js/web/`: web frontend. +- `js/components/`: shared frontend components. +- `js/e2e-web/`: Playwright end-to-end tests. +- `lexicons/`: source lexicons used to generate Go, JS, documentation, and API artifacts. -- `hack/` — development, provisioning, and test scripts. -- `.maestro/` — mobile end-to-end flows. +- `hack/`: development, provisioning, and test scripts. +- `.maestro/`: mobile end-to-end flows. -Check package-local documentation and configuration when working in one of these -areas. +When you work in one of these areas, check its local documentation and +configuration first. ## Development environment -Inspect the current machine before making environment-dependent decisions. +For agents: Inspect the current machine before you make decisions that depend on the +environment. -Prefer repository-provided Make targets and scripts over manually reproducing -their underlying commands. +Use the repository's Make targets and scripts. Do not reproduce their underlying +commands by hand. -For the fully containerized build, scratch-node, and browser-test workflow, use -the [streamplace-docker skill](.claude/skills/streamplace-docker/SKILL.md). -It covers isolated sibling checkouts without requiring host-native build tools. +For the containerized build, scratch-node, and browser-test workflow, use the +[streamplace-docker skill](.claude/skills/streamplace-docker/SKILL.md). It covers +isolated sibling checkouts and does not require host-native build tools. ## Building -A fresh checkout needs both the native build artifacts and the generated -frontend bundles before ordinary Go builds will work correctly. +A fresh checkout needs the native build artifacts and the generated frontend +bundles before ordinary Go builds will work. -The repository's cold-start dev entrypoint is: +To set up a fresh checkout, run: ```sh make dev-setup ``` -It handles setting up Meson, the C and Rust dependencies, and does an initial -frontend build. +This sets up Meson and the C and Rust dependencies, and runs an initial frontend +build. -For normal development: +For normal development, run: ```sh make dev ``` -After changing frontend code, rebuild the frontend rather than assuming an -existing `dist` directory reflects the current source: +After you change frontend code, rebuild the frontend. Do not assume an existing +`dist` directory reflects the current source: ```sh make app ``` -The Go packages most closely corresponding to the CI build are: +To build the Go packages that most closely match the CI build, run: ```sh go build ./pkg/... ./cmd/... ``` -For code you change, prefer targeted checks where practical: +For the code you change, run targeted checks where practical: ```sh -go vet ./pkg//... go test -count=1 ./pkg//... ``` -Avoid running very expensive repository-wide suites without a reason when a -targeted test provides equivalent coverage. +`go vet ./pkg//...` is a fast sanity check, but it is much weaker than +the lint in the commit gate (golangci-lint with staticcheck). A change that +passes `go vet` can still fail CI tests! See [Testing](#testing). + +Do not run expensive repository-wide suites when a targeted test gives the same +coverage. ## Frontend -The Go application embeds built frontend artifacts. An existing frontend bundle -may be stale after source changes or branch changes. +The Go application embeds the built frontend artifacts. A frontend bundle can be +stale after you change source or switch branches. -When changing code under `js/`, make sure the bundle used by builds or +When you change code under `js/`, make sure the bundle used by builds or end-to-end tests was generated from the current source. -Useful checks include: +For UI and component styling, follow the +[streamplace-design skill](.claude/skills/streamplace-design/SKILL.md). Every +visual value comes from a theme token. Do not hardcode raw literals. +Useful checks: ```sh cd js/app && npx tsc -p . --noEmit pnpm run check ``` -Use the workspace's existing package scripts rather than introducing parallel -tooling for formatting, type checking, or dependency analysis. +Use the workspace's existing package scripts. Do not add parallel tooling for +formatting, type checking, or dependency analysis. ## Lexicons and generated files -Changes under `lexicons/` require regeneration: +After you change files under `lexicons/`, regenerate the bindings: ```sh make lexicons ``` -Inspect the resulting diff and commit the required generated outputs. +Inspect the resulting diff and commit the generated outputs that the change +requires. Lexicon generation can affect Go code, JS types, documentation, and API -artifacts. Do not assume generated changes are irrelevant merely because they -are outside the directory you directly edited. +artifacts. Do not assume a generated change is irrelevant just because it is +outside the directory you edited. -If a lexicon is removed, check whether generated artifacts associated with it -also need to be removed. +If you remove a lexicon, check whether its generated artifacts also need to be +removed. -Do not hand-edit generated output when the corresponding generator should be -changed instead. +Do not hand-edit generated output. Change the generator instead. ## Tests -Run tests relevant to the code being modified. +Run the tests relevant to the code you changed. For Go packages: @@ -128,28 +138,27 @@ For Go packages: go test -count=1 ./pkg//... ``` -Use `-run` for focused testing when a package contains slow integration or media -tests. +When a package contains slow integration or media tests, use `-run` to run only +the tests you need. -The repository contains a local web end-to-end harness and Playwright suite. The +The repository has a local web end-to-end harness and Playwright suite. The normal entry point is: ```sh hack/e2e-web-local.sh ``` -The harness starts the services required by the browser tests and creates -temporary test state. Prefer the provided harness over manually reproducing its -process topology. +The harness starts the services the browser tests need and creates temporary test +state. For agents: Use the harness. Do not reproduce its process topology by hand. -When debugging an e2e failure, distinguish between: +When you debug an e2e failure, a good first thing to do is to work out which of these caused it: - application behaviour -- frontend bundle staleness -- test harness failure -- environment/networking failure +- a stale frontend bundle +- a test harness failure +- an environment or networking failure -Do not classify a failure as pre-existing or environment-only without verifying +For agents: Do not call a failure pre-existing or environment-only until you have verified it. ## Media and native dependencies @@ -157,79 +166,119 @@ it. Parts of Streamplace depend on native media libraries and cgo. If a Go build fails because native libraries or pkg-config metadata are missing, -inspect the repository's build environment and provisioning scripts rather than -installing arbitrary host dependencies or inventing paths. +you will want to inspect the repository's build environment and provisioning scripts. +For agents: Do not install arbitrary host dependencies or invent paths. -Prefer the repository's configured build environment for media-related -compilation and tests. +Use the repository's configured build environment to compile and test +media-related code. -Changes to media code should generally receive targeted tests for the affected -package or pipeline before broader suites are attempted. +When you change media code, add targeted tests for the affected package or +pipeline before you run broader suites. -## muxl +## muxl and other dependencies -Streamplace consumes `github.com/streamplace/muxl/go` as a Go dependency. +Streamplace uses `github.com/streamplace/muxl/go` as a Go dependency. -If a task specifically requires testing unreleased muxl changes, inspect the -current dependency and local environment before introducing a filesystem -`replace` directive. +If a task requires you to test unreleased muxl changes, inspect the current +dependency and the local environment before you add a filesystem `replace` +directive. -A local module replacement is development-only and must not be committed. +Local module replacements are for development only. Do not commit them. -Do not assume a sibling muxl checkout exists. +Agents: Do not assume a sibling muxl checkout exists. ## Running Streamplace locally Use the repository's existing development binaries and scripts. -Do not assume that ports mentioned in documentation are available on the current -machine. Check before starting additional long-running services. +Agents: Do not assume the ports in the documentation are free on your machine. Check +before you start additional long-running services. For scratch or test nodes: -- use isolated data directories; -- avoid inheriting unrelated production configuration; -- do not point development processes at production storage or credentials; -- prefer loopback/local-only listeners unless the task requires otherwise. +- Use isolated data directories. +- Do not inherit unrelated production configuration. +- Do not point development processes at production storage or credentials. +- Use loopback or local-only listeners unless the task requires otherwise. Do not enable development-only authentication or networking flags in production configuration. +## Testing + +A commit must pass more than `go vet` or one package's tests. +To run the full test suite: + +```sh +make check # golangci-lint, pnpm run check, gofmt, cargo check -D warnings +make fix # autofix: prettier --write, gofmt -w, cargo fix, go mod tidy +``` + +`.husky/pre-commit` runs a subset of these on every commit: `make golangci-lint`, +lint-staged (prettier), `pnpm run knip`, `pnpm run check:tokens` (the design +token ratchet), `cd js/app && pnpm run check`, and a `gofmt` check. A change that +passes `go vet` can still fail the hook. Run `make check`, or the relevant part +of it, before you treat work as done. Do not bypass the hook with `--no-verify`. +When the hook fails, fix the cause. + +`pnpm run check` runs `knip`, the token ratchet, the `js/app` and `e2e-web` +typechecks, and a `prettier --check` over all tracked files. `knip` only looks at +the `js/streamplace` package; see `knip.json` for its narrow reach. + +### golangci-lint exclusions + +`.golangci.yaml` runs staticcheck with all checks, but it excludes some rules +because generated code trips them. Do not change generated output to satisfy +these rules, and do not re-enable them: + +- `ST1003` (underscores in names): indigo codegen trips this. +- `ST1005` (capitalized error strings) and `ST1006` (generic receiver names): + uniffi-bindgen-go codegen trips these. +- `SA5008` (unknown JSON option): the codegen emits const-based JSON struct tags. +- `QF1003` (could use tagged switch): excluded across the repo as a style nit. +- The `unused` linter is disabled. + +### Conventions the linter does not enforce + +`make check` runs the full test suite. Reviewers also expect the Go idioms in +[docs/go-conventions.md](docs/go-conventions.md): `log.Log` for the info level +(there is no `log.Info`), `%w` error wrapping, `errors.WriteHTTP*` in HTTP +handlers, and `testify/require` in tests. + ## Git and changes -Inspect repository state before making changes: +Inspect the repository state before you make changes: ```sh git status git diff ``` -Do not: +Agents, please do not: -- discard unrelated working-tree changes; -- rewrite commits that are unrelated to the task; -- commit local filesystem paths; -- commit temporary module replacements; -- commit credentials or secrets; -- assume a particular branch, worktree, stacking, or push workflow unless the - task or repository configuration requires it. +- Discard unrelated working-tree changes. +- Rewrite commits that are unrelated to the task. +- Commit local filesystem paths. +- Commit temporary module replacements. +- Commit credentials or secrets. +- Assume a particular branch, worktree, stacking, or push workflow unless the + task or the repository configuration requires it. -Follow the repository's actual branch and contribution state rather than -imposing a preferred local Git workflow. +Follow the repository's actual branch and contribution state. Do not impose your +own preferred local Git workflow. -Before finishing, inspect the complete diff and make sure generated, formatted, -or unrelated changes have not leaked into the patch. +Before you finish, inspect the complete diff. Make sure no generated, formatted, +or unrelated changes have leaked into the patch. ## Working style -Prefer understanding the existing implementation before adding new abstractions. +Understand the existing implementation before you add new abstractions. Reuse established packages, helpers, scripts, and conventions where they fit. -When repository documentation contains machine-specific observations, treat them -as context rather than universal requirements. Verify them against the current -checkout and environment. +Some repository documentation records observations that are specific to one +machine. Treat these as context, and verify them against the current checkout and +environment before you rely on them. -For task-specific procedures, use relevant repository documentation or agent -skills when available instead of applying unrelated operational instructions -globally. +For task-specific procedures, use the relevant repository documentation or agent +skills when they exist. Do not apply unrelated operational instructions globally. diff --git a/CURRENT.md b/CURRENT.md index 3177d53bd..1cf4a355e 100644 --- a/CURRENT.md +++ b/CURRENT.md @@ -79,3 +79,81 @@ bare `maestro` calls with `$MAESTRO`. Verified: syntax, missing branch (message - exit 1), home-fallback full run (5 screenshots + video + tree). Did NOT test the interactive "y" install path (would reinstall maestro via curl|bash). README + skill updated with the behavior. + +## 2026-09-24: AGENTS.md quality-gate revision + design skill + token ratchet + +Goal: make AGENTS.md self-describing so it works for contributors who don't carry +a personal global `~/.claude/CLAUDE.md`. The repo was implicitly leaning on that +global guide to paper over gaps in the one file that actually travels with it. + +### what changed + +- `AGENTS.md`: new "Quality gate" section naming the real bar (`make check`, + `make fix`, the pre-commit subset) and demoting `go vet` to a fast pre-check; + documented the `.golangci.yaml` excludes (ST1003/ST1005/ST1006/SA5008/QF1003, + `unused` disabled) so nobody "fixes" generated code or re-enables a check; + pointed UI work at the design skill and Go idioms at the conventions doc. +- `docs/go-conventions.md` (new): the unwritten Go idioms — `log.Log` (there is + no `log.Info`), `%w` wrapping, `errors.WriteHTTP*`, `testify/require`. Grounded + in `pkg/log` + `pkg/errors`, not just call counts. +- `.claude/skills/streamplace-design/SKILL.md` (new): the design system, moved + out of `docs/redesign/DESIGN-SYSTEM.md` (deleted; git shows it as a rename). + Token tables preserved verbatim (spot-checked against `tokens.ts`); ratchet + section now reflects that it's actually enforced; fixed the dangling + `MIGRATION.md` reference. +- Token ratchet wired in: `package.json` `check:tokens` script, added to the root + `check` chain (so `make check` covers it) and to `.husky/pre-commit`. +- Fixed the 4 live token violations the unwired ratchet had let accumulate: + `verified-badge.tsx` `#fff`→`colors.white` (tokenized, theme-aware component); + `brand.tsx` palette swatch + two `error-boundary.tsx` crash-screen literals + marked `token-ok` (both intentionally theme-free). Ratchet green at baseline 0. +- `docs/redesign/AUDIT.md`: repointed the live "current values" ref at the skill, + genericized the historical build-order cell. + +### verified + +- `pnpm run check:tokens` → 0 literals, exit 0. `pnpm run knip` → exit 0. + `prettier --check` clean on all 8 changed + 2 new files. +- js/components tsc: zero errors in touched files. brand.tsx change is + comment-only; zero tsc errors reference it. + +### notes / side observations (not fixed) + +- `cd js/app && pnpm run check` currently FAILS (exit 2) on PRE-EXISTING stale + generated lexicons: source `lexicons/place/stream/branding/{export,import}Bundle.json` + exist but the gitignored generated `js/streamplace/src/lexicons/.../branding.ts` + (Sep 22) lacks them. Errors are in branding-admin.tsx / player.tsx / + chat-access.tsx — none in my diff. Fix is `make js-lexicons`. Until then, + pre-commit is red in this checkout for ANY commit. +- The gap-analysis claim "a new export in js/streamplace/src can fail a commit + even with zero importers" is FALSE — probed it: knip `include: ["unlisted"]` + reports undeclared deps, not unused exports, and its project/entry patterns + match nothing (knip emits "move to workspaces" config hints). knip is + effectively latent. Documented conservatively in AGENTS.md, no false trap. +- `js/scripts/check-tokens.mjs` usage comment claims `--update` rewrites the + baseline "only downward", but the code writes the current total unconditionally + (would ratchet UP too). Documented actual behavior in the skill; left the script + as-is (out of scope). + +## 2026-09-24 (later): humanize + STE100 pass on the docs + +Goal: make the docs read well for humans, not just agents. Applied a +humanizer-plus-Simplified-Technical-English pass to `AGENTS.md` and +`docs/go-conventions.md`. STE100 is the controlling standard for these reference +docs, so the humanizer's "add soul / opinions / mess" guidance is deliberately +NOT applied. + +What the pass did: active voice and second person ("you"); short sentences split +out of dense multi-clause ones; em dashes replaced with colons/periods; plain +words ("just because" not "merely because", "print" not "emit"); removed +idioms/metaphors ("bite people", "ride along", "cuts the chain", "cascading"); +removed the negative-parallelism tail in the Quality gate hook paragraph; no +contractions. Preserved every heading, command, link, the `#quality-gate` anchor, +and all factual content (verified by spot-check + tell-scan). + +Also gave `streamplace-design/SKILL.md` a light pass: em dashes to colons, +semicolon splices split, tailing negations and the focus-state parallelism fixed, +contractions expanded, and "reach for" / "must be flawless" / "update the skill" +removed. Kept the reference tables, token values, and the functional bold that +marks hard constraints. Verified the special characters (arrows, range en dashes, +minus signs, ellipsis) and all 14 hex values survived intact. diff --git a/docs/redesign/token-baseline.json b/brand/token-count.json similarity index 100% rename from docs/redesign/token-baseline.json rename to brand/token-count.json diff --git a/design/monoline_telescope_icon.html b/design/monoline_telescope_icon.html deleted file mode 100644 index 6c1f6fbcb..000000000 --- a/design/monoline_telescope_icon.html +++ /dev/null @@ -1,212 +0,0 @@ -

- A monochrome line-art space telescope icon that rocks back and forth up to an - adjustable 20 degree maximum, with speed, direction, and pause controls. -

- -
- - - 20° -
- -
- - - 4 -
- -
- - - 0° -
- - - Monoline space telescope icon - - A single-color, stroke-based space telescope in the style of an icon - library, gently rotating around its center. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/design/rotated_wings_svg.svg b/design/rotated_wings_svg.svg deleted file mode 100644 index 8c23dcd5d..000000000 --- a/design/rotated_wings_svg.svg +++ /dev/null @@ -1,42 +0,0 @@ - - scope illustration with rotated wings - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/go-conventions.md b/docs/go-conventions.md new file mode 100644 index 000000000..3ff964edf --- /dev/null +++ b/docs/go-conventions.md @@ -0,0 +1,93 @@ +# Streamplace Go conventions + +How we write Go in Streamplace. The linter may not catch these conventions, but +we expect you to follow them in this repo. + +## Logging + +Log through `stream.place/streamplace/pkg/log`. Each call takes the context +first, then the message, then alternating key-value pairs: + +```go +log.Log(ctx, "started ingest", "streamer", did, "rtmpPort", port) +``` + +Pick the level by what happened. The number is the glog `-v` level that a process +needs to print the line. The default is `-v=3`. At the default, `Error`, `Warn`, +and `Log` always print. `Debug` and `Trace` print only at a higher `-v`. + +| Call | min `-v` | Use it for | +| ----------- | -------- | -------------------------------------------- | +| `log.Error` | 1 | An operation failed and could not complete. | +| `log.Warn` | 2 | Something was wrong, but the code recovered. | +| `log.Log` | 3 | General information. This is the info level. | +| `log.Debug` | 4 | Detail for debugging. Prints only at `-v=4`. | +| `log.Trace` | 9 | Detailed tracing. Prints only at `-v=9`. | + +There is no `log.Info`. `log.Log` is the info level. + +Common mistakes: + +- The key-value arguments come in pairs. An odd number of arguments prints a + warning at runtime and may corrupt the line. Always check the arguments after you edit a + call. +- Do not pass a `caller` key. The logger adds one for you. + +A value such as a request id or a streamer did can apply to every log line from a +context. To attach such a value, derive a new context with `log.WithLogValues`. +You do not need to pass the value to every call: + +```go +ctx = log.WithLogValues(ctx, "streamer", did) +``` + +## Error wrapping + +Wrap errors with `%w`, not `%v`, so callers can inspect the cause with +`errors.Is` and `errors.As`: + +```go +return fmt.Errorf("failed to combine segments: %w", err) +``` + +`%v` converts the error to a string and breaks the error chain. A caller can no +longer match the underlying error. Use `%v` only when you want to hide the cause. + +## HTTP handlers + +Write error responses with the helpers in `stream.place/streamplace/pkg/errors`. +Do not call `w.WriteHeader` and encode JSON yourself, as the function will do it for you: + +```go +if err != nil { + errors.WriteHTTPBadRequest(w, "Error parsing PUSH_REWRITE payload", err) + return +} +``` + +| Helper | Status | Notes | +| ------------------------------- | ------ | ----------------------- | +| `WriteHTTPBadRequest` | 400 | | +| `WriteHTTPUnauthorized` | 401 | | +| `WriteHTTPForbidden` | 403 | | +| `WriteHTTPNotFound` | 404 | | +| `WriteHTTPUnsupportedMediaType` | 415 | | +| `WriteHTTPTooManyRequests` | 429 | takes no `err` argument | +| `WriteHTTPInternalServerError` | 500 | | +| `WriteHTTPNotImplemented` | 501 | | + +Each helper returns an `APIError{Msg, Status, Err}`. Most handlers ignore it and +return after they write the response. Use the return value only when the caller +needs the status or the message. + +## Tests + +Assert with `github.com/stretchr/testify/require`, not `assert`. `require` fails +the test immediately (`t.FailNow`) on the first broken precondition. A failed +setup then stops at once, instead of causing nil-pointer panics in the assertions +that follow: + +```go +require.NoError(t, err) +require.Equal(t, "sync", cmd.Name) +``` diff --git a/docs/redesign/AUDIT.md b/docs/redesign/AUDIT.md deleted file mode 100644 index 90f8394ed..000000000 --- a/docs/redesign/AUDIT.md +++ /dev/null @@ -1,136 +0,0 @@ -# UI Redesign — Phase 0 Audit - -Target: Linear.app (restraint, precision, dark-first, consistency) × YouTube -(content-forward, video-first layout grammar). Visual/interaction redesign -only — hooks, data flow, and functionality untouched. - -## 1. Screen map - -Navigation: React Navigation v8 alpha (not expo-router). Three levels: -RootStack → Tab.Navigator → per-tab stacks (`js/app/src/shell.tsx`, -`js/app/src/root-navigator.tsx`). Web shows a header + collapsible sidebar -(`js/app/src/router.tsx`, `useSidebarControl`); native uses bottom tabs. - -### Core screens (redesign targets) - -| Screen | File | Notes | -| --------------------- | ---------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Watch page (live) | `js/app/src/screens/mobile-stream.tsx` → `js/app/components/mobile/player.tsx` | The core product. Player + chat side panel (web ≥1024) / below (mobile). `BottomMetadata` title row exists but is gated to `showFullDesktopMode` (aspect>1 && width>1200). | -| Home / discovery | `js/app/src/screens/home.tsx` + `js/app/components/home/cards.tsx` | Custom responsive grid (1/2/3/4 cols), LiquidGlassView cards, LIVE badge, activity tags. | -| Chat | `js/components/src/components/chat/{chat,chat-message,chat-box}.tsx` + `js/app/components/mobile/chat.tsx` | Gesture-handler FlatList, 25 msgs native / 100 web, swipe reply/actions, per-user RGB colors. | -| VOD player | `js/app/src/screens/vod.tsx` (VodPlayer), `video.tsx` (legacy) | Seek bar via gesture-handler, vod-controls. | -| VOD gallery | `js/app/src/screens/video-list.tsx` | Grid, pagination, per-user via `?did`. | -| Broadcaster dashboard | `js/app/src/screens/live-dashboard.tsx` + `js/app/components/live-dashboard/` | BentoGrid metrics, stream-key (WHIP/RTMP), stream-monitor. | -| Go live | `js/app/src/screens/{launch-go-live,mobile-go-live}.tsx` | Player in ingest mode; `useLivestreamInfo` drives title/countdown/toggle. | -| Upload / drafts | `js/app/src/screens/upload.tsx` | 2193 lines, multi-phase tus upload + draft editor. Restyle in place only. | -| Settings | `js/app/components/settings/` (16 category screens) | Stack of categories under SettingsTab. | -| Auth / login | `js/app/components/login/{login,login-form,pds-host-selector-modal}.tsx` | ATProto OAuth, PDS selector. | -| Misc | `about.tsx`, `download.tsx`, `support.tsx`, `app-return.tsx` | Low traffic. | - -### Overlay surfaces — MUST keep transparent/minimal roots (do not re-skin) - -These render inside OBS browser sources, iframes, or popout windows. They must -never inherit an opaque `surface0` root background. - -- `danmu-obs.tsx` (OBS comment overlay) -- `embed.tsx`, `vod-embed.tsx` (iframe embeds) -- `chat-popout.tsx` / `chat-popout.native.tsx` (query flags: reverse, hideAfter…) -- `info-widget-embed.tsx`, `popout-info-widget.tsx` -- `popout-livestream.tsx`, `popout-multistream.tsx`, `popout-stream-monitor.tsx` -- `multi.tsx` (multi-stream wall) - -Intentional literals in these files get `// token-ok` markers (ratchet allowlist). - -## 2. Current styling system inventory - -Lives in `js/components/src/lib/theme/` (tokens.ts, theme.tsx, atoms.ts, -branded-theme-provider.tsx). Consumed via `useTheme()` → `{ theme.colors, -zero (pairified atoms), icons, isDark, setTheme }`. - -What exists today: - -- **Colors**: 23 full Tailwind ramps (50–950) + semantic destructive/success/ - warning ramps + iOS/Android platform system colors. Semantic `Theme.colors` - (background, card, popover, primary, muted, border, ring, text, textMuted, …) - generated from a palette by `generateThemeColorsFromPalette`. Dark/light/ - system switching works. `BrandedThemeProvider` overrides `primary/ring/accent`. -- **Typography**: THREE parallel systems (iOS HIG 11 styles, Material 13 - styles, universal 8 sizes) + mono scale + fontSize atoms 12–128. Font: - Atkinson Hyperlegible Next + Mono, 7 weights, loaded in - `js/app/components/provider/provider.shared.tsx`; family names referenced - only in tokens.ts. -- **Spacing**: 0–384 in 4px steps (31 keys — far more than needed). -- **Radii**: none/3/8/12/16/20/24/full. -- **Shadows**: sm–xl. **Motion**: 150/200/300/500, no shared easing. - **Touch targets**: 44/48/56. -- **Primitives** (`js/components/src/components/ui/`): Button (6 variants, - 6 sizes), Text (12 variants + conveniences), Input (3 variants), Dialog - (modal/sheet/fullscreen), Checkbox, Toast (reanimated), Dropdown, Select, - Slider, Loader, Menu (Radix on web), Tooltip, Textarea, Portal, View, - Icons, InfoBox/InfoRow, Admonition. Chat-only Badge. **Missing**: Skeleton, - Tabs, IconButton, Avatar (with live ring), general Badge, Surface/Card. -- **reanimated ~4.2.1**: present, used in toast, live-dot, gradient, chat, - player, sidebar-overlay, etc. - -### Hardcoded-literal debt (ratchet baseline, 2026-07-05) - -`node js/scripts/check-tokens.mjs` — counts hex, rgb()/rgba(), raw ramp -indexing (`colors.gray[...]`) outside `lib/theme`: - -| Directory | Literals | -| ----------------- | -------- | -| js/app/src | 25 | -| js/app/components | 195 | -| js/components/src | 140 | -| **Total** | **360** | - -Representative offenders: `shell.tsx` `#06f` accent fallback, `home.tsx` -`#774316`/`#99889988`, `video.tsx` `#000/#111/#aaa`, `vod.tsx` rgba scrims, -`ui/` modal scrims `rgba(0,0,0,0.5)`, chat mention styling, dashboard colors. -The baseline may only decrease (`docs/redesign/token-baseline.json`); the -final state is allowlist-only. - -**Final state (end of Phase 3): 24 literals** — all in `js/app/components`, -all either dynamic color computation (name-color-picker contrast math, -badge-picker swatches, sidebar tint alpha-injection) or data defaults -(branding config values). Every intentional literal in component styles -carries a `// token-ok` marker. Everything else flows through the token -system. - -## 3. Highest-leverage surfaces (by user exposure) - -1. Watch page (player + metadata + controls) — every viewer session -2. Chat — open during every live session -3. Home/discovery grid — every session entry point -4. App shell (header/sidebar/tab bar) — chrome on every screen -5. Player async states (buffering/offline/error) — first impression quality -6. Broadcaster dashboard + go-live — every streamer session -7. VOD gallery + VOD player -8. Settings (16 screens) — lower traffic, high consistency payoff -9. Login — first-run moment -10. Upload — creators only, but long dwell time - -## 4. Build order - -| Commit | Scope | -| ------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | -| C0 | This audit + ratchet script | -| C1–C2 | Token rebuild (surfaces/text/borders/indigo accent/live red, 7-step type scale, motion), Geist swap, theme.tsx semantic extension, DESIGN-SYSTEM.md | -| C3 | Text/Button/IconButton + focus ring | -| C4 | Inputs (input/textarea/select/checkbox/slider) | -| C5 | New primitives: Surface, Badge/LiveBadge, Avatar (live ring), Skeleton, Tabs | -| C6 | Overlays (dialog/toast/menu/dropdown/tooltip/loader/info-\*/admonition) + ui/ sweep | -| C7 | Watch page | -| C8 | Chat | -| C9 | Home/discovery | -| C10 | Broadcaster/go-live | -| C11 | Shell, settings, auth, upload, video-list, misc | -| C12 | Overlay-surface transparency pass, delete deprecated aliases, MIGRATION.md, final ratchet | - -Decisions locked with the user: typeface = **Geist** (Sans + Mono, 400/500/600 -only); accent = **Linear-style indigo** (~#5E6AD2); red reserved for LIVE. - -> Later revision: the accent was realigned to the web app's palette -> (`js/web/src/styles.css`) — pink/magenta `#e955c2` primary, teal `#1abbc0` -> secondary. The typeface (Geist) and the untinted near-black neutral ramp -> were kept. See `DESIGN-SYSTEM.md` for the current values. diff --git a/docs/redesign/DESIGN-SYSTEM.md b/docs/redesign/DESIGN-SYSTEM.md deleted file mode 100644 index 9ed87e5f8..000000000 --- a/docs/redesign/DESIGN-SYSTEM.md +++ /dev/null @@ -1,143 +0,0 @@ -# Streamplace Design System - -Dark-first. Linear-grade restraint, YouTube-familiar layout grammar. Every -visual decision is a token in `js/components/src/lib/theme/tokens.ts`, -consumed through `useTheme()` (`js/components/src/lib/theme/theme.tsx`). - -**The one rule: component code never contains a raw hex value, rgba(), -numeric font size, spacing, radius, or duration.** The ratchet -(`js/scripts/check-tokens.mjs`) enforces this. Intentional exceptions -(e.g. transparent OBS overlay roots) carry a `// token-ok` comment. - -## Color - -The neutral ramp (surfaces, text, borders) is a clean, untinted -near-black/white system. The accent colors (`primary`, `secondary`) are -aligned with the web app's CSS tokens (`js/web/src/styles.css`). - -### Surfaces (`theme.colors.surface0–3`, `surfaceHover`) - -Clean near-black, never pure black and never tinted. Surfaces separate with -hairline borders, not shadows. - -| Token | Dark | Light | Use | -| -------------- | ---------- | ---------- | ------------------------------ | -| `surface0` | `#0a0a0b` | `#ffffff` | App background | -| `surface1` | `#111113` | `#fafafa` | Cards, panels, inputs | -| `surface2` | `#18181b` | `#f4f4f5` | Popovers, menus, sheets | -| `surface3` | `#1f1f23` | `#ececef` | Hovered overlay rows, tooltips | -| `surfaceHover` | = surface3 | = surface3 | Hover fill on interactive rows | - -Legacy aliases (kept working): `background`→surface0, `card`→surface1, -`popover`→surface2. - -### Text (`theme.colors.text1–4`) - -White (dark) / ink (light) at fixed alphas. - -| Token | Dark | Light | Use | -| ------- | ----------------------- | ------------------ | ---------------------------------- | -| `text1` | `rgba(255,255,255,.92)` | `rgba(9,9,11,.92)` | Primary: titles, body | -| `text2` | `rgba(255,255,255,.65)` | `rgba(9,9,11,.66)` | Secondary: metadata, descriptions | -| `text3` | `rgba(255,255,255,.45)` | `rgba(9,9,11,.46)` | Tertiary: placeholders, timestamps | -| `text4` | `rgba(255,255,255,.30)` | `rgba(9,9,11,.32)` | Disabled | - -Legacy aliases: `text`→text1, `textMuted`→text2, `textDisabled`→text4. - -### Borders (`borderSubtle` / `border` / `borderStrong`) - -1px hairlines: `rgba(255,255,255,0.06 / 0.08 / 0.10)` in dark. Subtle for -surface separation, default for controls at rest, strong for hover. - -### Accent (`primary`, `ring`, `focus`) & secondary - -One accent: pink/magenta `#e955c2` (`colors.primary` ramp, the web's -`--primary`). Used **sparingly** — primary buttons, focus rings, active -states, links, the Go Live moment. Never for large fills or decoration. -Broadcaster branding may override `primary`/`ring` (and `focus` follows -`ring` automatically). - -Secondary/accent: teal `#1abbc0` (`colors.secondary` ramp, the web's -`--secondary`/`--accent`). - -### Status - -| Token | Dark | Rule | -| ------------------------------------- | ----------------- | ----------------------------------------------------------------------------------------------------- | -| `live` / `liveDim` / `liveForeground` | `#f23041` | **Reserved for the LIVE state only** — badges, live avatar rings, on-air dots. Never used for errors. | -| `success` | `#3dd68c` | Healthy ingest, confirmations | -| `warning` | `#ffb224` | Degraded states | -| `danger` / `destructive` | `#ff3b5c` | Errors, destructive actions | -| `overlay` | `rgba(0,0,0,0.6)` | Modal scrims | - -## Typography - -One typeface: **Geist** (+ **Geist Mono**), weights 400/500/600 only, -static (no variable fonts). Canonical scale -`typeScale` — sizes 12/13/14/16/20/24/32, line heights in the token file, -tight letter-spacing from 20px up: - -| Key | Size/Line | Weight | Use | -| ------ | ---------- | ------ | ------------------------------------- | -| `xs` | 12/16 | 400 | Badges, timestamps, overlines | -| `sm` | 13/18 | 400 | Chat messages, dense metadata | -| `base` | 14/20 | 400 | Default UI text | -| `md` | 16/24 | 400 | Stream titles (rows), emphasized body | -| `lg` | 20/26 −0.2 | 500 | Section headings | -| `xl` | 24/30 −0.3 | 600 | Page titles | -| `xxl` | 32/38 −0.5 | 600 | Hero moments only | - -- **Counts, timers, durations always use `tabularNums`** - (`fontVariant: ["tabular-nums"]`) so digits don't jitter; long-form timers - use Geist Mono. -- `typography.mono.*` (Geist Mono) for stream keys, ingest URLs, diagnostics. -- `typography.ios` / `typography.android` / fontFamily keys outside - regular/medium/semiBold are **deprecated remaps** — do not use in new code. - -## Spacing & layout - -4px grid. Canonical steps and their token keys: - -| Key | 1 | 2 | 3 | 4 | 6 | 8 | 12 | 16 | -| --- | --- | --- | --- | --- | --- | --- | --- | --- | -| px | 4 | 8 | 12 | 16 | 24 | 32 | 48 | 64 | - -All padding/margin/gap from this set (via `spacing[n]`, atoms `p`/`m`/`gap`). -Off-grid keys (5, 7, 9, 10, 11, 14, 20…) are deprecated. - -Radii (`borderRadius`): `sm` 4 (small controls), `md` 8 (cards, inputs, -buttons), `lg` 12 (thumbnails, modals), `full` (avatars, pills). `xl/2xl/3xl` -are deprecated aliases of `lg`. - -Hit targets: minimum 44px on touch platforms (`touchTargets.minimum`). - -## Motion (`motion`) - -| Token | Value | Use | -| -------------------- | -------------------------- | ----------------------------------------------- | -| `motion.fast` | 120ms | Micro: hover, press feedback | -| `motion.base` | 200ms | Standard: reveals, toggles, fades | -| `motion.slow` | 300ms | Structural: sheets, panels | -| `motion.bezier` | (0.25, 0.1, 0.25, 1) | `Easing.bezier(...motion.bezier)` in reanimated | -| `motion.easingCss` | same, as CSS string | Web transitions | -| `motion.sheetSpring` | damping 30 / stiffness 300 | **The only allowed spring**, sheets only | - -Everything that appears fades + translates 4–8px. Nothing pops. No bounce. - -## Elevation - -Prefer a raised surface + hairline border over shadow. Shadows (`shadows.sm` -only, in practice) are reserved for floating layers: menus, toasts, popovers. - -## Focus (web) - -Every interactive element: 2px `focus`-colored ring with 2px offset -(`outline` on web, border fallback native). Keyboard navigation must be -flawless; focus states are never removed, only styled. - -## Migration status - -Deprecated-but-working during the redesign (removal tracked in -MIGRATION.md at the end of Phase 3): platform typography scales, fontFamily -weight aliases, off-grid spacing keys, radius xl/2xl/3xl, `animations` -(use `motion`), the 17 unused Tailwind ramps. diff --git a/js/app/src/screens/brand.tsx b/js/app/src/screens/brand.tsx index 95d3a34d5..7ae1231ee 100644 --- a/js/app/src/screens/brand.tsx +++ b/js/app/src/screens/brand.tsx @@ -678,7 +678,7 @@ export default function BrandScreen() { /> void }) { marginBottom: 10, }} > - Try again + + Try again + - Reload the page + + Reload the page + ); diff --git a/js/scripts/check-tokens.mjs b/js/scripts/check-tokens.mjs index 62e8f9e76..b8264243c 100644 --- a/js/scripts/check-tokens.mjs +++ b/js/scripts/check-tokens.mjs @@ -1,7 +1,7 @@ #!/usr/bin/env node // Token ratchet: counts hardcoded style literals (hex colors, rgb()/rgba(), // raw palette-ramp indexing) in component code and fails if the count ever -// increases relative to docs/redesign/token-baseline.json. +// increases relative to brand/token-count.json. // // Usage: // node js/scripts/check-tokens.mjs # check against baseline @@ -16,7 +16,7 @@ import { dirname, join, relative } from "node:path"; import { fileURLToPath } from "node:url"; const repoRoot = join(dirname(fileURLToPath(import.meta.url)), "..", ".."); -const BASELINE = join(repoRoot, "docs", "redesign", "token-baseline.json"); +const BASELINE = join(repoRoot, "brand", "token-count.json"); const SCAN_DIRS = [ "js/app/src", diff --git a/package.json b/package.json index 2d3c02554..49b8e0c83 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,9 @@ }, "scripts": { "preinstall": "node hack/node-version.js", - "check": "knip && pnpm run check:workspaces && git ls-files | xargs prettier --check --ignore-unknown", + "check": "knip && pnpm run check:tokens && pnpm run check:workspaces && git ls-files | xargs prettier --check --ignore-unknown", "check:workspaces": "cd js/app && pnpm run check && pnpm --filter @streamplace/e2e-web run typecheck", + "check:tokens": "node js/scripts/check-tokens.mjs", "fix": "git ls-files | xargs prettier --write --ignore-unknown", "prepare": "husky && pnpm --filter @streamplace/i18n compile && pnpm --filter @streamplace/brand generate", "brand": "pnpm --filter @streamplace/brand generate",