@zvndev/darkmown 0.9.0 → 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/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
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
+
5
15
  ## 0.9.0 — 2026-06-15
6
16
 
7
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.
package/README.md CHANGED
@@ -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
@@ -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
  ```
@@ -213,7 +217,7 @@ Set `window.wd.debug = true` (it defaults to `false`) to log any `:computed` or
213
217
 
214
218
  ## Editor support
215
219
 
216
- 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.
217
221
 
218
222
  ## Security
219
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.
@@ -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.9.0",
3
+ "version": "0.10.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -17,9 +17,12 @@
17
17
  "test": "node --test tests/*.test.js",
18
18
  "test:cov": "node --test --experimental-test-coverage tests/*.test.js",
19
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",
20
22
  "typecheck": "tsc --noEmit",
21
23
  "types": "tsc -p tsconfig.types.json",
22
- "prepublishOnly": "npm test"
24
+ "prepublishOnly": "npm test",
25
+ "smoke": "node scripts/smoke-consumer.mjs"
23
26
  },
24
27
  "files": [
25
28
  "src",
@@ -48,7 +51,7 @@
48
51
  "type": "git",
49
52
  "url": "git+https://github.com/ZVN-DEV/darkmown.git"
50
53
  },
51
- "homepage": "https://github.com/ZVN-DEV/darkmown#readme",
54
+ "homepage": "https://darkmown.com",
52
55
  "bugs": {
53
56
  "url": "https://github.com/ZVN-DEV/darkmown/issues"
54
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
  ]
@@ -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>
@@ -166,4 +166,3 @@ Darkmown itself was built by a small agent team. The board below is a live `@loo
166
166
  @include /sprint-loop.wd
167
167
 
168
168
  </main>
169
- </content>
package/src/cli.js CHANGED
@@ -20,8 +20,9 @@ if (command === "help" || command === "--help" || command === "-h") {
20
20
  } else if (command === "init") {
21
21
  const target = process.argv[3] || ".";
22
22
  const result = initProject(path.resolve(process.cwd(), target));
23
- console.log(`Created Darkmown project at ${path.relative(process.cwd(), result.root) || "."}`);
24
- console.log(`Next: cd ${path.relative(process.cwd(), result.root) || "."} && npm install && npm run dev`);
23
+ const relativeRoot = path.relative(process.cwd(), result.root) || ".";
24
+ console.log(`Created Darkmown project at ${relativeRoot}`);
25
+ console.log(`Next: ${nextStep(relativeRoot)}`);
25
26
  } else if (command === "build") {
26
27
  const result = buildSite();
27
28
  console.log(`Built ${result.routes.length} routes into ${path.relative(process.cwd(), result.distRoot)}`);
@@ -157,6 +158,7 @@ Usage:
157
158
  darkmown dev Start the live compiler dev server
158
159
  darkmown build Compile site/pages into dist
159
160
  darkmown serve Preview the built dist locally
161
+ darkmown version Print the installed Darkmown version
160
162
  darkmown help Show this help
161
163
 
162
164
  Authoring:
@@ -167,3 +169,13 @@ Authoring:
167
169
  *.js Colocated page behavior
168
170
  `);
169
171
  }
172
+
173
+
174
+ /**
175
+ * @param {string} relativeRoot
176
+ * @returns {string}
177
+ */
178
+ function nextStep(relativeRoot) {
179
+ if (relativeRoot === ".") return "npm install && npm run dev";
180
+ return `cd ${relativeRoot} && npm install && npm run dev`;
181
+ }
package/src/scaffold.js CHANGED
@@ -13,9 +13,12 @@ const darkmownVersion = JSON.parse(
13
13
  export function initProject(root) {
14
14
  fs.mkdirSync(root, { recursive: true });
15
15
  writeNew(root, "package.json", JSON.stringify({
16
+ name: packageNameFromRoot(root),
17
+ private: true,
16
18
  scripts: {
17
19
  dev: "darkmown dev",
18
- build: "darkmown build"
20
+ build: "darkmown build",
21
+ preview: "darkmown serve"
19
22
  },
20
23
  devDependencies: {
21
24
  "@zvndev/darkmown": `^${darkmownVersion}`
@@ -146,3 +149,19 @@ function writeNew(root, file, content) {
146
149
  fs.mkdirSync(path.dirname(target), { recursive: true });
147
150
  fs.writeFileSync(target, `${content}\n`);
148
151
  }
152
+
153
+
154
+ /**
155
+ * Derive a safe default npm package name from the scaffold target directory.
156
+ * @param {string} root Absolute target directory.
157
+ * @returns {string}
158
+ */
159
+ function packageNameFromRoot(root) {
160
+ const base = path.basename(root) || "darkmown-site";
161
+ const normalized = base
162
+ .toLowerCase()
163
+ .replace(/^@/, "")
164
+ .replace(/[^a-z0-9._~-]+/g, "-")
165
+ .replace(/^[._-]+|[._-]+$/g, "");
166
+ return normalized || "darkmown-site";
167
+ }