Skip to content

refactor(core): migrate button renderers into nodes/button/#78

Merged
teezeit merged 1 commit intomainfrom
split-button
May 1, 2026
Merged

refactor(core): migrate button renderers into nodes/button/#78
teezeit merged 1 commit intomainfrom
split-button

Conversation

@teezeit
Copy link
Copy Markdown
Owner

@teezeit teezeit commented May 1, 2026

Summary

PR 2 of the monolith split — first node migrated end-to-end.

  • Created packages/core/src/nodes/button/{html,react,tailwind,index}.ts. The three render functions now live in self-contained modules; the index.ts exports a NodeDefinition<'button'>.
  • Registered button in nodes/_registry.ts. The dispatcher hook from PR 1 now routes button nodes through the registry.
  • Deleted the case 'button': arms and function renderButton(...) definitions from all three legacy renderer files.
  • Exposed previously-private helpers (buildClasses, escapeHtml, escapeJSX, sourceLine, repeatString) so node modules can reuse them. Future PRs will move them to src/nodes/_helpers.ts once the legacy files empty out.

Scope decision: parser stays put for now

Button parsing is regex-on-paragraphs with dropdown lookahead, tightly coupled with transformParagraph. Migrating the parser side requires designing the parser-side dispatch contract (which keys on MDAST type, not wiremd type — different from the renderer contract). PR 3 (container) is the right place to design that — container parsing has clean boundaries via wiremdContainer MDAST nodes.

Test plan

  • pnpm typecheck clean
  • pnpm test — 1,181 / 1,181 passing (was 1,180)
  • pnpm review:refresh — zero snapshot drift ("log unchanged"). Byte-equivalence with the legacy switch is proven.
  • 17 KNOWN_FAILURES still .fails — refactor neither fixes nor breaks parser bugs.
  • Public API unchanged.
  • CI green
  • Smoke test: render any fixture containing buttons, eyeball the output

🤖 Generated with Claude Code

PR 2 of the monolith split. First node migrated end-to-end through the
registry/strategy contract.

What moved:
- src/nodes/button/{html,react,tailwind}.ts — copies of the three
  renderButton functions, now self-contained modules importing only
  the public helpers (buildClasses, escapeHtml/JSX, sourceLine,
  repeatString, renderNode for recursion).
- src/nodes/button/index.ts — exports the NodeDefinition<'button'>.
- src/nodes/_registry.ts — registers `button`. The renderer dispatchers
  now route button nodes through the registry; the legacy switch arms
  are deleted.

What stays:
- Button parsing remains in src/parser/transformer.ts. Button is
  parsed via paragraph regex with dropdown lookahead — deeply tangled
  with paragraph parsing. The parser-side dispatch contract is best
  designed in PR 3 (container) where parse boundaries are clean.

Helpers exposed (now `export`):
- html-renderer.ts: buildClasses, escapeHtml, sourceLine
- react-renderer.ts: buildClasses, escapeJSX, repeatString
- tailwind-renderer.ts: escapeHtml

These were already de-facto shared; making them exported lets node
modules import them without duplication. Future PRs will move the
implementations to src/nodes/_helpers.ts once the legacy files are
empty enough.

Tests:
- tests/registry.test.ts — updated the "registry empty" assertion to
  the explicit migrated-types inventory (currently `['button']`). Adds
  a contract check that the registered button definition has all three
  render targets as functions.

Verification:
- pnpm typecheck: clean
- pnpm test: 1,181 / 1,181 passing (was 1,180; +1 for the new contract
  assertion)
- pnpm review:refresh: zero snapshot drift ("log unchanged")
- 17 KNOWN_FAILURES still .fails

Public API unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@teezeit teezeit merged commit ec58fbd into main May 1, 2026
8 checks passed
teezeit added a commit that referenced this pull request May 1, 2026
## Summary

Final PR of the monolith split. All node types with renderable output
now live in self-contained modules under
`packages/core/src/nodes/<type>/`. The three legacy renderer files
shrink from 2,214 lines to 383 lines (83% reduction) — each is now a
thin registry-first dispatcher with a fall-through "Unknown node"
comment for types whose fixtures expect that path.

**Adding a new component is now: drop a folder, register it, write
fixtures.**

## What's migrated (this PR + earlier squash-merged PRs #77 #78)

