Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude/skills/component-scaffold/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ description: Use when adding, moving, renaming, or reviewing a React component i
## Overview

Forty component directories live under `components/`, each expected to hold the
same four files. Nothing in the toolchain enforces that. `bun run check` runs
`tsgo`, `oxlint --type-aware`, `stylelint`, and `bun test`; none of them know a
same four files. Nothing in the toolchain enforces that. `bun run system-check` runs
`tsgo`, `oxlint`, `gale`, and `bun test`; none of them know a
component is missing its barrel, that a font size was typed as `15px` when
`--fs-section` is exactly `15px`, or that a `<div>` shipped with no class. The
convention is enforced by review, so it drifts.
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/style-critic/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ description: Use when SCSS/CSS is the subject of review or authoring in this rep
## Overview

Enforces the **CSS section of `CLAUDE.md`** — the twelve conventions are the
source of truth; this skill is how they get checked. `stylelint` lints SCSS syntax,
source of truth; this skill is how they get checked. `gale` lints SCSS syntax,
but nothing in the toolchain enforces the conventions themselves: tokens over
literals, gap over margins, named areas, reduced-motion guards. Convention held
only by review drifts, so this skill makes every rule mechanically checkable
Expand Down
7 changes: 5 additions & 2 deletions .github/workflows/pull-request-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,11 @@ jobs:
- name: 🧹 Lint TS
run: bun lint:ts

- name: 🎨 Lint CSS
run: bun lint:css
- name: 🎨 Lint SCSS
run: bun lint:scss

- name: 🤖 Lint Actions
run: bun lint:actions

- name: 🧪 Test
run: bun run test
Expand Down
7 changes: 5 additions & 2 deletions .github/workflows/sanity-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,11 @@ jobs:
- name: 🧹 Lint TS
run: bun lint:ts

- name: 🎨 Lint CSS
run: bun lint:css
- name: 🎨 Lint SCSS
run: bun lint:scss

- name: 🤖 Lint Actions
run: bun lint:actions

- name: 🧪 Test
run: bun run test
Expand Down
11 changes: 0 additions & 11 deletions .stylelintrc.json

This file was deleted.

229 changes: 15 additions & 214 deletions bun.lock

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions bunfig.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# Never write a `^` or `~` range into `package.json`; every dependency is pinned.
[install]
exact = true

[test]
preload = ["./test/preload.ts"]
coverageReporter = ["text", "lcov"]
Expand Down
31 changes: 18 additions & 13 deletions docs/tooling-rule-mapping.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ There was no Stylelint; SCSS was previously unlinted.

## Mapping

| Previous rule set | Now | Notes |
| -------------------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `@next/next/*` (core-web-vitals) | Oxlint `nextjs` plugin | Built-in port of `eslint-plugin-next` rules |
| `react` recommended subset | Oxlint `react` plugin | `react-in-jsx-scope` off — automatic JSX runtime, same as `eslint-config-next` |
| `react-hooks/rules-of-hooks` | `react/rules-of-hooks` (error) | Explicit in `.oxlintrc.json` |
| `react-hooks/exhaustive-deps` | `react/exhaustive-deps` (warn) | Explicit in `.oxlintrc.json`; existing `eslint-disable` comments honored by Oxlint |
| `jsx-a11y` subset | Oxlint `jsx-a11y` plugin | Full plugin enabled — strictly more coverage than before (see exceptions) |
| `typescript-eslint` recommended | Oxlint `typescript` plugin | Plus net-new type-aware rules via `oxlint-tsgolint` (`--type-aware`) — coverage the old setup never had |
| — (SCSS unlinted) | Stylelint `stylelint-config-recommended-scss` | Net-new coverage; found and fixed 2 duplicate-property bugs and 2 empty blocks on first run |
| Previous rule set | Now | Notes |
| -------------------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `@next/next/*` (core-web-vitals) | Oxlint `nextjs` plugin | Built-in port of `eslint-plugin-next` rules |
| `react` recommended subset | Oxlint `react` plugin | `react-in-jsx-scope` off — automatic JSX runtime, same as `eslint-config-next` |
| `react-hooks/rules-of-hooks` | `react/rules-of-hooks` (error) | Explicit in `.oxlintrc.json` |
| `react-hooks/exhaustive-deps` | `react/exhaustive-deps` (warn) | Explicit in `.oxlintrc.json`; existing `eslint-disable` comments honored by Oxlint |
| `jsx-a11y` subset | Oxlint `jsx-a11y` plugin | Full plugin enabled — strictly more coverage than before (see exceptions) |
| `typescript-eslint` recommended | Oxlint `typescript` plugin | Plus net-new type-aware rules via `oxlint-tsgolint` (`--type-aware`) — coverage the old setup never had |
| — (SCSS unlinted) | Gale (`stylelint-config-recommended-scss` ruleset) | Net-new coverage; found and fixed 2 duplicate-property bugs and 2 empty blocks on first run |

