Skip to content

Adopt the Bike Shaving house style - #386

Open
brainkim wants to merge 21 commits into
mainfrom
chore/b9g-eslint-config
Open

brainkim wants to merge 21 commits into
mainfrom
chore/b9g-eslint-config

Conversation

@brainkim

@brainkim brainkim commented Aug 28, 2026 •

Copy link
Copy Markdown
Member

Adopts the Bike Shaving house style across the repo: ESLint 8 + .eslintrc.json + prettier are replaced by the flat config importing @b9g/eslint-config@0.1.7 with eslint-plugin-esfold@0.1.10. The adoption drove eight upstream releases of the style packages; this branch tracks their final state. Net −689 lines across 133 files.

What's in here

  1. it.each restored — reverts the nine for-of conversions from Run the package test suites through libuild #376. libuild 0.2.20 implements .each on node, so both platforms report per-case results.
  2. The config swap — ESLint 9, eslint.config.js, prettier and its plugins removed. The lint script is just eslint ..
  3. The format sweep — esfold's join/break model applied everywhere. The signature rule, settled by cross-repo quorum: heads may ride the opener; operand-list members may not. Binary, arithmetic, and string-concat assignments, object values, and comment-pinned if conditions all break at the header with members at one aligned level; ternaries keep their condition on the assignment line with ?/: aligned beneath; plain ternary chains are flat, JSX chains keep the staircase.
  4. Star binding — function *named, *[Symbol.iterator](), async *[Symbol.asyncIterator](): the star binds to the name, so methods need no special case.
  5. Declaration restructures (type-surface neutral, the part worth the closest review): Element's members and Context's typed event-listener overrides moved from merged interfaces into class-body declares; module-local symbol slots are class-body declares; Context's merged interface is the bare extends Crank.Context {} hook, colocated with the class alongside ProvisionMap, EventMap, and the listener types.
  6. ~180 hand fixes carried from the original adoption — return types, ACROCase renames, flattened optional chains, typed catches.

Repo-level overrides

Three blocks in eslint.config.js, each with its justification inline; zero eslint-disables came out of this work.

  • docs/ and skills/ ignored.
  • Examples stay 2-space-indented (the indent width lives in three rules; the override sets all three).
  • Tests keep the crank plugin's component rules off (tests deliberately write non-idiomatic components) and jsx-curly-brace-presence off — those tests assert the exact children the renderer receives, and unwrapping {"a"} can merge children while rendered output stays identical.

