Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
a1a90c9
build(deps): pin exact versions and restrict install scripts
Savid Nov 11, 2026
916721e
build(deps): refresh transitive dependencies within their ranges
Savid Nov 11, 2026
108d554
build: move to Node 26, pnpm 12 and Playwright 1.65
Savid Nov 11, 2026
9b5679a
build: add knip and drop the dependencies it finds unused
Savid Nov 11, 2026
87c6dbd
refactor: remove the files and exports knip finds unused
Savid Nov 11, 2026
0519004
test: add v8 coverage to the unit tests
Savid Nov 11, 2026
1654b2f
style: check formatting of every file Prettier reads
Savid Nov 11, 2026
9abe867
ci: pin actions by SHA and run format, knip and Storybook builds
Savid Nov 11, 2026
9557623
docs: list the knip, format check and coverage commands
Savid Nov 11, 2026
5844d0c
build(api): generate a client for lab-backend's own routes
Savid Nov 11, 2026
8996f75
refactor(data): read config and bounds through the lab-backend client
Savid Nov 11, 2026
02caf89
refactor(slots): download blocks and blobs through the lab-backend cl…
Savid Nov 11, 2026
a03a70d
refactor(gas-profiler): read gas schedules and simulate blocks throug…
Savid Nov 11, 2026
b7ded7a
refactor(popout-card): decode the copied image without fetch
Savid Nov 11, 2026
a89c863
docs(data): read server data only through the generated clients
Savid Nov 11, 2026
05fe10c
refactor(styles): move theme tokens to light-dark() pairs and enforce…
Savid Nov 11, 2026
d8c07df
docs(storybook): add Foundations pages generated from the design tokens
Savid Nov 11, 2026
8e6aed1
fix(a11y): fix the axe violations stories hit in shared components
Savid Nov 11, 2026
e2d5c59
fix(charts): stop Map2D painting when it unmounts
Savid Nov 11, 2026
1b4fe70
test: share test-utils and MSW handlers, check stories under every theme
Savid Nov 11, 2026
4e771ca
refactor: import through the @/ alias
Savid Nov 11, 2026
0b2b712
build(ts): typecheck tests and stories, with noUncheckedIndexedAccess
Savid Nov 11, 2026
a5c018c
build(lint): move to ESLint 10 and skeleton's plugin set
Savid Nov 11, 2026
26d3636
refactor(react): use React 19's context and ref APIs
Savid Nov 11, 2026
c03d51a
chore(lint): fail on warnings and dead disable directives
Savid Nov 11, 2026
68b4d4f
refactor(react): satisfy the React Compiler rules
Savid Nov 11, 2026
81ebbed
refactor(react): key lists by identity and drop cloneElement
Savid Nov 11, 2026
fd2e033
refactor(styles): move fixed inline style values to token classes
Savid Nov 11, 2026
2f1b6c3
test: lint tests with the vitest plugin and run every skipped test
Savid Nov 11, 2026
80a81d7
test(storybook): build random story data from a seeded generator
Savid Nov 11, 2026
c38c06f
docs: rules for loading states, React and tests
Savid Nov 11, 2026
6fe7095
chore(lint): reject fixed values in inline styles
Savid Nov 11, 2026
6c97ae8
fix(data): go back to a default staleTime of 0
Savid Nov 11, 2026
d552a7e
fix: style only the loading overlay before React mounts
Savid Nov 12, 2026
57bee43
build(api): format the generated clients again
Savid Nov 12, 2026
85eb73a
fix(data): load lab-backend data whatever its shape
Savid Nov 12, 2026
2053a5e
docs(skills): build components to this branch's rules
Savid Nov 12, 2026
3ae5f51
test: keep only the useConfig and useBounds tests that test Lab's code
Savid Nov 12, 2026
02ddc5e
test(styles): measure contrast with culori
Savid Nov 12, 2026
99610c2
docs(styles): say what the contrast minimums are
Savid Nov 12, 2026
b465cb9
refactor: read the clock through one useNow hook
Savid Nov 12, 2026
43096d3
refactor(react): map skeleton placeholders again
Savid Nov 12, 2026
00f95a9
test: configure the generated clients before test modules load
Savid Nov 12, 2026
e97e6f1
test: drop the bounds handlers nothing uses
Savid Nov 12, 2026
97eb435
refactor(popout-card): turn the copied image into a Blob with fetch a…
Savid Nov 12, 2026
0a75107
test: drop the api-error and Breadcrumb unit tests
Savid Nov 12, 2026
ec9b97b
fix(slot-player): stop logging every maxSlot calculation
Savid Nov 12, 2026
20d8b23
test(design-docs): drop the check that every token namespace has a page
Savid Nov 12, 2026
fb279ef
chore(lint): say why opacity modifiers are rejected
Savid Nov 12, 2026
2a1f5ae
fix(slot-player): idle while waiting at the live edge
Savid Nov 12, 2026
dba48fd
refactor(slots): size the timeline rows with classes
Savid Nov 12, 2026
fad6f5b
refactor(slots): draw the attestation deadline band with w-1/3
Savid Nov 12, 2026
e5fab34
fix(hooks): read the clock again when the tab is shown
Savid Nov 12, 2026
df3a09a
style: use the canonical form of 88 Tailwind class pairs
Savid Nov 12, 2026
653f6d3
build(deps): move to Tailwind 4.3.5 so full lint runs check canonical…
Savid Nov 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
12 changes: 12 additions & 0 deletions .claude/rules/loading-states.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
paths:
- '**/src/pages/**/*.tsx'
- '**/src/components/**/*.tsx'
---

