Skip to content

Commit 93e08df

Browse files
committed
Wireframe: theme support, inline badges, feature-focused E2E fixtures, docs
- Add WireframePalette derived from theme's BackgroundColor, TextColor, SubtleTextColor, and AccentColor so all 25 built-in themes work - Fix inline badge parsing: '(( Badge )) trailing text' now emits badge + text wrapped in an implicit row for side-by-side rendering - Remove diagram.Title from wireframe parser (use ::: HEADER ::: instead) - Restructure E2E fixtures into 3 feature-focused wireframes: wireframe-form (default), wireframe-dashboard (dracula), wireframe-showcase (catppuccin-latte) - Update PRD with Wireframe DSL scope (section 5.3) - Update README: gallery thumbnails, Wireframe DSL section with component syntax table, 'If You Want This' entry, link to markdown-ui-dsl - Update Build-Gallery.ps1 wireframe label handling
1 parent 5c29596 commit 93e08df

16 files changed

Lines changed: 656 additions & 318 deletions

README.md

Lines changed: 78 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Representative snapshot fixtures from the E2E suite. Click any thumbnail to open
1212
<!-- markdownlint-disable MD033 -->
1313
<h3>Diagram Types</h3>
1414

15-
DiagramForge currently supports more than a dozen diagram types across Mermaid and the conceptual DSL. This gallery is representative, not complete, and includes a few icon-enabled examples. See [Supported syntax](#supported-syntax) and [Conceptual DSL](#conceptual-dsl) below for the full set and syntax details.
15+
DiagramForge currently supports more than a dozen diagram types across Mermaid, the Conceptual DSL, and the Wireframe DSL. This gallery is representative, not complete, and includes a few icon-enabled examples. See [Supported syntax](#supported-syntax), [Conceptual DSL](#conceptual-dsl), and [Wireframe DSL](#wireframe-dsl) below for the full set and syntax details.
1616

1717
<table cellpadding="12" width="100%">
1818
<tr>
@@ -138,6 +138,20 @@ DiagramForge currently supports more than a dozen diagram types across Mermaid a
138138
<br />
139139
<sub>Snake Timeline (Dracula)</sub>
140140
</td>
141+
<td align="center" valign="top" width="33%">
142+
<a href="https://github.com/jongalloway/DiagramForge/blob/main/tests/DiagramForge.E2ETests/Fixtures/wireframe-form.expected.svg">
143+
<img src="https://raw.githubusercontent.com/jongalloway/DiagramForge/main/tests/DiagramForge.E2ETests/Fixtures/wireframe-form.expected.svg" alt="Wireframe form dialog" height="96" />
144+
</a>
145+
<br />
146+
<sub>Wireframe Form</sub>
147+
</td>
148+
<td align="center" valign="top" width="33%">
149+
<a href="https://github.com/jongalloway/DiagramForge/blob/main/tests/DiagramForge.E2ETests/Fixtures/wireframe-dashboard.expected.svg">
150+
<img src="https://raw.githubusercontent.com/jongalloway/DiagramForge/main/tests/DiagramForge.E2ETests/Fixtures/wireframe-dashboard.expected.svg" alt="Wireframe dashboard (Dracula)" height="96" />
151+
</a>
152+
<br />
153+
<sub>Wireframe Dashboard (Dracula)</sub>
154+
</td>
141155
</tr>
142156
</table>
143157

@@ -278,7 +292,7 @@ That's the whole API.
278292

279293
Most diagram-as-code tools assume a browser. Mermaid.js needs a JavaScript engine to run at all — and even once you've stood up headless Chrome and extracted the SVG, you find that it renders text via `<foreignObject>` wrapping HTML `<div>`s instead of native `<text>` elements. That's fine in a web page. It's a blank box in Inkscape, a parse error in Illustrator, and a mess when you try to drop it into a PowerPoint slide. See mermaid-js/mermaid [#2688](https://github.com/mermaid-js/mermaid/issues/2688), [#1845](https://github.com/mermaid-js/mermaid/issues/1845), [#1923](https://github.com/mermaid-js/mermaid/issues/1923), [#2169](https://github.com/mermaid-js/mermaid/issues/2169).
280294

281-
DiagramForge aims lower and hits harder: a **subset** of Mermaid, rendered to **actual** SVG. Flowcharts, block diagrams, sequence diagrams, state diagrams, mindmaps, timelines, Venn diagrams, architecture diagrams, and XY charts — the output opens anywhere.
295+
DiagramForge aims lower and hits harder: a **subset** of Mermaid, rendered to **actual** SVG. Flowcharts, block diagrams, sequence diagrams, state diagrams, mindmaps, timelines, Venn diagrams, architecture diagrams, XY charts, and low-fidelity wireframes — the output opens anywhere.
282296

283297
- **Real SVG.** Native `<text>` elements. No `<foreignObject>`, no embedded HTML, no CSS-in-SVG. Opens in Inkscape, imports into PowerPoint and Keynote, renders with librsvg.
284298
- **Pure .NET.** `net10.0`, zero native dependencies, zero runtime package dependencies. No headless browser, no Node, no shelling out.
@@ -316,7 +330,7 @@ string svg = renderer.Render(diagramText);
316330
File.WriteAllText("out.svg", svg);
317331
```
318332

319-
The renderer auto-detects the syntax from the input. No need to tell it whether it's Mermaid or Conceptual DSL.
333+
The renderer auto-detects the syntax from the input. No need to tell it whether it's Mermaid, Conceptual DSL, or Wireframe DSL.
320334

321335
### With a custom theme
322336

@@ -343,7 +357,7 @@ See [doc/theming.md](doc/theming.md) for the full `Theme` property surface and [
343357

344358
### With diagram frontmatter
345359

346-
Diagram files can embed a small frontmatter block ahead of Mermaid or Conceptual DSL content:
360+
Diagram files can embed a small frontmatter block ahead of Mermaid, Conceptual DSL, or Wireframe DSL content:
347361

348362
```text
349363
---
@@ -784,6 +798,7 @@ Rule of thumb: if the diagram is already easy to describe as Mermaid, use Mermai
784798
| Central concept with surrounding pillars / capabilities (3–8 items) | Conceptual DSL | `diagram: radial\ncenter: Platform\nitems:\n - Security\n - Reliability\n - Observability` |
785799
| Concentric strategy / segmentation target | Conceptual DSL | `diagram: target\ncenter: Launch\nrings:\n - Inner ring: Pricing and messaging\n - Outer ring: Audience reach` |
786800
| Visual step-by-step journey / snake timeline (3+ steps) | Conceptual DSL | `diagram: snake\ntitle: Journey\nsteps:\n - Start: Begin here\n - Middle: Keep going\n - End: Arrive` |
801+
| Low-fidelity UI mockup / wireframe sketch | Wireframe DSL | `wireframe: Settings\n::: HEADER :::\n # Settings\n--- END ---\n[ text: Name ]\n[ Save ](#save)` |
787802

788803
Planned conceptual additions are aimed at presentation-native graphics that Mermaid does not cover idiomatically, such as tree hierarchies / org charts.
789804

@@ -919,6 +934,64 @@ steps:
919934

920935
Requires at least 3 steps. Each step follows the format `Label: Description` — the description is optional. Icons use the standard `icon:pack:name` prefix.
921936

937+
### Wireframe DSL
938+
939+
Low-fidelity wireframe diagrams based on the [markdown-ui-dsl](https://github.com/MegaByteMark/markdown-ui-dsl) syntax. The first non-frontmatter line must start with `wireframe`.
940+
941+
DiagramForge implements a focused subset of the DSL — enough to sketch forms, dashboards, and component showcases as static, theme-able SVG.
942+
943+
#### Supported components
944+
945+
| Component | Syntax | Example |
946+
| --- | --- | --- |
947+
| Header | `::: HEADER :::`…`--- END ---` | `::: HEADER :::\n # Page Title\n--- END ---` |
948+
| Card | `::: CARD: Label :::`…`--- END ---` | `::: CARD: Users :::\n ## 1,240\n Total\n--- END ---` |
949+
| Row layout | `=== ROW ===`…`--- END ---` | `=== ROW ===\n [ OK ](#ok)\n [ Cancel ]\n--- END ---` |
950+
| Button (primary) | `[ Label ](#link)` | `[ Submit ](#save)` |
951+
| Button (secondary) | `[ Label ]` | `[ Cancel ]` |
952+
| Text input | `[ text: placeholder ]` | `[ text: Email address ]` |
953+
| Checkbox | `[x]` / `[ ]` + label | `[x] Accept terms` |
954+
| Radio button | `(x)` / `( )` + label | `(x) Yearly` |
955+
| Toggle | `[on]` / `[off]` + label | `[on] Dark Mode` |
956+
| Dropdown | `[v] Label {options}` | `[v] Country {USA, UK, Canada}` |
957+
| Tab bar | `\|[ Active ]\| Tab2 \| Tab3 \|` | `\|[ Overview ]\| Settings \|` |
958+
| Badge (inline) | `(( Label )) text` | `(( New )) 3 notifications` |
959+
| Heading | `# Heading` or `## Heading` | `# Recent Activity` |
960+
| Body text | plain text line | `Status: Cannot Reproduce` |
961+
| Divider | `***` | `***` |
962+
| Image placeholder | `[ IMG: description ]` | `[ IMG: Profile photo ]` |
963+
964+
#### Example
965+
966+
```text
967+
wireframe: Settings Dialog
968+
::: HEADER :::
969+
# Preferences
970+
--- END ---
971+
[ text: Display name ]
972+
[v] Theme {Light, Dark, System}
973+
[x] Send notifications
974+
( ) Daily digest
975+
(x) Real-time
976+
***
977+
=== ROW ===
978+
[ Save ](#save)
979+
[ Cancel ]
980+
--- END ---
981+
```
982+
983+
All wireframe colors are derived from the active theme's `BackgroundColor`, `TextColor`, `SubtleTextColor`, and `AccentColor`, so every built-in theme (including dark themes) works automatically. Use [frontmatter](#with-diagram-frontmatter) to pick a theme:
984+
985+
```text
986+
---
987+
theme: dracula
988+
---
989+
wireframe: Dashboard
990+
::: HEADER :::
991+
# Mission Control
992+
--- END ---
993+
```
994+
922995
## Architecture
923996

924997
```mermaid
@@ -945,7 +1018,7 @@ Parsers produce a syntax-independent `Diagram` (nodes, edges, groups, labels, la
9451018

9461019
## Roadmap
9471020

948-
See [`doc/prd.md`](doc/prd.md) for the full plan. Short version: more Mermaid diagram types, more conceptual layouts, theme packs, eventually D2 and DOT parsers.
1021+
See [`doc/prd.md`](doc/prd.md) for the full plan. Short version: more Mermaid diagram types, more conceptual layouts, wireframe enhancements, theme packs, eventually D2 and DOT parsers.
9491022

9501023
For analysis of which SmartArt-style conceptual diagrams are most worth adding next, see [`doc/smartart-analysis.md`](doc/smartart-analysis.md).
9511024

doc/prd.md

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# Product Requirements Document (PRD)
2+
23
## Project: Markdown‑Driven Diagram Rendering Engine (Text → SVG)
34

45
---
@@ -38,7 +39,7 @@ Clients (e.g., MarpToPptx, Obsidian plugins, CLI tools) are responsible for extr
3839
- Provide a **pluggable parser architecture** for future syntaxes (e.g., D2, DOT).
3940
- Remain **independent of any specific client** (Marp, PPTX, web apps, etc.).
4041
- Support for themes and palletes, both built-in and user supplied.
41-
- Leverage existing ecosystem. Refer to https://github.com/mermaid-js/mermaid for specification and transpile open source code (with attribution) if possible.
42+
- Leverage existing ecosystem. Refer to <https://github.com/mermaid-js/mermaid> for specification and transpile open source code (with attribution) if possible.
4243

4344
### 2.2 Non‑Goals
4445

@@ -148,7 +149,26 @@ Examples that remain good candidates for the Conceptual DSL:
148149
- Radial / hub-and-spoke
149150
- Pillars / stacked segments
150151

151-
### 5.3 Future Diagram Types (not in v1)
152+
### 5.3 Wireframe DSL
153+
154+
Low-fidelity wireframe diagrams based on the [markdown-ui-dsl](https://github.com/MegaByteMark/markdown-ui-dsl) syntax. The wireframe parser implements a focused subset of the DSL to render UI mockups as self-contained SVG.
155+
156+
Supported component subset:
157+
158+
- Container primitives: column, row, card, header, footer
159+
- Form elements: text input, checkbox, radio button, toggle, dropdown
160+
- Navigation: tab bar (active/inactive states)
161+
- Content: heading, body text, badge (inline with trailing text), image placeholder, divider
162+
- Actions: button (primary via link syntax, secondary via plain brackets)
163+
- Layout: `=== ROW ===` for horizontal arrangement, `::: CARD :::` for grouped content, `***` dividers, indent-based nesting
164+
165+
Selection rule:
166+
167+
- Use the wireframe DSL when the goal is a quick, theme-able, low-fidelity UI sketch inside a slide or document.
168+
- The wireframe DSL is not a UI framework; it renders static SVG of standard UI widget shapes.
169+
- All wireframe colors are derived from the four semantic theme properties (`BackgroundColor`, `TextColor`, `SubtleTextColor`, `AccentColor`) via `WireframePalette`, so every built-in theme works out of the box including dark themes.
170+
171+
### 5.4 Future Diagram Types (not in v1)
152172

153173
- D2 subset
154174
- Architecture diagrams

scripts/Build-Gallery.ps1

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ foreach ($file in $svgFiles) {
112112
$label = Get-FriendlyLabel $themeName
113113
}
114114
else {
115-
$labelSeed = $fixtureName -replace '^mermaid-', '' -replace '^conceptual-', 'Conceptual '
115+
$labelSeed = $fixtureName -replace '^mermaid-', '' -replace '^conceptual-', 'Conceptual ' -replace '^wireframe-', 'Wireframe '
116116
$label = (($labelSeed -split '-') | ForEach-Object {
117117
if ($_.Length -le 2) {
118118
$_.ToUpperInvariant()
@@ -124,13 +124,13 @@ foreach ($file in $svgFiles) {
124124
}
125125

126126
$entries += [PSCustomObject]@{
127-
File = $file
128-
Width = $width
129-
Height = $height
130-
Label = $label
131-
Theme = $themeName
127+
File = $file
128+
Width = $width
129+
Height = $height
130+
Label = $label
131+
Theme = $themeName
132132
IsThemeShowcase = $isThemeShowcase
133-
Content = (Get-Content -LiteralPath $file.FullName -Raw)
133+
Content = (Get-Content -LiteralPath $file.FullName -Raw)
134134
}
135135
}
136136

src/DiagramForge/Parsers/Wireframe/WireframeDslParser.cs

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,8 @@ public Diagram Parse(string diagramText)
6363
if (rawLines.Length == 0)
6464
throw new DiagramParseException("Diagram text is empty.");
6565

66-
// Locate the header line ("wireframe [: title]") and parse the optional title.
66+
// Locate the header line ("wireframe [: title]") to find where the body starts.
6767
int startLine = 0;
68-
string? title = null;
6968

7069
for (int i = 0; i < rawLines.Length; i++)
7170
{
@@ -77,14 +76,6 @@ public Diagram Parse(string diagramText)
7776
throw new DiagramParseException(
7877
"Wireframe DSL must begin with 'wireframe' or 'wireframe: <title>'.");
7978

80-
int colonPos = trimmed.IndexOf(':', StringComparison.Ordinal);
81-
if (colonPos >= 0)
82-
{
83-
var titlePart = trimmed[(colonPos + 1)..].Trim();
84-
if (!string.IsNullOrEmpty(titlePart))
85-
title = titlePart;
86-
}
87-
8879
startLine = i + 1;
8980
break;
9081
}
@@ -93,8 +84,8 @@ public Diagram Parse(string diagramText)
9384
.WithSourceSyntax(SyntaxId)
9485
.WithDiagramType("wireframe");
9586

96-
if (title is not null)
97-
builder.WithTitle(title);
87+
// The wireframe declaration line ("wireframe: Title") identifies the diagram
88+
// but does not render a visible title. Use ::: HEADER ::: for visual headers.
9889

9990
ParseBody(rawLines, startLine, builder);
10091

@@ -147,6 +138,32 @@ private static void ParseBody(string[] lines, int startLine, IDiagramSemanticMod
147138
continue;
148139
}
149140

141+
// Inline badge+text: "(( Label )) trailing text" → implicit row with badge + text.
142+
if (TrySplitInlineBadge(trimmed, out var badgeLabel, out var trailingText))
143+
{
144+
// Wrap in an implicit row so badge and text render side-by-side.
145+
var rowId = $"wf_{nodeCounter++}";
146+
var rowNode = new Node(rowId, string.Empty);
147+
rowNode.Metadata["wireframe:kind"] = "row";
148+
builder.AddNode(rowNode);
149+
if (containerStack.TryPeek(out var rp))
150+
AddContainmentEdge(builder, rp, rowId);
151+
152+
var badgeNode = new Node($"wf_{nodeCounter++}", badgeLabel);
153+
badgeNode.Metadata["wireframe:kind"] = "badge";
154+
builder.AddNode(badgeNode);
155+
AddContainmentEdge(builder, rowId, badgeNode.Id);
156+
157+
if (!string.IsNullOrWhiteSpace(trailingText))
158+
{
159+
var textNode = new Node($"wf_{nodeCounter++}", trailingText);
160+
textNode.Metadata["wireframe:kind"] = "text";
161+
builder.AddNode(textNode);
162+
AddContainmentEdge(builder, rowId, textNode.Id);
163+
}
164+
continue;
165+
}
166+
150167
// Leaf component
151168
var leaf = ParseLeafComponent(trimmed, $"wf_{nodeCounter++}");
152169
if (leaf is not null)
@@ -471,6 +488,32 @@ private static bool IsContainerEnd(string line)
471488
return textNode;
472489
}
473490

491+
/// <summary>
492+
/// Detects "(( Label )) trailing text" on a single line and splits it into
493+
/// a badge label and optional trailing text so the body parser can emit two nodes.
494+
/// </summary>
495+
private static bool TrySplitInlineBadge(string line, out string badgeLabel, out string trailingText)
496+
{
497+
badgeLabel = string.Empty;
498+
trailingText = string.Empty;
499+
500+
if (!line.StartsWith("((", StringComparison.Ordinal))
501+
return false;
502+
503+
int closeIdx = line.IndexOf("))", 2, StringComparison.Ordinal);
504+
if (closeIdx < 0)
505+
return false;
506+
507+
// Only split when there is content after the closing "))"
508+
// (standalone badges are handled by ParseParenComponent).
509+
if (closeIdx + 2 >= line.Length)
510+
return false;
511+
512+
badgeLabel = line[2..closeIdx].Trim();
513+
trailingText = line[(closeIdx + 2)..].Trim();
514+
return badgeLabel.Length > 0;
515+
}
516+
474517
private static Node? ParseTabBar(string line, string nodeId)
475518
{
476519
// |[ Active ]| Tab2 | Tab3 |

0 commit comments

Comments
 (0)