Skip to content

refactor(core): finish monolith split — all node types in nodes/#79

Merged
teezeit merged 4 commits intomainfrom
split-rest
May 1, 2026
Merged

refactor(core): finish monolith split — all node types in nodes/#79
teezeit merged 4 commits intomainfrom
split-rest

Conversation

@teezeit
Copy link
Copy Markdown
Owner

@teezeit teezeit commented 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)

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

  • pnpm typecheck clean
  • pnpm test — 1,180 / 1,180 passing
  • pnpm review:refresh — zero snapshot drift ("log unchanged"). Byte-equivalence with the legacy switches is proven.
  • 17 KNOWN_FAILURES still .fails — refactor neither fixes nor breaks parser bugs.
  • 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

teezeit and others added 4 commits May 1, 2026 22:23
The repo-wide rule `*.md text eol=lf` was forcing the CRLF fixture to
LF on every checkout, surfacing a phantom modification on every
branch. A more-specific rule restores byte preservation for that one
file without affecting any other markdown.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Checkpoint commit on the way to PR 3 (full migration). Migrates 21
node types end-to-end through the registry/strategy contract:

- container (recursion validator: 11 subtypes, sidebar-main special case)
- layout: nav, nav-item, brand, grid, grid-item, row
- content: heading, paragraph, text, image, icon, link, list,
  list-item, table, table-header, table-row, table-cell, blockquote,
  code

Each migrated type:
- nodes/<type>/{html,react,tailwind,index}.ts — self-contained module
  exporting NodeDefinition; legacy case arm + function deleted from
  the four legacy renderer files.
- icon/_iconmap.ts — shared icon character map (60+ glyphs for HTML,
  smaller subset for React/Tailwind, social-icons list).

Helpers exposed from legacy renderers (now `export`):
- html-renderer: applyRadioGroupName (was already), buildClasses,
  escapeHtml, sourceLine, renderChildrenList (was already), renderNode
- react-renderer: applyRadioGroupName, buildClasses, escapeJSX,
  renderNode, repeatString
- tailwind-renderer: applyRadioGroupName, escapeHtml, renderNode

Tests:
- registry.test.ts updated to use a deliberately-bogus type for the
  unknown-type branch (previously used `text`, now migrated).

Verification:
- pnpm typecheck: clean
- pnpm test: 1,180 / 1,180 passing
- pnpm review:refresh: zero snapshot drift ("log unchanged")

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Completes the strangler-fig migration of all renderable node types
into self-contained modules under src/nodes/. The three legacy
renderers shrink from 2,214 lines to 383 lines (83% reduction); each
now consists of a thin registry-first dispatcher plus a fall-through
"Unknown node" comment for the unhandled types whose fixtures expect
that path.

Migrated this commit:
- Forms (6): input, textarea, select, checkbox, radio, radio-group.
  radio-group preserves the radio-group counter mutation pattern via
  the now-exported nextRadioGroupName helper.
- UI (7): badge, separator, comment, breadcrumbs (HTML-only),
  tabs (HTML-only, includes inline tabs script), tab (HTML-only),
  demo (HTML-only). React/Tailwind targets are intentionally absent
  on HTML-only nodes — the dispatcher falls through and emits the
  "Unknown node" comment those fixtures snapshot.

Contract change:
- nodes/_contract.ts — render targets are now optional. Many node
  types historically had no React/Tailwind renderer; instead of
  forcing every module to implement no-op stubs, the dispatcher just
  falls through to the "Unknown node" comment which preserves the
  snapshot bytes.

State + meta nodes (alert, accordion, accordion-item, breadcrumb-item,
loading-state, empty-state, error-state, form, option) had no
dedicated renderers in the legacy code — they hit the default arm.
Skipped: nothing to migrate.

Tests:
- registry.test.ts assertion weakened from "all three render targets
  exist" to "type matches and at least one render target exists" to
  reflect the optional-targets contract.

Verification:
- pnpm typecheck: clean
- pnpm test: 1,180 / 1,180 passing
- pnpm review:refresh: zero snapshot drift ("log unchanged")
- 17 KNOWN_FAILURES still .fails

Stats:
- 35+ node types migrated total (button, container, layout family,
  content family, forms family, UI family).
- src/nodes/: 136 files, ~30 node-type folders.
- src/renderer/{html,react,tailwind}-renderer.ts: 200 + 112 + 71 lines.
- Adding a new component is now ~"drop a folder + register it".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Documents the parse → AST → dispatch → render flow now that the
monolith split has landed. Includes a one-glance "adding a new
component" recipe pointing at src/nodes/<type>/ folders so future
contributors land in the right place.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@teezeit teezeit merged commit b97cd82 into main May 1, 2026
8 checks passed
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