feat!: replace heading-class layout syntax with ::: containers#93
Open
teezeit wants to merge 19 commits into
Open
feat!: replace heading-class layout syntax with ::: containers#93teezeit wants to merge 19 commits into
teezeit wants to merge 19 commits into
Conversation
- 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>
## 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 re-injected into the tree incorrectly.
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:
- When collecting a container's children, any `:::type` opener found triggers a recursive call — 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.
- **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 | 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](https://claude.com/claude-code)
- Intercept <a href="*.md"> clicks in the webview and post a navigate message so clicking a link switches the editor to that file instead of opening a browser URL - Add resolveLink() supporting both relative and absolute hrefs; absolute paths walk up the directory tree to find the target file - showTextDocument uses the current editor's viewColumn so the file replaces in-place rather than opening a new tab - Register onDidDispose in deserializeWebviewPanel (mirrors the createOrShowPreview path) so restored panels clean up correctly - Switch vscode:prepublish from tsc to esbuild bundle so wiremd is inlined into the VSIX and the extension activates correctly Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(tabs): interactive tabs via '## Title {.tabs}' heading class
Mirrors the grid pattern: heading with .tabs class declares the tabs
container; direct child headings (depth+1) become tab panels. First
tab active by default; {.active} on a sub-heading overrides.
- Parser: detect .tabs class in processNodeList, emit tabs/tab nodes
- Renderer: tabs case emits header row + panel divs, injects one-time
click-delegated switcher script (idempotent via window.__wmdTabsInit)
- Styles: structural rules in getStyleCSS wrapper (hidden panels),
sketch-themed pill headers matching nav-item aesthetic
- Tests: 7 parser + 6 renderer tests (TDD)
* docs(tabs): add tabs-demo example + rendered artifact
Source: examples/tabs-demo.md
Rendered: docs/tabs-demo.html (under docs/ so it's not caught by the
examples/**/*.html gitignore rule, letting mobile users preview via
htmlpreview.github.io).
* style(tabs): replace button look with underline tab style
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: add tabs syntax to guide and spec
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* style(tabs): update tab header border color for improved visibility
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Initial plan * feat: parse :icon-name: syntax in table cells Agent-Logs-Url: https://github.com/teezeit/wiremd/sessions/3928f112-c787-4fa9-9aae-b79fc8490d4e Co-authored-by: teezeit <17304928+teezeit@users.noreply.github.com> * fix: trim whitespace from icon cell remainder text Agent-Logs-Url: https://github.com/teezeit/wiremd/sessions/3928f112-c787-4fa9-9aae-b79fc8490d4e Co-authored-by: teezeit <17304928+teezeit@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: teezeit <17304928+teezeit@users.noreply.github.com>
* feat(vscode): update icon to 128px logo, fix vsix packaging - Replace placeholder icon with wiremd logo (128×128 px) - Exclude parent node_modules from vsix via .vscodeignore (180 MB → 95 KB) - Rebuild wiremd-preview-0.1.0.vsix Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(vscode): remove vsix binary from repo, gitignore *.vsix Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(vscode): bundle preview-provider.ts with esbuild to inline wiremd Without bundling, preview-provider.js relied on ../node_modules/wiremd at runtime which broke after ../node_modules was excluded from the vsix. Bundling inlines wiremd so the extension is fully self-contained. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: add wireframe skill documentation and workflows - Introduced SKILL.md for wireframe skill, detailing usage for documenting existing screens and wireframing future iterations. - Added syntax reference in syntax.md for wiremd, covering disambiguation rules, buttons, inputs, navigation, and more. - Created vscode.md for setting up and using the wiremd VS Code extension, including installation and daily workflows. - Implemented build scripts for Obsidian plugin and VS Code extension, ensuring easy packaging and installation. - Developed render-all.sh for rendering all wireframes to HTML and generating an index page. - Added serve.sh for starting a live-reload preview server for wireframe files. - Created CLAUDE.md to provide guidance on using the wiremd project, including commands, architecture, and testing. * feat: add claude-skill/ as first-class distribution artifact Move the wireframe skill from .claude/skills/wireframe/ into claude-skill/ at the repo root so it ships alongside the npm package (like vscode-extension/). .claude/skills/wireframe is now a symlink to ../../claude-skill for local use. - Add claude-skill/ with rewritten SKILL.md (generic, not JAM-specific) - Add references/styles.md, references/examples/dashboard.md and settings-form.md - Copy QUICK-REFERENCE.md into references/quick-reference.md (build:skill script) - Update syntax.md: grid+card variant, tabs via button groups, progress bars, component mapping table, column spans - Add claude-skill to package.json files array and build:skill script Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…ut detection (#18) Grid item and tab panel inner loops were calling transformNode per node, which only handles single MDAST nodes and skips the heading-based layout detection (grids, tabs) that lives in processNodeList. Nested layouts were silently rendered as plain headings. Fix: collect raw MDAST nodes for each item/panel, then pass through processNodeList — the same path used by :::containers. Closes #15 Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces `{.row}` as a new layout heading syntax alongside `{.grid-N}`.
Content directly under `## {.row}` is auto-wrapped as implicit flex items;
`###` children are supported for per-item `{.left}`/`{.center}`/`{.right}`
alignment. Row-level `{.right}` and `{.center}` map to justify-content.
Also fixes duplicate CSS class names on row/grid/tabs nodes (syntactic
class names were leaking into props.classes), suppresses empty `<h2>`
terminators from rendering, and wires up row rendering in the React and
Tailwind renderers.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…) (#23) Adds inline badges for status labels, counts, and filter chips. Parser detects |Label| and |Label|{.variant} in both plain and rich-content paragraphs; renderers (HTML, React, Tailwind) and all 7 CSS styles updated. Includes 23 new tests and docs updated across SKILL.md, syntax references, QUICK-REFERENCE.md, SYNTAX-SPEC, and guide. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: resolve ![[file.md]] includes in parser, CLI, and VS Code preview Add resolveIncludes() to the parser which expands ![[file.md]] syntax by inlining the referenced file's content. Paths resolve relative to the base file. Missing files render as a warning blockquote. Wired into the CLI pipeline and the VS Code preview provider. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs: document ![[file.md]] include syntax across all references - docs/guide/syntax.md: add File Includes section - QUICK-REFERENCE.md: add include row to component table, troubleshooting entry - SYNTAX-SPEC-v0.1.md: move file includes from deferred to included (v0.1) - claude-skill/references/syntax.md: replace obsolete :::display with ![[file.md]] - claude-skill/SKILL.md: update gotcha #5 — :::display is obsolete, ![[]] works in CLI and VS Code Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…tyle own it (#25) Sidebar layout columns had redundant padding/background/border/box-shadow baked into .layout-sidebar across all 7 themes. These fight with the :::sidebar container's own styling when nested inside a sidebar-main layout. Remove per-theme overrides from .layout-sidebar and add a single .container-sidebar { width: 100% } rule so the sidebar fills its column. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…frame themes (#27) Cards should fill their container — the 400px cap was forcing cards into a narrow column even when placed in a grid or a full-width layout. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…items (#29) * test(row): add failing cases for dropdown options lost inside implicit row Covers two bugs (both now fixed): - dropdown options empty when select is a direct row child - option list leaking as a stray grid-item sibling Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(row): associate dropdown option list with select inside implicit row items When the row parser looped over direct children it called processNodeList with a single-element slice, so nextNode was always undefined inside the call. Dropdown selects therefore got no options; the list then appeared as a stray grid-item bullet list. Fix: before pushing the grid-item, peek at the next sibling; if the current node is a dropdown paragraph (ends with `v]`) and the sibling is a list, include both in the same processNodeList call so the existing lookahead logic can associate options with the select. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Old syntax used heading classes to declare layouts, which rendered the
heading itself as an invisible no-op — a violation of the core principle
that wiremd syntax should resemble its visual output:
## Title {.grid-3} → heading disappears, grid appears
## {.row} → heading disappears, row appears
## Reports {.tabs} → heading disappears, tabs appear
New syntax uses explicit ::: container blocks, matching every other
wiremd container and making the structure visible in the source:
::: grid-3 → grid container
::: row → flex row
::: tabs → tabbed panel
**Breaking changes:**
- `## Heading {.grid-N}` → `::: grid-N` … `:::`
- `## Heading {.row}` → `::: row` … `:::`
- `## Heading {.row .right}` → `::: row {.right}` … `:::`
- `## Heading {.tabs}` + `### Tab` children → `::: tabs` with `::: tab Label` children
- `## {.sidebar}` / `## {.main}` inside `:::layout` → `::: sidebar` / `::: main`
- Bare `##` closers are gone — every container closes with `:::`
Grid items, alignment (`{.left}`, `{.right}`, `{.center}`), and
col-span (`{.col-span-N}`) still use `###` headings inside containers.
**Implementation:**
- `transformer.ts`: `transformContainer()` handles grid-N/row/tabs/tab;
heading-class detection removed from `processNodeList`
- `html-renderer.ts`: sidebar/main detection updated for container children
- `remark-containers.ts`: fixed bug where nested tabs/grids without a
blank line before their closer were silently dropped
- All 439 non-CLI tests updated and passing
**Docs & examples:**
- QUICK-REFERENCE, SYNTAX-SPEC, README, FAQ, all docs/ updated
- claude-skill references and examples updated
- All ~200 occurrences across examples/gallery/ migrated
- Migration script at scripts/migrate-v0.3.py
Co-Authored-By: Claude Sonnet 4.6 <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.
Why
The old heading-class approach used markdown headings as invisible layout declarations:
This violated wiremd's core principle: syntax should resemble output. A heading that disappears on render is confusing, hard to read, and inconsistent with every other container in the system (
::: card,::: hero,::: alertall use:::already).What changed
Every layout container now uses explicit
:::syntax:## Title {.grid-3}+##::: grid-3…:::## Title {.grid-3 card}::: grid-3 card…:::## Title {.row}::: row…:::## Title {.row .right}::: row {.right}…:::## Reports {.tabs}+### Tab::: tabs+::: tab Label…:::## Nav {.sidebar}inside layout::: sidebar…:::## Content {.main}inside layout::: main…:::##(closer):::Grid items still use
###headings. Alignment ({.left},{.right},{.center}) and col-span ({.col-span-N}) stay on###children — only the container declaration changes.Implementation
transformer.ts—transformContainer()handlesgrid-N,row,tabs,tabcontainer types; heading-class detection removed fromprocessNodeListhtml-renderer.ts— sidebar/main detection updated to look forcontainerchildren withcontainerType === 'sidebar'/'main'remark-containers.ts— fixed a bug where nestedtabs/gridcontainers without a blank line before their closer were silently dropped (movedparseContainerOpenercheck before implicit-closer check)Migration
A migration script lives at
scripts/migrate-v0.3.pyfor automated conversion of existing.mdfiles.Manual pattern:
## Title {.grid-N card}→::: grid-N card##→:::## Title {.row}→::: row## Title {.tabs}+### Tabpanels →::: tabs+::: tab Labelblocks:::layout {.sidebar-main}, replace## {.sidebar}/## {.main}with::: sidebar/::: mainDocs & examples updated
QUICK-REFERENCE.md,SYNTAX-SPEC-v0.1.md,README.md,FAQ.mddocs/guide pagesclaude-skill/references and examplesexamples/andexamples/gallery/(25+ files)🤖 Generated with Claude Code