Verification

  • Core: 618 passed on chromium; eslint-plugin-crank 126 passed; crankdown typecheck + tests clean.
  • eslint . and root tsc --noEmit fully clean; every changed website file parser-verified (website's own tsc is pinned to TS 4.9 with preexisting, untouched failures).
  • Known cosmetic residuals tracked upstream in esfold, non-blocking: union bars inside casts/generics, and the second operand of an === inside if indenting one deeper than the first.

🤖 Generated with Claude Code

https://claude.ai/code/session_019pggktip8wsuxzy2VCY9p7

@brainkim
brainkim force-pushed the chore/b9g-eslint-config branch 3 times, most recently from cd4e509 to deb711c Compare August 28, 2026 07:12
@brainkim
brainkim force-pushed the chore/b9g-eslint-config branch from deb711c to 9ef6606 Compare September 5, 2026 01:16
@brainkim
brainkim force-pushed the chore/b9g-eslint-config branch 2 times, most recently from d19c6ea to fbcafd5 Compare September 14, 2026 17:42
brainkim and others added 21 commits September 14, 2026 13:45
libuild 0.2.20 implements .each on node, so the cases run on both
platforms with per-case reporting restored. 126 passed on bun and node.

This reverts commit c377f5b.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019pggktip8wsuxzy2VCY9p7
Replaces ESLint 8, .eslintrc.json and prettier with the flat house
config, and applies its autofixes across the repo. Overrides mirror the
old config's intent: examples stay space-indented, and tests keep the
crank plugin's component rules off. Two repo-specific settings: empty
single-extends interfaces are allowed because ProvisionMap, EventMap
and ChildIterable are extension points, and tagged templates are
allowed as statements because tests invoke the jsx tag to assert it
throws. One rule is off for tests: the jsx-curly-brace-presence fixer
joins adjacent string expression children into a single text node,
which changes rendered output that the tests assert exactly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019pggktip8wsuxzy2VCY9p7
Explicit return types on every function declaration and exported
component; real signatures for the remaining bare Function types;
ACROCase renames; deep optional chains flattened into named locals;
catch clauses bind their errors; reasoned inline disables for the
class/interface declaration merging behind Element and Context and for
the empty extension-point interfaces in the global Crank namespace.
crankdown's Marked returns Array<Element | string>, its real return
type.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019pggktip8wsuxzy2VCY9p7
Reformats the repo under the new esfold/breaks rule (eslint-plugin-esfold
0.1.5, whose fixer now converges) and answers the new design rules:
Element's members move from its merged interface into the class body, the
event method typings move from Context's merged interface into the class,
and the symbol slots on Context and CustomEventTarget move into their
merged interfaces. The examples override gains indent-binary-ops to match
its 2-space indent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019pggktip8wsuxzy2VCY9p7
Stars now bind to the name (*[Symbol.iterator], async *[Symbol.asyncIterator]).
With @b9g/no-symbol-fields removed, the symbol slots return to class-body
declare fields, which drops Context's generic merged interface and its
unused-vars disable, and restores the inline handleEvent union now that
esfold 0.1.6 tiles it correctly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019pggktip8wsuxzy2VCY9p7
The indent rule no longer has an opinion on class heritage clauses, so the
continuation line takes one level of indentation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019pggktip8wsuxzy2VCY9p7
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019pggktip8wsuxzy2VCY9p7
The isHuggable fix unhugs object-pattern parameters which carry a type
literal, and long string arguments wrap again instead of joining past the
line limit. The indent carveouts do not rewrite existing code, so the
stair-stepped ternary chains and the cascaded scope assignment are
hand-flattened to the fresh-format shapes. JSX chains keep the staircase
by design.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019pggktip8wsuxzy2VCY9p7
One-parameter signatures returning a union break at the parameter list
again instead of stranding the union's tail.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019pggktip8wsuxzy2VCY9p7
A group heading an operand list takes the header break whenever the list
spans lines, so binary and arithmetic assignments, object values, and
comment-pinned if conditions all put their operands at one aligned level.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019pggktip8wsuxzy2VCY9p7
@b9g/eslint-config 0.1.8 sets curly to multi-line, so the 86 guard
clauses braced by the original adoption return to their one-line form.
eslint-plugin-esfold 0.1.11 breaks a type's brackets before the union
inside them (crankdown's Marked return type), and with indent-binary-ops
off, the === operands in the hydration URL check align by hand to match
fresh output.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019pggktip8wsuxzy2VCY9p7
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019pggktip8wsuxzy2VCY9p7
esfold does not reach inside template literals, so the markup in jsx and
css templates is aligned manually: contents sit one level deeper than the
line which opens the template, and the closing backtick matches it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019pggktip8wsuxzy2VCY9p7
The matrix layout shows each piece's shape, so the block opts out of
esfold's joining.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019pggktip8wsuxzy2VCY9p7
Reports for a multi-line group start at the group's opening line, so
eslint-disable-next-line esfold/breaks suppresses the whole group and
survives the fix pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019pggktip8wsuxzy2VCY9p7
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019pggktip8wsuxzy2VCY9p7
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019pggktip8wsuxzy2VCY9p7
@brainkim
brainkim force-pushed the chore/b9g-eslint-config branch from fbcafd5 to 866575c Compare September 14, 2026 17:46

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant