@zvndev/darkmown 0.8.2 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -7,7 +7,7 @@ Darkmown is a Markdown-native web framework. You build sites by writing `.md` an
7
7
  - `.md` files are plain CommonMark. Directives stay inert text.
8
8
  - `.wd` files are the same Markdown **plus directives** (state, loops, conditionals, includes, forms).
9
9
  - To make a `.md` page interactive, **rename it to `.wd`**. ⚠️ Renaming means the old `.md` is **gone** — never leave both `index.md` and `index.wd`, that is a fatal `Duplicate route` build error. Upgrade = delete the `.md`, create the `.wd`.
10
- - Static pages ship **zero** JavaScript; a page loads the ~3.1 KB runtime only if it declares reactive behavior.
10
+ - Static pages ship **zero** JavaScript; a page loads the ~3.2 KB runtime only if it declares reactive behavior.
11
11
 
12
12
  ## Project layout & routing
13
13
 
package/CHANGELOG.md CHANGED
@@ -2,6 +2,27 @@
2
2
 
3
3
  All notable changes to Darkmown are documented here. Versions follow [semver](https://semver.org); pre-1.0 minor versions may contain breaking changes.
4
4
 
5
+ ## 0.10.0 — 2026-06-15
6
+
7
+ Launch-readiness: every public claim is now backed by runnable evidence instead of stale copy.
8
+
9
+ - **Provable docs.** README, `docs/cli.md`, `docs/spec-alignment.md`, and the gold-standard audit are re-synced to the shipped code — loop semantics, the build-time vs reactive split, and per-row `:button` actions (`cart += product` carries the row into another list; `cart remove line` drops it) are documented exactly as they compile. A docs-snippet test compiles the README/docs examples so the copy can't drift from the code again.
10
+ - **Consumer smoke script.** `npm run smoke` packs the tarball, installs it into a throwaway project, scaffolds, and builds it the way a real consumer would — the same supply-chain check the release workflow runs, now available locally.
11
+ - **CLI + scaffold polish.** `darkmown version` prints the installed package version; first-run scaffold output and CLI guidance now match the docs.
12
+ - **Editor + packaging scripts.** `npm run test:extension` and `npm run pack:extension` build and tokenize-test the VS Code grammar from the repo root; the extension ships its own `LICENSE` and a corrected README.
13
+ - **`homepage` now points at https://darkmown.com.**
14
+
15
+ ## 0.9.0 — 2026-06-15
16
+
17
+ The "proof + full test pyramid" release: prove the moat on the homepage, test everything from unit to browser e2e, and pass a gold-standard open-source audit.
18
+
19
+ - **Landing: the vs-Markdoc proof.** The homepage is now a side-by-side comparison — the real `.wd` source for a live search-filter + add-to-cart store rendered working on the page, next to the equivalent Markdoc (which has no native loop and needs a React component + hydration for interactivity). The homepage is now itself a reactive `.wd` app; `/markdown/` and `/docs/` stay static (zero JS) to prove the static path.
20
+ - **Full test pyramid.** Unit + integration backfill (grammar whitelists, skin compiler, interpolation, router, every compile-error path), a zero-dependency CLI/pipeline e2e (`darkmown init` → build → serve → fetch → assert), real-browser e2e via Playwright (`npm run test:e2e`), and a seeded property/fuzz suite (5,000 cases/run) for the parser and expression validators. The zero-dep `npm test` suite grew from 98 to ~200 checks; coverage is ~96% lines, gated at 80% in CI.
21
+ - **The fuzzer earned its keep:** it found two real bugs, now fixed — two `:button` action errors that omitted the file path, and bare object interpolation (`{ meta }`) emitting `[object Object]` instead of an actionable error. Arrays now join with `, ` consistently.
22
+ - **Type safety.** Source stays plain JavaScript but is fully type-checked via JSDoc + `checkJs` (`npm run typecheck`, gated in CI), and ships `.d.ts` declarations. `typescript` is a dev-only dependency; the runtime stays zero-dependency.
23
+ - **Lean shipped runtime.** JSDoc is stripped from the runtime on emit, so the browser still downloads ~3.2 KB gzipped even though the source carries full type annotations. The size budget now measures the shipped artifact.
24
+ - **Gold-standard hardening (46.5/50 audit).** Release pack-smoke test before publish, `prepublishOnly` guard, `dependabot` (npm + GitHub Actions), CI `concurrency` cancellation, `.github/` PR + issue templates, `CODEOWNERS`, a short `CODE_OF_CONDUCT.md`, an exposed/documented `window.wd.debug`, and repo-root tidy-up.
25
+
5
26
  ## 0.8.2 — 2026-06-14
6
27
 
7
28
  Honesty, hardening, and polish pass — every claim in the docs is now backed by shipped code.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **[darkmown.com](https://darkmown.com)** · Markdown that runs.
4
4
 
5
- Darkmown is a Markdown-native web framework. Two formats, one rule: `.md` stays plain CommonMark forever, and renaming a file to `.wd` ("whateverdown") is what unlocks directives — includes, loops, state, conditionals, and sections. Static pages ship **zero** framework JavaScript; reactive pages share one runtime around ~3.1 KB gzipped (CI-enforced under 5 KB).
5
+ Darkmown is a Markdown-native web framework. Two formats, one rule: `.md` stays plain CommonMark forever, and renaming a file to `.wd` ("whateverdown") is what unlocks directives — includes, loops, state, conditionals, and sections. Static pages ship **zero** framework JavaScript; reactive pages share one runtime around ~3.2 KB gzipped (CI-enforced under 5 KB).
6
6
 
7
7
  ## Quick start
8
8
 
@@ -27,7 +27,8 @@ The package is `@zvndev/darkmown`; the command it installs is plain `darkmown`.
27
27
  ```sh
28
28
  npm install
29
29
  npm test
30
- npm run dev # live demo site — the same site that runs darkmown.com
30
+ npm run smoke # pack, install, scaffold, and build a temporary consumer app
31
+ npm run dev # live demo site — the same site that runs darkmown.com
31
32
  ```
32
33
 
33
34
  ## Commands
@@ -36,6 +37,7 @@ npm run dev # live demo site — the same site that runs darkmown.com
36
37
  - `darkmown dev` starts the live compiler with browser reload and an in-browser error overlay when a build fails.
37
38
  - `darkmown build` writes static output to `dist`.
38
39
  - `darkmown serve` previews the built `dist` locally.
40
+ - `darkmown version` prints the installed package version.
39
41
  - `darkmown help` prints CLI usage.
40
42
 
41
43
  ## Authoring model
@@ -45,7 +47,7 @@ npm run dev # live demo site — the same site that runs darkmown.com
45
47
  - Files or folders starting with `.`, `-`, or `_` are hidden from routing.
46
48
  - `site/_` is the include shelf for `@include /name.wd`.
47
49
  - Matching `page.skin` and `page.js` colocate styling and behavior by basename.
48
- - Static pages ship zero Darkmown runtime. Reactive pages share `/__wd/runtime.js` (currently ~3.1 KB gzipped, CI-enforced under 5 KB).
50
+ - Static pages ship zero Darkmown runtime. Reactive pages share `/__wd/runtime.js` (currently ~3.2 KB gzipped, CI-enforced under 5 KB).
49
51
  - Shelf `.json` files are published at `/__wd/data/` so `:fetch` works on any static host.
50
52
 
51
53
  ## Interpolation
@@ -76,14 +78,16 @@ tags: [sales, revenue, "q1, q2"]
76
78
 
77
79
  `@loop <things> into <thing>` is the only loop. The source decides the behavior:
78
80
 
81
+ A JSON-file loop is unrolled at build time; includes inside inherit the loop value. A `:state` list loop is reactive and patched by key.
82
+
79
83
  ```wd
80
- @loop /features.json into card <- JSON file: unrolled at build time
81
- @include /feature-card.wd <- includes inherit the loop value
84
+ @loop /features.json into card
85
+ @include /feature-card.wd
82
86
  @endloop
83
87
 
84
88
  :state todos = [{"id": 1, "title": "Route pages"}]
85
89
 
86
- @loop todos into todo <- :state list: reactive, patched by key
90
+ @loop todos into todo
87
91
  - { todo.title }
88
92
  @endloop
89
93
  ```
@@ -119,7 +123,7 @@ Operators: `==` `!=` `<` `<=` `>` `>=`, plus `contains` for case-insensitive sub
119
123
 
120
124
  ### Editable lists — per-row actions
121
125
 
122
- A `:button` inside a reactive `@loop` can act on its own row. Two row actions exist:
126
+ A `:button` inside a reactive `@loop` can act on its own row. `cart += product` carries the current row into another list; `cart remove line` drops the current row from the looped list:
123
127
 
124
128
  ```wd
125
129
  :state products = [{"id": 1, "name": "Aurora", "price": 49}]
@@ -128,14 +132,14 @@ A `:button` inside a reactive `@loop` can act on its own row. Two row actions ex
128
132
  @loop products into product
129
133
  ::: card
130
134
  **{ product.name }** — ${ product.price }
131
- :button "Add to cart" -> cart += product <- carry this row into another list
135
+ :button "Add to cart" -> cart += product
132
136
  :::
133
137
  @endloop
134
138
 
135
139
  @loop cart into line
136
140
  ::: card
137
141
  { line.name }
138
- :button "Remove" -> cart remove line <- drop this row from the looped list
142
+ :button "Remove" -> cart remove line
139
143
  :::
140
144
  @endloop
141
145
  ```
@@ -209,9 +213,11 @@ Loading…
209
213
 
210
214
  Reactive pages expose `window.wd` — `wd.get(key)`, `wd.set(key, value)`, `wd.state`, `wd.render()` — so colocated `.js` can do anything the directives can't. Section-scoped keys are addressed as `sectionId:name`.
211
215
 
216
+ Set `window.wd.debug = true` (it defaults to `false`) to log any `:computed` or `@loop … where` expression that fails to evaluate to the console — useful while authoring reactive pages.
217
+
212
218
  ## Editor support
213
219
 
214
- A VS Code extension in [`editors/vscode`](editors/vscode) gives `.wd` and `.skin` files syntax highlighting, snippets, and folding — so a `.wd` file reads as Markdown-plus-directives, never as broken Markdown. Install it from source by building a `.vsix`: `cd editors/vscode && npx @vscode/vsce package`, then `code --install-extension darkmown-*.vsix`. A Marketplace listing is coming soon.
220
+ A VS Code extension in [`editors/vscode`](editors/vscode) gives `.wd` and `.skin` files syntax highlighting, snippets, and folding — so a `.wd` file reads as Markdown-plus-directives, never as broken Markdown. For launch, it is source-installable: build a `.vsix` with `npm run pack:extension`, then install `editors/vscode/darkmown-*.vsix` with VS Code. Marketplace/Open VSX publishing is tracked as post-launch distribution work, so the public package does not imply store availability yet.
215
221
 
216
222
  ## Security
217
223
 
package/docs/cli.md CHANGED
@@ -12,6 +12,14 @@ darkmown help
12
12
 
13
13
  ## Commands
14
14
 
15
+ ### `darkmown help` / `darkmown --help`
16
+
17
+ Prints the command summary, authoring directories, and common directive syntax.
18
+
19
+ ### `darkmown version` / `darkmown --version`
20
+
21
+ Prints the installed package version.
22
+
15
23
  ### `darkmown init [dir]`
16
24
 
17
25
  Creates a minimal Darkmown site with:
@@ -23,7 +31,7 @@ Creates a minimal Darkmown site with:
23
31
  - `site/_/nav.wd`
24
32
  - `README.md`
25
33
 
26
- Existing files are not overwritten.
34
+ Existing files are not overwritten. The scaffolded `package.json` is private by default, includes `dev`, `build`, and `preview` scripts, and names the app after the target directory.
27
35
 
28
36
  After scaffolding:
29
37
 
@@ -50,4 +58,18 @@ Compiles `site/pages` into `dist`.
50
58
 
51
59
  Static pages do not receive `/__wd/runtime.js`; reactive pages do.
52
60
 
53
- When a `.md` file contains `.wd` syntax (directives, includes, loops), the build prints a hint suggesting a rename to `.wd` — the syntax stays plain text in `.md` by design.
61
+ When a `.md` file contains `.wd` syntax (directives, includes, loops), the build prints a hint suggesting a rename to `.wd` — the syntax stays plain text in `.md` by design.
62
+
63
+ ### `darkmown serve`
64
+
65
+ Serves the already-built `dist` directory for local preview. Run `darkmown build` first.
66
+
67
+ ## Smoke checks
68
+
69
+ From this repository, run:
70
+
71
+ ```sh
72
+ npm run smoke
73
+ ```
74
+
75
+ The smoke script packs the local tarball, installs the packed CLI in a temporary driver project, scaffolds a consumer app through that installed bin, installs the same tarball into the app, builds it, verifies the reactive home route, and verifies the plain `.md` about route stays zero-JS.
@@ -6,7 +6,7 @@ This audit compares the current Darkmown implementation with the original MARROW
6
6
 
7
7
  - **Markdown-first authoring:** `.md` is strict CommonMark rendered by a real parser (markdown-it); `.wd` is the same Markdown plus first-party directives. Renaming the file is the upgrade path — extensions genuinely gate functionality.
8
8
  - **Content tree over components:** routing, includes, and repeated fragments are file/document based instead of React-style components.
9
- - **Tiered output:** static routes emit HTML/CSS and no Darkmown runtime; reactive routes opt into `/__wd/runtime.js` (~3.1 KB gzipped, CI-enforced below the 5 KB target).
9
+ - **Tiered output:** static routes emit HTML/CSS and no Darkmown runtime; reactive routes opt into `/__wd/runtime.js` (~3.2 KB gzipped, CI-enforced below the 5 KB target).
10
10
  - **No virtual DOM:** state changes patch direct DOM bindings, conditional regions, and keyed loop regions.
11
11
  - **Keyed list patching:** reactive loops reconcile by item key (`id`/`key`/value) instead of re-rendering the region.
12
12
  - **Sections with scoped state:** `::: section #id .class` containers scope `:state`; bindings and actions resolve through the nearest scope chain.
@@ -44,7 +44,7 @@ This audit compares the current Darkmown implementation with the original MARROW
44
44
  - **Adapter-style Tier 2 (decided):** Darkmown does not own a server. `:form action="/url" into reply` posts urlencoded via fetch and lands the JSON reply in state, degrading to a plain native POST without JS. Sessions ride on `:fetch` plus ordinary cookies against any backend. A first-party `site/api/` runtime stays open as a future option if a real project demands it.
45
45
  - **Dev error overlay:** failed rebuilds render the compiler error in the browser; the overlay clears on the next good build.
46
46
  - **`darkmown serve`:** local preview of the built `dist`, no dev client injected.
47
- - **Packaging:** v0.3.0, license UNLICENSED pending a licensing decision, scaffold pins the live version, npm pack contents covered by tests.
47
+ - **Packaging:** v0.3.0 introduced package metadata and pack tests; the current public package is MIT licensed and scaffolded projects pin the live Darkmown version.
48
48
 
49
49
  ## Stage 6 additions (2026-06-13)
50
50
 
@@ -69,10 +69,10 @@ The compiler recognizes three directives — `:note`, `:try`, and `:sprint` —
69
69
  - First-party server runtime (`site/api/`), HTML-fragment `swap` semantics, and cart server sync — parked behind the adapter decision until a real project needs them.
70
70
  - `@loop` over fetch sub-paths (looping a path *inside* fetched state rather than top-level state).
71
71
  - **View transitions:** re-enabling `transitions: true` with proper activation fallbacks (disabled today — see Stage 4), plus `@starting-style` emission and browser fallbacks for older engines.
72
- - Editor tooling, richer diagnostics, and an actual npm publish (needs a license decision).
72
+ - Richer diagnostics and package-consumer QA in regular CI. Editor tooling exists as a source-installable VS Code extension; public Marketplace/Open VSX distribution remains a launch/distribution follow-up.
73
73
 
74
74
  ## Current claim
75
75
 
76
76
  Darkmown is faithful to the core thesis: Markdown remains the authoring center, static pages ship zero framework JavaScript, and reactive behavior compiles into tiny direct-DOM islands only where declared. The extension is the feature gate, the loop is one concept with progressive disclosure, and state scopes to document structure.
77
77
 
78
- It is not yet the full production framework described in the original document.
78
+ The public npm package is published as `@zvndev/darkmown`, the project is MIT licensed, and the remaining gaps are explicitly scoped as post-launch product/distribution work rather than missing v0.9 claims.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zvndev/darkmown",
3
- "version": "0.8.2",
3
+ "version": "0.10.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -10,15 +10,26 @@
10
10
  "bin": {
11
11
  "darkmown": "./src/cli.js"
12
12
  },
13
+ "types": "./types/index.d.ts",
13
14
  "scripts": {
14
15
  "build": "node src/cli.js build",
15
16
  "dev": "node src/cli.js dev",
16
- "test": "node --test tests/*.test.js"
17
+ "test": "node --test tests/*.test.js",
18
+ "test:cov": "node --test --experimental-test-coverage tests/*.test.js",
19
+ "test:e2e": "playwright test",
20
+ "test:extension": "npm --prefix editors/vscode ci && npm --prefix editors/vscode test",
21
+ "pack:extension": "cd editors/vscode && npx --yes @vscode/vsce package",
22
+ "typecheck": "tsc --noEmit",
23
+ "types": "tsc -p tsconfig.types.json",
24
+ "prepublishOnly": "npm test",
25
+ "smoke": "node scripts/smoke-consumer.mjs"
17
26
  },
18
27
  "files": [
19
28
  "src",
29
+ "types",
20
30
  "site",
21
- "docs",
31
+ "docs/cli.md",
32
+ "docs/spec-alignment.md",
22
33
  "AGENTS.md",
23
34
  "README.md",
24
35
  "LICENSE",
@@ -30,11 +41,17 @@
30
41
  "dependencies": {
31
42
  "markdown-it": "^14.2.0"
32
43
  },
44
+ "devDependencies": {
45
+ "@playwright/test": "^1.49.0",
46
+ "@types/markdown-it": "^14.1.2",
47
+ "@types/node": "^22.19.21",
48
+ "typescript": "^5.9.3"
49
+ },
33
50
  "repository": {
34
51
  "type": "git",
35
52
  "url": "git+https://github.com/ZVN-DEV/darkmown.git"
36
53
  },
37
- "homepage": "https://github.com/ZVN-DEV/darkmown#readme",
54
+ "homepage": "https://darkmown.com",
38
55
  "bugs": {
39
56
  "url": "https://github.com/ZVN-DEV/darkmown/issues"
40
57
  },
@@ -1,7 +1,7 @@
1
1
  [
2
- { "id": "loops", "title": "Nested conditionals in loops", "shipped": true, "blocked": false },
2
+ { "id": "loops", "title": "Nested `:if` inside loop rows", "shipped": true, "blocked": false },
3
3
  { "id": "editor", "title": "VS Code syntax highlighting", "shipped": true, "blocked": false },
4
- { "id": "filter", "title": "State-driven list filtering", "shipped": false, "blocked": false },
4
+ { "id": "filter", "title": "Reactive `@loop where` filtering", "shipped": true, "blocked": false },
5
5
  { "id": "api", "title": "First-party site/api/ runtime", "shipped": false, "blocked": true },
6
6
  { "id": "swap", "title": "HTML-fragment swap responses", "shipped": false, "blocked": false }
7
7
  ]
@@ -131,7 +131,7 @@ tags: [sales, revenue, "q1, q2"]
131
131
 
132
132
  ## Reactive directives
133
133
 
134
- Reactive pages opt into `/__wd/runtime.js` (~3.1 KB gzipped, under a CI-enforced 5 KB budget). Static pages do not.
134
+ Reactive pages opt into `/__wd/runtime.js` (~3.2 KB gzipped, under a CI-enforced 5 KB budget). Static pages do not.
135
135
 
136
136
  ```wd
137
137
  :state count = 0
@@ -176,6 +176,6 @@ See it all live on the [Data & Forms page](/data/).
176
176
 
177
177
  ## Spec status
178
178
 
179
- The implementation is faithful to the original core thesis: Markdown-first authoring, no component ceremony, zero runtime on static pages, and tiny direct-DOM reactivity only when declared. `:fetch`, `:form` (including server round-trips), `:computed`, and `persist` are all shipped and live — try them on the [Data & Forms page](/data/). Still on the roadmap: a first-party server runtime (`site/api/`), HTML-fragment swaps, and nested `:if` over loop items. See `docs/spec-alignment.md` in the package for the full audit.
179
+ The implementation is faithful to the original core thesis: Markdown-first authoring, no component ceremony, zero runtime on static pages, and tiny direct-DOM reactivity only when declared. `:fetch`, `:form` (including server round-trips), `:computed`, and `persist` are all shipped and live — try them on the [Data & Forms page](/data/). Still on the roadmap: a first-party server runtime (`site/api/`), HTML-fragment swaps, and `@loop` over fetched sub-paths. See `docs/spec-alignment.md` in the package for the full audit.
180
180
 
181
181
  </main>
@@ -133,3 +133,161 @@ a
133
133
  display block
134
134
  color $accent
135
135
  font-size .9rem
136
+
137
+ /* ── vs-Markdoc comparison ───────────────────────────────────────── */
138
+
139
+ .versus
140
+ display grid
141
+ grid-template-columns 1fr 1fr
142
+ gap 1.25rem
143
+ align-items start
144
+ margin 2.5rem 0 1rem
145
+
146
+ .vs-card
147
+ border 1px solid $line
148
+ radius $radius
149
+ bg $panel
150
+ padding 1.25rem 1.25rem 1.1rem
151
+ display flex
152
+ flex-direction column
153
+ min-width 0
154
+
155
+ .vs-card.vs-dark
156
+ bg #fbf7ef
157
+ border 1px solid #e3d8c4
158
+ box-shadow 0 1px 0 #efe7d6
159
+
160
+ .vs-head
161
+ display flex
162
+ align-items baseline
163
+ justify-content space-between
164
+ gap .75rem
165
+ margin-bottom .85rem
166
+
167
+ .vs-tag
168
+ font-family ui-monospace, SFMono-Regular, Menlo, monospace
169
+ font-size .72rem
170
+ font-weight 700
171
+ text-transform uppercase
172
+ letter-spacing .08em
173
+ padding .25rem .55rem
174
+ radius 999px
175
+
176
+ .vs-tag-wd
177
+ color white
178
+ bg $accent
179
+
180
+ .vs-tag-md
181
+ color $muted
182
+ bg #efece3
183
+ border 1px solid $line
184
+
185
+ .vs-sub
186
+ font-family ui-monospace, SFMono-Regular, Menlo, monospace
187
+ font-size .82rem
188
+ color $muted
189
+
190
+ .vs-card pre
191
+ margin .25rem 0 0
192
+ font-size .82rem
193
+ line-height 1.55
194
+
195
+ .vs-live-label
196
+ margin 1rem 0 .35rem
197
+ font-size .88rem
198
+ color $muted
199
+ font-style italic
200
+
201
+ .vs-live
202
+ border 1px dashed #cdbfa6
203
+ radius $radius
204
+ bg #fffdf8
205
+ padding 1rem 1.1rem .85rem
206
+ margin .15rem 0 0
207
+
208
+ .vs-live input[data-wd-bind-input]
209
+ width 100%
210
+ box-sizing border-box
211
+ font inherit
212
+ font-size .95rem
213
+ padding .55rem .7rem
214
+ border 1px solid $line
215
+ radius 6px
216
+ bg white
217
+ margin 0 0 .75rem
218
+
219
+ .vs-live .product
220
+ display flex
221
+ align-items center
222
+ justify-content space-between
223
+ gap .75rem
224
+ padding .5rem .7rem
225
+ margin .35rem 0
226
+ bg $panel
227
+ border 1px solid $line
228
+ radius 6px
229
+ font-size .95rem
230
+
231
+ .vs-live .product p
232
+ margin 0
233
+
234
+ .vs-live .product button
235
+ border 0
236
+ bg $accent
237
+ color white
238
+ font inherit
239
+ font-size .82rem
240
+ padding .35rem .7rem
241
+ radius 6px
242
+ cursor pointer
243
+ flex-shrink 0
244
+
245
+ .vs-live .product button:hover
246
+ bg #11514a
247
+
248
+ .vs-live > p:last-of-type
249
+ margin .85rem 0 0
250
+ font-size .95rem
251
+
252
+ .vs-needs
253
+ margin 1rem 0 0
254
+ padding 1rem 1.1rem
255
+ border-left 3px solid $warm
256
+ bg #fcf4ef
257
+ radius 6px
258
+
259
+ .vs-needs strong
260
+ color $warm
261
+ font-size .92rem
262
+
263
+ .vs-needs pre
264
+ margin .7rem 0 .6rem
265
+ font-size .78rem
266
+
267
+ .vs-needs-foot
268
+ display block
269
+ font-size .85rem
270
+ color $muted
271
+
272
+ .vs-caption
273
+ margin auto 0 0
274
+ padding-top 1rem
275
+ font-style italic
276
+ color $ink
277
+ font-size .95rem
278
+
279
+ .vs-card.vs-dark .vs-caption
280
+ color $accent
281
+
282
+ /* Secondary value-prop row, demoted below the comparison */
283
+
284
+ .props
285
+ margin 1.5rem 0 0
286
+
287
+ .props h2
288
+ margin-top 1.5rem
289
+
290
+ @media (max-width: 720px)
291
+ .versus
292
+ grid-template-columns 1fr
293
+ gap 1rem
@@ -1,6 +1,6 @@
1
1
  ---
2
- title: Darkmown — Markdown that runs
3
- description: Build full websites in plain Markdown. Routing, state, server calls, includes, and reactive islands — no JavaScript or CSS required. Your docs and your code are all just text.
2
+ title: Darkmown — Markdown that runs, not Markdown that needs React
3
+ description: A live store widget — search filter, add-to-cart, running total — written as one Markdown file that runs. The same outcome in Markdoc needs a React component, hydration, and a build step. Compare them side by side.
4
4
  ---
5
5
 
6
6
  @include /nav.wd
@@ -8,17 +8,105 @@ description: Build full websites in plain Markdown. Routing, state, server calls
8
8
  <main>
9
9
 
10
10
  <section class="hero">
11
- <p class="eyebrow">The Markdown framework</p>
11
+ <p class="eyebrow">Darkmown vs. Markdoc</p>
12
12
 
13
- # Markdown that runs.
13
+ # Markdown that runs. Not Markdown that needs React.
14
14
 
15
- Not just pages — **full websites**. Routing, state, server calls, embeds, and reactive islands, all from plain Markdown files. No JavaScript or CSS required. Your docs and your code are all just text.
15
+ The left file is Markdown. It is also a running app — a live search filter, add-to-cart, and a running total. The right file is the closest Markdoc can get on its own: a static list. To make it do what the left one already does, you add a React component, hydration, and a build step.
16
16
 
17
17
  <a class="btn primary" href="/app/">See a whole app in one file</a>
18
18
  <a class="btn" href="/docs/">Read the docs</a>
19
19
  </section>
20
20
 
21
- :note "Static pages ship zero JavaScript. The moment a page asks for reactivity, one ~3.1 KB runtime loads — never before. JS optional, CSS optional, always."
21
+ <section class="versus">
22
+
23
+ ::: div .vs-card .vs-dark
24
+ <header class="vs-head"><span class="vs-tag vs-tag-wd">Darkmown</span><span class="vs-sub">store.wd</span></header>
25
+
26
+ ```wd
27
+ :state products = [{"id":1,"name":"Aurora Lamp","price":49},{"id":2,"name":"Briza Fan","price":39},{"id":3,"name":"Cove Speaker","price":89},{"id":4,"name":"Dune Mug","price":12}]
28
+ :state cart = []
29
+ :state q = ""
30
+ :computed count = cart.length
31
+
32
+ :bind q placeholder="Search products…"
33
+
34
+ @loop products into p where p.name contains q
35
+ ::: card .product
36
+ **{ p.name }** — ${ p.price }
37
+ :button "Add" -> cart += p
38
+ :::
39
+ @endloop
40
+
41
+ Cart: { count } item(s)
42
+ ```
43
+
44
+ <p class="vs-live-label">↓ and here is that exact file, running</p>
45
+
46
+ ::: div .vs-live
47
+ :state products = [{"id":1,"name":"Aurora Lamp","price":49},{"id":2,"name":"Briza Fan","price":39},{"id":3,"name":"Cove Speaker","price":89},{"id":4,"name":"Dune Mug","price":12}]
48
+ :state cart = []
49
+ :state q = ""
50
+ :computed count = cart.length
51
+
52
+ :bind q placeholder="Search products…"
53
+
54
+ @loop products into p where p.name contains q
55
+ ::: card .product
56
+ **{ p.name }** — ${ p.price }
57
+ :button "Add" -> cart += p
58
+ :::
59
+ @endloop
60
+
61
+ Cart: **{ count }** item(s)
62
+ :::
63
+
64
+ <p class="vs-caption">This is the whole file. It runs.</p>
65
+ :::
66
+
67
+ ::: div .vs-card
68
+ <header class="vs-head"><span class="vs-tag vs-tag-md">Markdoc</span><span class="vs-sub">store.md</span></header>
69
+
70
+ ```text
71
+ {% if $products %}
72
+ - **Aurora Lamp** — $49
73
+ - **Briza Fan** — $39
74
+ - **Cove Speaker** — $89
75
+ - **Dune Mug** — $12
76
+ {% /if %}
77
+ ```
78
+
79
+ <p class="vs-live-label">Markdoc has <code>{% if %}</code>, <code>{% partial %}</code>, and <code>$variables</code> — but no loop tag, so each row is written by hand and the list is static. To filter, add to a cart, or total it, you reach for code:</p>
80
+
81
+ ::: div .vs-needs
82
+ <strong>+ store-widget.jsx</strong> — a React component, registered as a custom tag:
83
+
84
+ ```jsx
85
+ const tags = { store: { render: 'Store' } };
86
+ Markdoc.renderers.react(content, React, {
87
+ components: { Store } // your filter + cart + total live here
88
+ });
89
+ ```
90
+
91
+ <span class="vs-needs-foot">…plus a React + hydration runtime shipped to the browser, and the build step that wires the tag to the component.</span>
92
+ :::
93
+
94
+ <p class="vs-caption">Same outcome needs a React component.</p>
95
+ :::
96
+
97
+ </section>
98
+
99
+ :note "Static pages ship zero JavaScript. The moment a page asks for reactivity — like this very homepage — one ~3.2 KB runtime loads, never before. JS optional, CSS optional, always."
100
+
101
+ <section class="props">
102
+
103
+ ## Three rules, no escape hatch
104
+
105
+ - **Zero-JS static.** A page with no state ships no framework JavaScript. `/markdown/` and `/docs/` prove it — open them and check the network tab.
106
+ - **One runtime.** Every reactive page shares the same ~3.2 KB runtime. No per-component bundles, no hydration tax that scales with your UI.
107
+ - **One loop, one binding.** `@loop … into …` is the only loop; `{ name }` is the only interpolation. Nothing else to learn, nothing else to misuse.
108
+
109
+ </section>
22
110
 
23
111
  ## A whole framework, in text
24
112