refactor(core): migrate button renderers into nodes/button/#78
Merged
Conversation
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>
7 tasks
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>
2 tasks
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>
2 tasks
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
PR 2 of the monolith split — first node migrated end-to-end.
packages/core/src/nodes/button/{html,react,tailwind,index}.ts. The three render functions now live in self-contained modules; theindex.tsexports aNodeDefinition<'button'>.buttoninnodes/_registry.ts. The dispatcher hook from PR 1 now routes button nodes through the registry.case 'button':arms andfunction renderButton(...)definitions from all three legacy renderer files.buildClasses,escapeHtml,escapeJSX,sourceLine,repeatString) so node modules can reuse them. Future PRs will move them tosrc/nodes/_helpers.tsonce 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 viawiremdContainerMDAST nodes.Test plan
pnpm typecheckcleanpnpm 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.KNOWN_FAILURESstill.fails— refactor neither fixes nor breaks parser bugs.🤖 Generated with Claude Code