# Loading states

- Every asynchronous read renders loading, error and settled states, and empty where that can happen. Tests and stories cover each one.
- Loading renders a skeleton in the shape of the content it stands in for, built from `LoadingContainer` (`src/components/Layout/LoadingContainer/`). A page's skeleton is `[PageName]Skeleton`, a component's `[ComponentName]Skeleton`, in that page's `components/` folder.
- Data goes stale when a refetch fails or a live feed stops. Show that a value is stale or unavailable; never present an old value as current.
- While data loads, controls that would act on it are disabled.
13 changes: 13 additions & 0 deletions .claude/rules/react.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
paths:
- '**/src/**/*.tsx'
- '**/src/**/*.ts'
---

# React

- Components are function declarations returning `JSX.Element`, defined at module level (never inside another component), with props typed in `<Name>.types.ts`.
- Derive values during render. Don't copy props or query data into state, or set state in an effect to mirror something you can compute; to reset state when a prop changes, give the component a `key` or compare with the previous value during render.
- Keep render pure: no `Date.now()`, `Math.random()` or `ref.current` during render. Take the time from a clock hook or a prop, and read refs in effects and event handlers.
- Key list items by what identifies them in the data, never by their index.
- `useMemo`/`memo` only for expensive work or for an identity a downstream memo depends on. The React Compiler's rules run in lint only; the build does not memoize for you.
17 changes: 16 additions & 1 deletion .claude/rules/storybook.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,30 @@
---
paths:
- '**/src/**/*.stories.tsx'
- '**/src/**/*.mdx'
- '**/.storybook/**'
---

# Storybook

When creating a new story, add the following decorators:

```tsx
decorators: [
Story => (
<div className="min-w-[600px] rounded-sm bg-surface p-6">
<div className="min-w-150 rounded-sm bg-surface p-6">
<Story />
</div>
),
],
```

When choosing a title, use the full nested path to the story, e.g. `Components/Layout/Container`.

## Stories are tests

Every story is a test in Chromium under the light, dark and star themes (`pnpm test:storybook`). A failed `play`, a console error, an accessibility violation or an unmocked `/api/` request fails it. Fix the story or the component; the `test-exclude` tag is only for stories that never settle (endless timers, WebGL).

- Mock the API per story with `parameters.msw.handlers`, reusing `@/test-utils/handlers`: the pending handler for loading, the failing one for errors. A keyed handler (`config`, `bounds`) replaces the preview's default for that key.
- Fixture times come from `NOW_MS` in `@/test-utils/time`. A component that shows times relative to now gets `beforeEach: freezeNow` on its meta. Random data comes from `seededRandom` in `@/test-utils/random`, so a story renders the same on every run.
- Each `play` awaits the state its story shows, with `findBy…` for async data. Await every `expect` and `userEvent`.
42 changes: 42 additions & 0 deletions .claude/rules/styling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
paths:
- '**/src/**/*.tsx'
- '**/src/**/*.ts'
- '**/src/**/*.css'
- '**/src/**/*.mdx'
---

# Styling

- Every value is a token in `src/styles/tokens.css`, used as a Tailwind class. Tailwind's defaults are removed, so
`bg-red-500`, `text-white`, `text-7xl` and `shadow-2xl` do not exist. Spacing and sizing use the 4px step (`p-4`,
`gap-6`, `h-75`); each text size carries its line height, and `text-sm/6` sets another.
- Lint rejects classes and colours that bypass the tokens: arbitrary values (`h-[300px]`), `dark:` variants,
`leading-*`, opacity modifiers (`bg-primary/10`), literal values in `style`, and colour literals in SVG colour
attributes. `style` only carries values computed at runtime, such as a height passed as a prop. Lint can't see a fixed
value behind a constant (`height: ROW_HEIGHT`), so make that a class too, unless the constant keeps several elements
aligned. It reads class strings in `className` and `…ClassName` props, `clsx`, the router's `activeProps` and
`inactiveProps`, class constants and variant maps named `…ClassName`, `…Classes` or `…Styles`, and arrow-function
helpers named `get…Class` or `get…Styles`. Write each class whole (not `` `text-${tone}` ``) so lint checks it and
Tailwind finds it.
- Name tokens for their role (`danger`, `muted`, `panel`), never their look. Tags and chips name their category.
- Every base colour is a `light-dark(light, dark)` pair with its own value in `html.star`. Lab has three themes: light
and dark are the two sides of each pair, and star is a dark scheme with its own palette. Colours derived from the
base ones (tints, `panel`, `on-solid`) follow all three. Give a colour a pair or use a derived token; never a
`dark:` variant. `on-dark:` is for what a token cannot carry, such as swapping a logo, and never takes a colour.
- Layouts Tailwind has no class for (grid templates, a size less the viewport, card insets) are named utilities in
`src/styles/utilities.css`.
- No state is carried by colour alone: a word or a mark goes with it.
- Charts read colours through `useThemeColors()` and `src/utils/dataVizColors.ts`, which resolve the tokens for the
active theme.

