Skip to content

feat: nested container support#83

Open
teezeit wants to merge 6 commits intoakonan:mainfrom
teezeit:feat/nested-containers
Open

feat: nested container support#83
teezeit wants to merge 6 commits intoakonan:mainfrom
teezeit:feat/nested-containers

Conversation

@teezeit
Copy link
Copy Markdown
Contributor

@teezeit teezeit commented Apr 17, 2026

What this fixes

The previous parser (remark-containers.ts) used a flat linear scan: when it found a :::type opener, it iterated forward collecting sibling nodes until it hit a closing :::. Any :::type encountered during that scan was either treated as plain text content or, in some paths, re-injected into the tree in a way that could produce incorrect output.

This meant nested containers silently didn't work:::card inside :::modal would appear as a sibling of the modal, not a child.

What changed

Rewrote remark-containers.ts using a recursive collectContainer() approach (ported from the v2-proposal reference implementation):

  • When collecting a container's children, any :::type opener found triggers a recursive call to collectContainer() — producing a properly nested AST node.
  • Three cases handled cleanly: (1) complete container in a single plain-text paragraph, (2) complete container with inline elements, (3) multi-block container with recursive nesting.
  • Implicit same-paragraph closing — handles [Save]* [Cancel]\n::: where the closing ::: shares a paragraph with the last content line (remark folds them when there's no blank line).
  • Opener-content extraction:::alert Warning: this action is irreversible adds the inline text as the container's first child, with the type parsed as alert only.

Bugs fixed

Symptom Root cause Fix
:::card inside :::modal rendered as a sibling, not nested Flat scan had no recursion; nested opener was re-injected at the wrong level collectContainer() recurses when it encounters a nested opener
[Save]\n::: (no blank line before closer) — closer was missed, container ate subsequent nodes Implicit closer detection was inconsistent across code paths Unified implicit-closer handling in Case 3
:::alert Some message parsed containerType as "alert Some message" Regex ([^{]+?) captured all non-brace chars including spaces New regex (\S+) stops at whitespace; remainder becomes inline content

Tests added

  • should nest a container inside another container — asserts :::card is a child container of :::modal, not a sibling
  • should extract inline content from the opener line — asserts ::: alert Warning text produces correct containerType + first child

Docs

Added nesting and opener-content examples to the Block Containers section of docs/guide/syntax.md.

🤖 Generated with Claude Code

teezeit and others added 6 commits April 17, 2026 14:14
- Add `{.grid-N card}` boolean modifier — grid items render with card
  chrome (border/shadow/bg) without changing the structural syntax
- Add `{.col-span-N}` on child headings to span multiple grid columns
- Fix: col-span items now reset to span 1 on mobile across all 7 CSS
  themes, preventing overflow when the grid collapses to 1 column
- Grid heading label is declaration-only and never rendered in output
- Add migration script (scripts/migrate-v0.2.sh) for existing files
- Update all examples, docs, and quick reference

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat: grid card modifier, col-span, and responsive fix
… routing (#2)

* feat: render button with href as <a> tag

[Button]{href:./page.md} now renders as a styled <a> anchor instead
of a non-navigating <button>. Checks both node.href and node.props.href
so the attribute form {href:url} works naturally.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: support [[Button](url)] linked-button syntax

[[Text](url)] renders as a button-styled <a> anchor. CommonMark parses
this as text:"[" + link + text:"]" — the transformer now detects that
3-child pattern and hoists the link url to button.href.

Supports variants: [[Label](url)]* for primary, [[Label](url)]{.cls}
for attributes. Also fixes {href:url} attribute form which was stored
in props.href but renderer only checked node.href.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: reset link styling on buttons rendered as <a> tags

a.wmd-button gets text-decoration:none and color:inherit so button-links
look identical to regular buttons — no underline, no browser link color.
Applied once in getStyleCSS() so all 7 themes inherit it automatically.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: multi-file routing in dev server

The dev server now renders any .md file on demand when its URL is
requested, enabling button-links like [[Docs](./docs.md)] to navigate
between wireframe files in the browser.

- startServer() accepts renderFile callback and rootDir
- GET /page.md → calls renderFile(path) and serves result
- GET /page.html → serves cached HTML or renders from .md
- GET / → serves main watched file (unchanged)
- GET unknown → 404
- startServer() now returns the server instance (testability)
- CLI passes renderFile and rootDir to startServer
- Add href? to button node type

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: redirect / to entry file for symmetric navigation

GET / now redirects to /{inputFile} (e.g. /agency-site.md) so there
is no special root — every file is addressed by its own path. Links
back to the entry file work the same as links to any other file.

Fallback: when no inputFile is set (programmatic use), / still serves
the pre-rendered outputPath as before.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: nav link support, active state, and multi-button-link inline

Nav `[[ ]]` items now fully support links and active states:

- `[text](url)` inside `[[ ]]` creates a navigable nav item with real href.
  Previously the link URL was silently dropped (remark parses links as
  `link` nodes with no `.value`; the inline-containers plugin was using
  `c.value || ''` which discarded them).
  Fix: added `serializeChild` to reconstruct `[text](url)` from MDAST
  link nodes before splitting items on `|`.

- `*text*` inside `[[ ]]` is now treated as the active/current-page item
  (strips asterisks, adds `.active` class with theme-matched styling).
  Previously rendered as literal `*text*`.

- `[text](url)*` in nav renders as a primary-styled button link.

- Multiple `[[Btn](url)]` patterns on the same line now render correctly.
  Previously the second button's URL was dropped and brackets showed as
  literal text. Root cause: `transformParagraph`'s 3-child detection
  only handled a single button-link. For two buttons on one line remark
  produces 5 children (`[`, link, `]* [`, link, `]`).
  Fix: new `tryParseButtonLinkSequence` generalises detection to n ≥ 1
  (replaces the old 3-child check); multiple matches return a
  `button-group` container.

Also adds:
- Active nav-item CSS for all 6 themes (`.wmd-nav-item.wmd-active`)
- `docs/guide/syntax.md` — Button Links section
- `QUICK-REFERENCE.md` — nav link and active-state rows
- `examples/gallery/multi-page/` — three-page navigable prototype
  (home/about/contact sharing a nav with live button-link navigation)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add renderSidebarMainLayout() to html-renderer — groups children by
  {.sidebar}/{.main} heading markers into wmd-layout-sidebar/main divs
- Add sidebar + layout CSS to all 7 style themes (nav-matching backgrounds,
  full-width buttons, section label styling)
- Fix grid detection inside containers: extract processNodeList() shared
  helper so both transformToWiremdAST and transformContainer detect
  {.grid-N} headings — previously grids inside :::layout, :::card etc.
  were silently rendered as plain headings
- Add examples/sidebar-layout.md
- Add parser and renderer tests for sidebar layout and nested grids

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
The wiremd CLI writes an .html output file next to the source .md by
default. These are build artifacts and shouldn't be tracked.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Rewrites remark-containers.ts from a flat linear scan to a recursive
collectContainer() approach, enabling true nesting of ::: blocks and
fixing several implicit-closing edge cases.

Co-Authored-By: Claude Sonnet 4.6 <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