## Exceptions

Expand All @@ -46,20 +46,25 @@ Rules disabled in `.oxlintrc.json`, and why:
| `jsx-a11y/no-static-element-interactions` | Fires on the `onBlur` focus-containment wrapper in `TimelineCluster`. The rule is aimed at click and key handlers on static elements |
| `jsx-a11y/prefer-tag-over-role` | Suggests `<fieldset>`/`<select>`/`<output>` for `role="group"`/`"listbox"`/`"status"`. Most sites are inside an SVG canvas, where those tags are not available |

Rules tuned in `.stylelintrc.json`, and why:
Rules tuned in `gale.json`, and why:

| Rule | Reason |
| ---------------------------------- | ------------------------------------------------------------------------------------------------ |
| `no-descending-specificity` | Noisy specificity-ordering rule; conflicts with idiomatic `:hover` child-selector patterns |
| `property-no-unknown` | Kept on, with `corner-shape` allowed (CSS Borders L4, not yet in Stylelint's property DB) |
| `property-no-unknown` | Kept on, with `corner-shape` allowed (CSS Borders L4, not yet in Gale's property DB) |
| `selector-pseudo-class-no-unknown` | Kept on, with `:global`/`:local` allowed — CSS Modules pseudo-selectors, not real pseudo-classes |

`stylelint-config-recommended-scss` is the baseline rather than the `standard`
variant: the stylistic rules `standard` adds (`selector-class-pattern`,
The ruleset began as `stylelint-config-recommended-scss` and is now written out
in full in `gale.json`, so nothing named `stylelint` is installed. The
`recommended` set is the baseline rather than the `standard` variant: the
stylistic rules `standard` adds (`selector-class-pattern`,
`alpha-value-notation`, `color-function-notation`) conflict with camelCase CSS
Module class names and the repo's existing color notation. Stylistic enforcement
stays deferred per PRD goal 6; `oxfmt` handles SCSS formatting.

Gale implements the `scss/*` rules natively — no plugin package — and honors
`stylelint-disable` comments, which is why the existing ones still work.

`block-no-empty` stays on; the intentional empty marker classes in
`components/listSearch.module.scss` carry `stylelint-disable-next-line`
comments explaining themselves.
88 changes: 88 additions & 0 deletions gale.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
{
"rules": {
"at-rule-no-deprecated": true,
"block-no-empty": true,
"color-no-invalid-hex": true,
"custom-property-no-missing-var-function": true,
"declaration-block-no-duplicate-custom-properties": true,
"declaration-block-no-duplicate-properties": [
true,
{
"ignore": ["consecutive-duplicates-with-different-syntaxes"]
}
],
"declaration-block-no-shorthand-property-overrides": true,
"declaration-property-value-keyword-no-deprecated": true,
"font-family-no-duplicate-names": true,
"font-family-no-missing-generic-family-keyword": true,
"function-calc-no-unspaced-operator": true,
"function-linear-gradient-no-nonstandard-direction": true,
"keyframe-block-no-duplicate-selectors": true,
"keyframe-declaration-no-important": true,
"media-feature-name-no-unknown": true,
"media-type-no-deprecated": true,
"named-grid-areas-no-invalid": true,
"nesting-selector-no-missing-scoping-root": [
true,
{
"ignoreAtRules": ["mixin"]
}
],
"no-descending-specificity": null,
"no-duplicate-at-import-rules": true,
"no-empty-source": true,
"no-invalid-double-slash-comments": true,
"no-invalid-position-at-import-rule": [
true,
{
"ignoreAtRules": ["use", "forward"]
}
],
"no-invalid-position-declaration": [
true,
{
"ignoreAtRules": ["mixin"]
}
],
"no-irregular-whitespace": true,
"property-no-deprecated": true,
"property-no-unknown": [
true,
{
"ignoreProperties": ["corner-shape"]
}
],
"scss/at-extend-no-missing-placeholder": true,
"scss/at-if-no-null": true,
"scss/at-rule-no-unknown": true,
"scss/comment-no-empty": true,
"scss/declaration-nested-properties-no-divided-groups": true,
"scss/dollar-variable-no-missing-interpolation": true,
"scss/function-quote-no-quoted-strings-inside": true,
"scss/function-unquote-no-unquoted-strings-inside": true,
"scss/load-no-partial-leading-underscore": true,
"scss/load-partial-extension": [true, "never"],
"scss/no-duplicate-mixins": true,
"scss/no-global-function-names": true,
"scss/operator-no-newline-after": true,
"scss/operator-no-newline-before": true,
"scss/operator-no-unspaced": true,
"selector-anb-no-unmatchable": true,
"selector-pseudo-class-no-unknown": [
true,
{
"ignorePseudoClasses": ["global", "local"]
}
],
"selector-pseudo-element-no-unknown": true,
"selector-type-no-unknown": [
true,
{
"ignore": ["custom-elements"]
}
],
"string-no-newline": true,
"syntax-string-no-invalid": true,
"unit-no-unknown": true
}
}
12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@
"dev": "next dev --port 46642",
"build": "next build",
"start": "next start",
"lint": "bun run --sequential lint:ts lint:css",
"lint": "bun run --sequential lint:ts lint:scss lint:actions",
"lint:ts": "oxlint",
"lint:ts:fix": "oxlint --fix",
"lint:css": "gale \"{app,components,styles}/**/*.scss\"",
"lint:css:fix": "gale --fix \"{app,components,styles}/**/*.scss\"",
"lint:scss": "gale \"{app,components,styles}/**/*.scss\"",
"lint:scss:fix": "gale --fix \"{app,components,styles}/**/*.scss\"",
"lint:actions": "github-actionlint",
"typecheck": "tsgo --noEmit",
"test": "bun test --parallel --dots",
"test:watch": "bun test --parallel --dots --watch",
Expand All @@ -33,7 +34,7 @@
"oxfmt"
],
"*.{css,scss}": [
"stylelint --fix",
"gale --fix",
"oxfmt"
],
"*.{json,jsonc,json5,md,mdx,yml,yaml,toml,html}": "oxfmt"
Expand Down Expand Up @@ -62,14 +63,13 @@
"@types/react-svg-pan-zoom": "3.3.9",
"@typescript/native-preview": "7.0.0-dev.20260707.2",
"build-meta": "0.1.1",
"github-actionlint": "1.7.12",
"husky": "9.1.7",
"lint-staged": "17.5.1",
"oxfmt": "0.68.0",
"oxlint": "1.83.0",
"oxlint-tsgolint": "7.0.2001",
"sass": "1.104.1",
"stylelint": "17.15.0",
"stylelint-config-recommended-scss": "17.0.1",
"typescript": "6.0.3"
},
"trustedDependencies": [
Expand Down
Loading