## Adding a token

1. Add it to the `@theme static` block in `tokens.css`, in Tailwind's namespace for it (`--color-*`, `--text-*` with
its `--text-*--line-height`, `--radius-*`, `--container-*`, …). A base colour is `light-dark()` of two hex values,
with its star value in `html.star`. Any other colour derives from the base ones with `var()`, or is a tint:
`--color-primary-15: color-mix(in oklab, var(--color-primary) 15%, transparent)`.
2. Add a base colour to its group in `src/styles/tokens.ts`, and to a `CONTRAST_RULES` entry or to
`CONTRAST_EXEMPT` with the reason. A derived colour goes in `DERIVED_COLORS`.
3. `pnpm test:unit` checks the file, the colour names and tints, contrast in all three themes, and that `index.html`
and `ThemeProvider` give the browser each theme's background.
13 changes: 13 additions & 0 deletions .claude/rules/tests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
paths:
- '**/src/**/*.test.ts'
- '**/src/**/*.test.tsx'
- '**/src/test-utils/**'
---

# Tests

- Unit tests render through `@/test-utils` (`render`, `renderHook`: the app's query client without retries and a router; pass `client`, `wrapper` or `initialProps` when a test needs them) and mock the API with `server.use(...)` MSW handlers. A request no handler answers fails the test. Don't stub `fetch`; lint rejects importing `@testing-library/react` directly.
- `src/test-utils/handlers/<domain>.ts` holds a fixture typed with the app's types and a `<domain>Handlers` object with a handler for each state a test or story uses (such as `success`, `failure`, and `pending`, which never resolves), re-exported from `handlers/index.ts`. Unit tests and stories share them.
- Fixtures, tests and stories take the current time from `@/test-utils/time` (`NOW_MS`), never `Date.now()` or `new Date()`, and random data from `seededRandom` in `@/test-utils/random`, never `Math.random()`.
- Fix a flaky or failing test; lint fails on `.only` and `.skip`.
28 changes: 0 additions & 28 deletions .claude/rules/theming.md

This file was deleted.

18 changes: 9 additions & 9 deletions .claude/skills/clean-copy/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ field, or the value beside it already says the thing, the caption is noise. A fi
The tell: copy that rules out a failure rather than describing a behaviour.

> Cancel upload
> *Always keeps the files already transferred — never wipes the folder like the old cancel did.*
> _Always keeps the files already transferred — never wipes the folder like the old cancel did._

That second clause exists because of a bug. The user doesn't know about the bug, doesn't share
your memory of fixing it, and now has a worry they arrived without. **The fix for a fixed bug
Expand All @@ -48,16 +48,16 @@ component; an explanatory `<p>` gets deleted, not shortened to a stub.

Copy stays when the user genuinely cannot act without it:

- A real consequence at the moment of choosing. Under a destructive button: *"Anything not yet
synced is lost."* They need it before they click, and they can't infer it.
- A compatibility or scope fact they can't see. *"Works with keys issued after March; older
keys need rotating first."* The checkbox alone doesn't tell them whether it applies to them.
- What a blank state means and what to do next. *"No projects yet. Create one and it will
appear here."*
- A real consequence at the moment of choosing. Under a destructive button: _"Anything not yet
synced is lost."_ They need it before they click, and they can't infer it.
- A compatibility or scope fact they can't see. _"Works with keys issued after March; older
keys need rotating first."_ The checkbox alone doesn't tell them whether it applies to them.
- What a blank state means and what to do next. _"No projects yet. Create one and it will
appear here."_
- Units, scale, or format on a bare value.

Note the first example is one sentence. Had it continued *"— only use this when a sync is
stuck"*, that half would go: it instructs the user on when they're allowed to press a button
Note the first example is one sentence. Had it continued _"— only use this when a sync is
stuck"_, that half would go: it instructs the user on when they're allowed to press a button
they can already see. Keep the consequence, cut the permission slip.

## Working style
Expand Down
Loading
Loading