- **Container family**: container (11 subtypes incl. sidebar-main
extractor as recursion validator)
- **Layout** (6): nav, nav-item, brand, grid, grid-item, row
- **Content** (14): heading, paragraph, text, image, icon, link, list,
list-item, table, table-header, table-row, table-cell, blockquote, code
- **Forms** (6): input, textarea, select, checkbox, radio, radio-group
- **UI** (7): badge, separator, comment, breadcrumbs (HTML-only), tabs
(HTML-only), tab (HTML-only), demo (HTML-only)
- Plus: button (PR #78), styles split (PR #77)

**Skipped intentionally**: alert, accordion, accordion-item,
breadcrumb-item, loading-state, empty-state, error-state, form, option —
these had no dedicated renderers in the legacy code (fall through to
default), so there's nothing to migrate.

## Contract change

`NodeDefinition.render.{html,react,tailwind}` are now **optional**. Many
node types historically had no React/Tailwind renderer — instead of
forcing every module to implement no-op stubs, the dispatcher falls
through to the "Unknown node" comment, preserving snapshot bytes
exactly.

## Stats

| | Before | After |
|---|---|---|
| html-renderer.ts | 953 | 200 |
| react-renderer.ts | 638 | 112 |
| tailwind-renderer.ts | 623 | 71 |
| **total legacy** | **2,214** | **383** (-83%) |
| nodes/ files | 0 | 136 |
| node folders | 0 | ~30 |

Plus: 1 small chore commit fixing the CRLF fixture's gitattributes rule
(was showing as modified on every branch due to autocrlf interaction).

## Test plan

- [x] `pnpm typecheck` clean
- [x] `pnpm test` — 1,180 / 1,180 passing
- [x] `pnpm review:refresh` — zero snapshot drift ("log unchanged").
Byte-equivalence with the legacy switches is proven.
- [x] 17 `KNOWN_FAILURES` still `.fails` — refactor neither fixes nor
breaks parser bugs.
- [x] Public API unchanged.
- [ ] CI green
- [ ] Smoke test: render a complex fixture in the editor; eyeball each
style.

## What's next (separate PRs, not this one)

- **CONTRIBUTING.md** update: replace the 13-step Feature Development
Checklist with "drop a `nodes/<type>/` folder, register it, add
fixtures."
- **Knock out parser bug clusters** now that the parser still in
`transformer.ts` can be tackled with a clean slate. The 17
`KNOWN_FAILURES` are in scope for a future cleanup pass.
- Optional: parser-side migration (split `transformer.ts` per node
type). The renderer split is the hard half; the parser is mostly
mechanical from here.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
teezeit added a commit that referenced this pull request May 1, 2026
…ecture (#80)

## Summary

Both files still described the pre-split flow ("add a case to
html-renderer.ts, react-renderer.ts, tailwind-renderer.ts") even though
those switches were deleted in #77 / #78 / #79. New contributors
following the old checklist would land in the wrong place.

**No code change** — pure docs.

### CLAUDE.md
- Architecture tree shows the new `nodes/<type>/` layout, the registry +
contract files, and the per-theme styles split.
- New "Render flow (Strategy + Registry)" paragraph explains the
dispatcher → registry → node module flow and the intentional "Unknown
node" fall-through for nodes without per-target renderers.
- New "Adding a new component" pointer to the CONTRIBUTING checklist.
- Classic tests list adds `registry.test.ts` and `styles-split.test.ts`
(added in #77).

### CONTRIBUTING.md "Feature Development Checklist"
- Replaced the three "add a case" bullets with one **Node module**
bullet pointing at `src/nodes/<your-type>/`.
- Notes that React and Tailwind targets are **optional** — many UI nodes
(tabs, breadcrumbs, demo) ship HTML-only and the dispatcher falls
through cleanly.
- CSS bullet points at `renderer/styles/_structural.ts` and the
per-theme files instead of the old monolithic `styles.ts`.
- Removed the "Renderer index" bullet — `renderer/index.ts` no longer
needs per-component plumbing.

## Test plan

- [x] No code touched; nothing to test.
- [ ] Skim both files for accuracy.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
teezeit added a commit that referenced this pull request May 1, 2026
## Summary

Self-contained brief for the next architectural beat after the renderer
split. **No code change** — just the planning doc at
`.github/dev-docs/parser-contract-plan.md`.

The renderer monolith is split (#77 #78 #79 #80). The next high-leverage
move is **not** splitting `parser/transformer.ts` per file — it's fixing
the contract: explicit `TransformContext` with `peekNext`/`consumeNext`,
plus isolated per-node parse tests that don't go through the full
markdown→AST→render pipeline.

The doc explains why (the file split is cosmetic; the leverage is the
contract), what to build (`_context.ts` + test helpers + per-node
tests), and what NOT to do (no file split in this PR, no syntax change,
no bug fix). It also captures the user's pending syntax direction
(`((pill))` over `|pill|`, `:::columns` over `:::grid`, etc.) so the
next session has the destination clear before starting work.

Two syntax ideas got pushed back during discussion and are flagged for
design calls before any code:
- Free-floating `{.right}/{.center}/{.left}` "applies until resolved" —
stateful-parsing trap; counter-proposed "applies to nearest element
only."
- Navbar `[[ a | b | c ]]` `|` separators — collides with table syntax,
new escape rules; counter-proposed alignment classes on items.

## Test plan

- [x] No code touched.
- [ ] Skim the doc for accuracy.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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