@zvndev/darkmown 1.1.0 → 1.2.1
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 +17 -0
- package/README.md +54 -3
- package/docs/cli.md +46 -16
- package/docs/spec-alignment.md +9 -3
- package/package.json +1 -1
- package/site/_/roadmap.json +3 -1
- package/site/pages/app.wd +1 -1
- package/site/pages/data.wd +2 -2
- package/site/pages/docs/index.wd +21 -0
- package/site/pages/folio/cart.wd +2 -2
- package/site/pages/forge/index.wd +1 -1
- package/site/pages/interactions/index.skin +113 -0
- package/site/pages/interactions/index.wd +86 -0
- package/src/api-runner.js +219 -0
- package/src/behaviors/carousel.js +153 -0
- package/src/behaviors/sortable.js +175 -0
- package/src/builder.js +199 -4
- package/src/cli.js +79 -54
- package/src/compiler/body.js +14 -0
- package/src/compiler/context.js +12 -1
- package/src/compiler/directives.js +123 -0
- package/src/compiler/index.js +1 -0
- package/src/compiler/loops.js +57 -8
- package/src/compiler/page.js +9 -3
- package/src/deploy.js +174 -0
- package/src/headers.js +4 -0
- package/src/runtime.js +3 -1
- package/src/scaffold.js +79 -147
- package/src/templates/blog/README.md +25 -0
- package/src/templates/blog/site/_/nav.wd +6 -0
- package/src/templates/blog/site/_/posts.json +16 -0
- package/src/templates/blog/site/pages/index.skin +51 -0
- package/src/templates/blog/site/pages/index.wd +24 -0
- package/src/templates/blog/site/pages/posts/hello-darkmown.md +16 -0
- package/src/templates/blog/site/pages/posts/markdown-native.md +20 -0
- package/src/templates/dashboard/README.md +25 -0
- package/src/templates/dashboard/api/metrics.js +15 -0
- package/src/templates/dashboard/site/_/nav.wd +6 -0
- package/src/templates/dashboard/site/pages/index.skin +74 -0
- package/src/templates/dashboard/site/pages/index.wd +75 -0
- package/src/templates/landing/README.md +25 -0
- package/src/templates/landing/site/_/nav.wd +6 -0
- package/src/templates/landing/site/pages/index.skin +140 -0
- package/src/templates/landing/site/pages/index.wd +54 -0
- package/src/templates/starter/README.md +32 -0
- package/src/templates/starter/site/_/nav.wd +7 -0
- package/src/templates/starter/site/pages/about.md +10 -0
- package/src/templates/starter/site/pages/index.skin +47 -0
- package/src/templates/starter/site/pages/index.wd +27 -0
- package/src/templates/store/README.md +28 -0
- package/src/templates/store/api/checkout.js +18 -0
- package/src/templates/store/site/_/nav.wd +6 -0
- package/src/templates/store/site/_/products.json +8 -0
- package/src/templates/store/site/pages/index.skin +100 -0
- package/src/templates/store/site/pages/index.wd +83 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
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
|
+
## 1.2.1 — 2026-06-27
|
|
6
|
+
|
|
7
|
+
Repository hygiene and documentation polish from a gold-standard audit (50/50 — every dimension at React-tier). No runtime, API, or behavior change; the shipped package is byte-identical except for one source comment.
|
|
8
|
+
|
|
9
|
+
- **Architecture docs name the value layer.** `src/compiler/format.js` (format pipes + list aggregates) is now listed in the compiler module map in both `CLAUDE.md` and `src/compiler/index.js` — previously the only module in the compile path absent from the architecture doc.
|
|
10
|
+
- **Honest CI labels.** The coverage-gate job was labeled "src-only line ≥ 90%" but `npm run test:cov` has always enforced **100%** (`scripts/test-coverage-gate.mjs 100`). The job name and its comment now state the real bar.
|
|
11
|
+
- **Tighter ignore rules.** `.env` / `.env.*` are now gitignored (the build's hidden-segment asset guard already kept them out of `dist`; this stops one from ever reaching git), and a stale internal design spec that predated the `docs/superpowers/` ignore rule is no longer tracked.
|
|
12
|
+
|
|
13
|
+
## 1.2.0 — 2026-06-27
|
|
14
|
+
|
|
15
|
+
Backends, deploy, and rich interactions — all additive. The core runtime is unchanged (still well under the 8 KB budget); the new behavior modules are budgeted separately so static pages stay zero-JS.
|
|
16
|
+
|
|
17
|
+
- **Serverless `api/` model.** Backend endpoints are plain-JS Web-standard handlers — `export default (request, context) => Response` — in a top-level `api/` directory (`api/echo.js` → `/api/echo`; `api/users/[id].js` → `/api/users/:id`). Darkmown owns no server and adds no backend DSL: it adapts to the host. The build stays 100% static. `darkmown dev` runs a local runner so `/api/*` behaves identically before you deploy; on **Vercel** the functions run natively (Edge); `darkmown build --target cloudflare` emits a `dist/_worker.js` that routes `/api/*` to the same functions and serves the rest from `env.ASSETS`. `form-action 'self'` was added to both CSP variants (and `vercel.json`) so a native `:form action="/api/…"` POST is allowed.
|
|
18
|
+
- **Templates + deploy CLI.** `darkmown init --template <starter|blog|store|dashboard|landing>` scaffolds from in-package templates — each a real, deployable site (`store`/`dashboard` ship an example `api/` function). `darkmown deploy <vercel|cloudflare> [--prod]` writes platform config, runs a target-aware build, wraps the platform CLI (`npx vercel` / `npx wrangler pages deploy`), and prints the deploy URL; an unauthenticated CLI surfaces the login to run.
|
|
19
|
+
- **Interaction behaviors (pay-for-what-you-use).** `:slider name = v min max step` is compile-time only — a range input bound through `:bind` (range values coerce to Number), shipping **no** JS. `:sortable` (a `@loop` clause) and `:carousel` compile to tiny `/__wd/behaviors/<name>.js` modules injected **only** on the pages that use them, budgeted **separately** from the core runtime in `.size-snapshot.json`. `:sortable` drag-reorders a plain reactive `:state`/`:store` loop via Pointer Events + Arrow keys through the public `window.wd` API; `:carousel` uses native CSS scroll-snap plus prev/next, dots, autoplay, and mouse drag. New `/interactions/` demo page.
|
|
20
|
+
- **Review hardening (a11y + backend).** Drag/keyboard feedback and focus now target the author's styled row (not the loop wrapper), so the dragging style and focus ring actually show; keyboard reorder gains screen-reader instructions, `aria-posinset`/`aria-setsize`, and a live "Moved to position N of M" announcement. The carousel drops an incomplete `tablist`/`tab` role for plain buttons with `aria-current`, suppresses autoplay under `prefers-reduced-motion` (and pauses when the tab is hidden). The dev API runner preserves multiple `Set-Cookie` headers (no comma-fold), matching production; `darkmown deploy` recognizes wrangler's real auth-failure wording and strips trailing punctuation from parsed URLs; the build warns on `api/` route collisions and on bare npm imports in Cloudflare advanced-mode handlers (which ship without a bundler).
|
|
21
|
+
|
|
5
22
|
## 1.1.0 — 2026-06-27
|
|
6
23
|
|
|
7
24
|
Follow-up to 1.0: turn the two rough edges flagged at release into real fixes. No runtime change (still ~7.4 KB gzipped).
|
package/README.md
CHANGED
|
@@ -50,9 +50,10 @@ npm run dev # live demo site — the same site that runs darkmown.com
|
|
|
50
50
|
|
|
51
51
|
## Commands
|
|
52
52
|
|
|
53
|
-
- `darkmown init [dir]` scaffolds a new site.
|
|
54
|
-
- `darkmown dev` starts the live compiler with browser reload
|
|
55
|
-
- `darkmown build` writes static output to `dist`.
|
|
53
|
+
- `darkmown init [dir] [--template <name>]` scaffolds a new site from a template (`starter`, `blog`, `store`, `dashboard`, `landing`).
|
|
54
|
+
- `darkmown dev` starts the live compiler with browser reload, an in-browser error overlay, and a local runner for `api/*` functions.
|
|
55
|
+
- `darkmown build [--target cloudflare]` writes static output to `dist`.
|
|
56
|
+
- `darkmown deploy <vercel|cloudflare> [--prod]` builds and deploys via the platform CLI.
|
|
56
57
|
- `darkmown serve` previews the built `dist` locally.
|
|
57
58
|
- `darkmown version` prints the installed package version.
|
|
58
59
|
- `darkmown help` prints CLI usage.
|
|
@@ -615,6 +616,56 @@ Fetched data and a form live happily on the same page:
|
|
|
615
616
|
- `:computed total = items.length * 4` derives state from state with a compile-time-checked expression — names, numbers, arithmetic, comparisons, and list aggregates (`sum`/`avg`/`min`/`max`/`count`). See [Computed values](#computed-values--computed).
|
|
616
617
|
- `:if item.path` works inside reactive loops for per-row branches, and nests — an inner `:if` resolves after the outer branch and stays reactive.
|
|
617
618
|
|
|
619
|
+
## Backends & deploy
|
|
620
|
+
|
|
621
|
+
Darkmown builds **100% static, CDN-cacheable HTML** — never per-request server rendering. Reactive pages hydrate from data-attributes client-side; dynamic data arrives via `:fetch`. When you need a backend, you don't learn a new syntax — you write a plain serverless function, and *it just comes with wherever you deploy*.
|
|
622
|
+
|
|
623
|
+
A backend endpoint is a plain-JS Web-standard handler in a top-level `api/` directory:
|
|
624
|
+
|
|
625
|
+
```js
|
|
626
|
+
// api/subscribe.js → /api/subscribe
|
|
627
|
+
export const config = { runtime: "edge" }; // Vercel runs api/ as Edge Functions
|
|
628
|
+
|
|
629
|
+
export default async function (request, context) {
|
|
630
|
+
const { email } = await request.json(); // context.params for /api/users/[id]
|
|
631
|
+
return Response.json({ ok: true, email });
|
|
632
|
+
}
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
- **One shape, every host.** `export default (request) => Response` is exactly what Vercel Edge, Cloudflare Pages, and Netlify Edge run. `api/users/[id].js` → `/api/users/:id`.
|
|
636
|
+
- **Local parity.** `darkmown dev` runs a local runner, so `:fetch /api/subscribe` and `:form action="/api/subscribe"` behave the same as production before you ever deploy.
|
|
637
|
+
- **Deploy in one command.** `darkmown deploy vercel` (functions run natively) or `darkmown deploy cloudflare` (the build emits a `dist/_worker.js` that routes `/api/*` and serves the rest from `env.ASSETS`). It prints your URL, or the login to run if the platform CLI isn't signed in.
|
|
638
|
+
- **Custom server / remote backend.** Point `:fetch`/`:form` at an absolute `https://…` URL and widen the CSP `connect-src` (and `form-action` for native form POSTs). Darkmown owns no server — it adapts to yours.
|
|
639
|
+
|
|
640
|
+
Templates get you to a running, deployable app fast: `darkmown init shop --template store` ships a cart **and** an `api/checkout.js`; `--template dashboard` ships a `:fetch` view **and** an `api/metrics.js`.
|
|
641
|
+
|
|
642
|
+
## Interactions — `:slider`, `:sortable`, `:carousel`
|
|
643
|
+
|
|
644
|
+
Rich interactions are **pay-for-what-you-use**: `:sortable`/`:carousel` compile to a tiny `/__wd/behaviors/<name>.js` module injected **only** on pages that use them, budgeted separately from the ≤8 KB core runtime. `:slider` is compile-time only — zero extra JS.
|
|
645
|
+
|
|
646
|
+
```wd
|
|
647
|
+
:slider volume = 50 min=0 max=100 step=5
|
|
648
|
+
Volume: { volume }
|
|
649
|
+
|
|
650
|
+
:store tasks = ["Draft", "Review", "Ship"]
|
|
651
|
+
@loop tasks into t sortable
|
|
652
|
+
- { t }
|
|
653
|
+
@endloop
|
|
654
|
+
|
|
655
|
+
:carousel autoplay=4000
|
|
656
|
+
::: slide
|
|
657
|
+
First slide
|
|
658
|
+
:::
|
|
659
|
+
::: slide
|
|
660
|
+
Second slide
|
|
661
|
+
:::
|
|
662
|
+
:endcarousel
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
- **`:slider name = v min max step`** renders a range input two-way bound through `:bind`; range values coerce to Number so `:computed`/math see a number. Ships no behavior module.
|
|
666
|
+
- **`:sortable`** (a `@loop` clause) drag-reorders the underlying `:state`/`:store` list via Pointer Events (mouse + touch), with full keyboard support (Arrow Up/Down on a focused row, screen-reader instructions + a live "Moved to position N of M" announcement), rewriting the list through the public `window.wd` API so the keyed loop repaints. Valid only on a plain reactive loop (no `where`/`sort`/`reverse`/`offset`/`limit`).
|
|
667
|
+
- **`:carousel [autoplay=N]`** treats **each direct child block as one slide** (wrap each in its own block, e.g. `::: slide`, and size it in your skin), using native CSS scroll-snap (touch swipe is free) plus prev/next buttons, dot navigation, and mouse drag. `autoplay` is suppressed under `prefers-reduced-motion`.
|
|
668
|
+
|
|
618
669
|
## Inline attributes
|
|
619
670
|
|
|
620
671
|
A trailing `{.class .class #id}` attaches classes / an id to the inline element directly before it — most often to style a link as a button without a wrapper:
|
package/docs/cli.md
CHANGED
|
@@ -20,18 +20,21 @@ Prints the command summary, authoring directories, and common directive syntax.
|
|
|
20
20
|
|
|
21
21
|
Prints the installed package version.
|
|
22
22
|
|
|
23
|
-
### `darkmown init [dir]`
|
|
23
|
+
### `darkmown init [dir] [--template <name>]`
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
Scaffolds a Darkmown site from a template (default: `starter`). Available templates:
|
|
26
26
|
|
|
27
|
-
-
|
|
28
|
-
- `
|
|
29
|
-
- `
|
|
30
|
-
- `
|
|
31
|
-
-
|
|
32
|
-
- `README.md`
|
|
27
|
+
- **`starter`** — the minimal counter + todo-loop site.
|
|
28
|
+
- **`blog`** — markdown posts + a `.wd` index that loops a JSON manifest.
|
|
29
|
+
- **`store`** — product grid, a shared `:store` cart, and a checkout that posts to `api/checkout.js`.
|
|
30
|
+
- **`dashboard`** — stat cards that `:fetch` from `api/metrics.js`.
|
|
31
|
+
- **`landing`** — a marketing one-pager with a `:carousel`.
|
|
33
32
|
|
|
34
|
-
|
|
33
|
+
```sh
|
|
34
|
+
darkmown init my-site --template store
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Existing files are never overwritten. The generated `package.json` is private, sets `"type": "module"` (so `api/*.js` import as ESM), includes `dev`/`build`/`preview` scripts, and names the app after the target directory.
|
|
35
38
|
|
|
36
39
|
After scaffolding:
|
|
37
40
|
|
|
@@ -52,17 +55,28 @@ Runs a live compiler:
|
|
|
52
55
|
- injects `/__wd/dev-client.js`
|
|
53
56
|
- reloads the browser through `/__wd/dev-events`
|
|
54
57
|
|
|
55
|
-
### `darkmown build`
|
|
58
|
+
### `darkmown build [--target cloudflare]`
|
|
59
|
+
|
|
60
|
+
Compiles `site/pages` into `dist`. The output is always 100% static HTML.
|
|
56
61
|
|
|
57
|
-
|
|
62
|
+
Static pages do not receive `/__wd/runtime.js`; reactive pages do. Pay-for-what-you-use behavior modules (`:sortable`, `:carousel`) emit to `dist/__wd/behaviors/` only on the pages that use them.
|
|
58
63
|
|
|
59
|
-
|
|
64
|
+
`--target cloudflare` additionally emits `dist/_worker.js` — a Cloudflare Pages worker that routes `/api/*` to the project's `api/*.js` functions and serves everything else from `env.ASSETS`. The default target leaves `api/` for Vercel to run natively. `darkmown deploy` sets the right target automatically.
|
|
60
65
|
|
|
61
66
|
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
67
|
|
|
68
|
+
### `darkmown deploy <vercel|cloudflare> [--prod]`
|
|
69
|
+
|
|
70
|
+
Builds (target-aware) and deploys to the platform by wrapping its CLI:
|
|
71
|
+
|
|
72
|
+
- **`vercel`** — writes a `vercel.json` if absent, then runs `npx vercel deploy` (`--prod` to promote). `api/` functions run natively as Edge Functions.
|
|
73
|
+
- **`cloudflare`** — builds the `dist/_worker.js`, then runs `npx wrangler pages deploy dist --project-name <name>`.
|
|
74
|
+
|
|
75
|
+
The deploy URL is printed when the CLI reports it. If the platform CLI isn't signed in, the command surfaces the login to run (`npx vercel login` / `npx wrangler login`) — in a Claude Code session you can run it inline with the `!` prefix — then re-run the deploy.
|
|
76
|
+
|
|
63
77
|
### `darkmown serve`
|
|
64
78
|
|
|
65
|
-
Serves the already-built `dist` directory for local preview. Run `darkmown build` first.
|
|
79
|
+
Serves the already-built `dist` directory for local preview (static only — use `darkmown dev` to exercise `api/*` locally). Run `darkmown build` first.
|
|
66
80
|
|
|
67
81
|
## Smoke checks
|
|
68
82
|
|
|
@@ -74,8 +88,24 @@ npm run smoke
|
|
|
74
88
|
|
|
75
89
|
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.
|
|
76
90
|
|
|
77
|
-
##
|
|
91
|
+
## Backends (`api/`)
|
|
92
|
+
|
|
93
|
+
Backend endpoints are plain-JS Web-standard handlers in a top-level `api/` directory — the same shape on every host, and the only thing you write for a server:
|
|
94
|
+
|
|
95
|
+
```js
|
|
96
|
+
// api/subscribe.js → /api/subscribe
|
|
97
|
+
export const config = { runtime: "edge" }; // Vercel: run as an Edge Function
|
|
98
|
+
|
|
99
|
+
export default async function (request, context) {
|
|
100
|
+
const { email } = await request.json();
|
|
101
|
+
return Response.json({ ok: true, email });
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`api/users/[id].js` maps to `/api/users/:id` (`context.params.id`). These run in `darkmown dev` (local runner), on **Vercel** natively, and on **Cloudflare Pages** via the `dist/_worker.js` that `darkmown deploy cloudflare` emits. `:fetch`/`:form` point at `/api/*` with no extra config. Need a Node-only API on Vercel? Drop `export const config` and use `(req, res)` — note local-dev parity is then lost for that function.
|
|
106
|
+
|
|
107
|
+
**Custom server / remote backend.** Point `:fetch`/`:form` at an absolute URL (`https://api.example.com/…`) and widen the CSP `connect-src` (and `form-action` for native form POSTs) in `vercel.json` / `dist/_headers` / your serve config. Darkmown still ships static; only the requests leave your origin.
|
|
78
108
|
|
|
79
|
-
### Cloudflare Pages
|
|
109
|
+
### Cloudflare Pages note
|
|
80
110
|
|
|
81
|
-
|
|
111
|
+
Cloudflare advanced mode (`dist/_worker.js`) does not run a bundler, so an `api/` handler deployed to Cloudflare must be dependency-free (or pre-bundled). The same source still runs on Vercel (which bundles) and in `darkmown dev`.
|
package/docs/spec-alignment.md
CHANGED
|
@@ -41,7 +41,7 @@ This audit compares the current Darkmown implementation with the original MARROW
|
|
|
41
41
|
|
|
42
42
|
## Stage 5 additions (2026-06-11, same day)
|
|
43
43
|
|
|
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.
|
|
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.
|
|
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
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.
|
|
@@ -134,13 +134,19 @@ Four progressive-disclosure capabilities plus an assets and a security change. T
|
|
|
134
134
|
- **Page-colocated static assets:** any non-page file under `site/pages/` (anything but `.md`/`.wd` routes and colocated `.skin`/`.js`) is copied to `dist/` at its path (`site/pages/logo.svg` → `dist/logo.svg`). The shelf (`site/_/` → `/__wd/media`, `/__wd/data`) is unchanged; a name clash with an emitted route is skipped with a warning. As of v0.14.1, hidden path segments (`.`, `-`, `_`) and symlinked page assets are skipped. `.avif` was added to the served content-types.
|
|
135
135
|
- **`:fetch` URL scheme hardening (security):** `:fetch`/`refresh=` URLs are validated at compile time — relative, `http(s)://`, or a leading `{ state }` interpolation are allowed; protocol-relative `//host` and non-http(s) schemes (`file:`, `data:`, `javascript:`, …) are rejected with a corrective hint. **This is scheme validation, not a host allowlist** — SSRF to arbitrary http(s) hosts remains the author's responsibility (see the trust boundary).
|
|
136
136
|
|
|
137
|
+
## 1.2.0 additions — backends, deploy & interactions (2026-06-27)
|
|
138
|
+
|
|
139
|
+
- **Serverless `api/` model:** backend endpoints are plain-JS Web-standard handlers — `export default (request, context) => Response` — in a top-level `api/` directory (`api/echo.js` → `/api/echo`; `api/users/[id].js` → `/api/users/:id` with `context.params.id`). Darkmown owns no server; it *adapts* to the host. The build stays 100% static. On Vercel the `api/` functions run natively (Edge); `darkmown deploy cloudflare` (or `build --target cloudflare`) emits `dist/_worker.js` that routes `/api/*` to the same functions and serves everything else from `env.ASSETS`. `darkmown dev` runs a local runner so `/api/*` behaves identically before deploy. No `.wd` backend DSL — minimal new learning, zero lock-in. `form-action 'self'` was added to both CSP variants (and `vercel.json`) so a native `:form action="/api/…"` POST is allowed.
|
|
140
|
+
- **Templates + deploy CLI:** `darkmown init --template <starter|blog|store|dashboard|landing>` scaffolds from in-package templates (each a real, deployable site; `store`/`dashboard` ship an example `api/` function). `darkmown deploy <vercel|cloudflare> [--prod]` writes platform config, runs a target-aware build, and wraps the platform CLI (`npx vercel` / `npx wrangler pages deploy`), printing the deploy URL; an unauthenticated CLI surfaces a login hint.
|
|
141
|
+
- **Interaction behaviors (pay-for-what-you-use):** `:slider name = v min max step` is compile-time only — a range input bound through `:bind` (range values coerce to Number), ships **no** JS module. `:sortable` (a `@loop` clause) and `:carousel` compile to tiny `/__wd/behaviors/<name>.js` modules injected **only** on pages that use them, built on the public `window.wd` API. They are budgeted **separately** from the 8 KB core runtime (each in `.size-snapshot.json`), so core stays untaxed. `:sortable` requires a plain reactive `:state`/`:store` loop (no where/sort/reverse/offset/limit) and reorders via Pointer Events + keyboard; `:carousel` uses native CSS scroll-snap plus prev/next/dots/autoplay/mouse-drag.
|
|
142
|
+
|
|
137
143
|
## Demo-only directives (not part of the public surface)
|
|
138
144
|
|
|
139
|
-
The compiler recognizes three directives — `:note`, `:try`, and `:sprint` — that exist **only to power the demo site** (darkmown.com). They are **not** part of the supported public directive set, are not documented for authors, and may change or be removed without notice. Do not build pages that depend on them. The supported public directives are the ones catalogued above: `:state`, `:store`, `:computed`, `:effect`, `:every`, `:theme`, `:button`, `:bind`, `:if`/`:else if`/`:else`/`:endif`, `:fetch` (with `headers`/`refresh`), `:form`/`:input`/`:submit`, `:video`/`:audio`/`:embed`, `@loop`/`@empty`/`@endloop` (with `where`, `sort by` — including reactive `{ state }` keys —, `reverse`, `offset`, `limit`), `@include`, and `::: section` (with reactive `.class when`). Interpolation supports **format pipes** (`{ value | name:arg }`) and `:computed`/list-pipe **aggregates** (`sum`/`avg`/`min`/`max`/`count`). The runtime escape hatch is `window.wd` (`get`/`set`/`subscribe`/`state`/`render`).
|
|
145
|
+
The compiler recognizes three directives — `:note`, `:try`, and `:sprint` — that exist **only to power the demo site** (darkmown.com). They are **not** part of the supported public directive set, are not documented for authors, and may change or be removed without notice. Do not build pages that depend on them. The supported public directives are the ones catalogued above: `:state`, `:store`, `:computed`, `:effect`, `:every`, `:theme`, `:button`, `:bind`, `:if`/`:else if`/`:else`/`:endif`, `:fetch` (with `headers`/`refresh`), `:form`/`:input`/`:submit`, `:slider`, `:carousel`, `:video`/`:audio`/`:embed`, `@loop`/`@empty`/`@endloop` (with `where`, `sort by` — including reactive `{ state }` keys —, `reverse`, `offset`, `limit`, `sortable`), `@include`, and `::: section` (with reactive `.class when`). Interpolation supports **format pipes** (`{ value | name:arg }`) and `:computed`/list-pipe **aggregates** (`sum`/`avg`/`min`/`max`/`count`). The runtime escape hatch is `window.wd` (`get`/`set`/`subscribe`/`state`/`render`).
|
|
140
146
|
|
|
141
147
|
## Still missing from the full vision
|
|
142
148
|
|
|
143
|
-
-
|
|
149
|
+
- HTML-fragment `swap` semantics and cart server sync — still parked. (The serverless `api/` model shipped in 1.2; a `.wd` backend DSL remains a deliberate non-goal.)
|
|
144
150
|
- **View transitions:** named/typed transitions (`view-transition-name`, per-element morphs) and `@starting-style` emission, on top of the directional fade + speculation-rules prerender shipped in 0.18.0 (see Stage 4 / Stage 14).
|
|
145
151
|
- 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.
|
|
146
152
|
|
package/package.json
CHANGED
package/site/_/roadmap.json
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
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
4
|
{ "id": "filter", "title": "Reactive `@loop where` filtering", "shipped": true, "blocked": false },
|
|
5
|
-
{ "id": "api", "title": "
|
|
5
|
+
{ "id": "api", "title": "Serverless `api/` model + local-dev runner", "shipped": true, "blocked": false },
|
|
6
|
+
{ "id": "deploy", "title": "`init --template` + `darkmown deploy` CLI", "shipped": true, "blocked": false },
|
|
7
|
+
{ "id": "behaviors", "title": "Interaction behaviors (`:slider`/`:sortable`/`:carousel`)", "shipped": true, "blocked": false },
|
|
6
8
|
{ "id": "swap", "title": "HTML-fragment swap responses", "shipped": false, "blocked": false }
|
|
7
9
|
]
|
package/site/pages/app.wd
CHANGED
|
@@ -70,7 +70,7 @@ Nice — you are tracking the whole near-term roadmap.
|
|
|
70
70
|
|
|
71
71
|
This form posts to the demo echo endpoint and shows the reply. With JavaScript it is a `fetch`; without it, the exact same markup is a native POST. On Vercel/dev the sample endpoint replies; on static-only hosts, point `action=` at your own backend — Darkmown adapts instead of owning one:
|
|
72
72
|
|
|
73
|
-
:form action="/
|
|
73
|
+
:form action="/api/echo" into reply
|
|
74
74
|
:input note placeholder="Tell us what you'd build with this"
|
|
75
75
|
:submit "Send to the server"
|
|
76
76
|
:endform
|
package/site/pages/data.wd
CHANGED
|
@@ -95,7 +95,7 @@ Submit the form and this sentence reacts. Nothing leaves the page.
|
|
|
95
95
|
|
|
96
96
|
Darkmown does not own your backend — point a form at any endpoint. With JS, the submit becomes a fetch and the JSON reply lands in state; without JS, the same markup is a plain native POST:
|
|
97
97
|
|
|
98
|
-
:form action="/
|
|
98
|
+
:form action="/api/echo" into reply
|
|
99
99
|
:input message placeholder="Say something to the server"
|
|
100
100
|
:submit "Send to server"
|
|
101
101
|
:endform
|
|
@@ -110,7 +110,7 @@ The reply will appear here.
|
|
|
110
110
|
The request failed: { reply_error }
|
|
111
111
|
:endif
|
|
112
112
|
|
|
113
|
-
In `darkmown dev` the echo endpoint is built into the dev server; on darkmown.com it is a real serverless function behind the same URL. On static-only hosts such as a plain Cloudflare Pages deploy, replace `action="/
|
|
113
|
+
In `darkmown dev` the echo endpoint is built into the dev server; on darkmown.com it is a real serverless function behind the same URL. On static-only hosts such as a plain Cloudflare Pages deploy, replace `action="/api/echo"` with your own API endpoint. Either way, Darkmown itself stays static — sessions ride on `:fetch` plus ordinary cookies against your real API.
|
|
114
114
|
|
|
115
115
|
## Persistent state survives reload
|
|
116
116
|
|
package/site/pages/docs/index.wd
CHANGED
|
@@ -362,6 +362,27 @@ You have { cart } items.
|
|
|
362
362
|
|
|
363
363
|
See it all live on the [Data & Forms page](/data/).
|
|
364
364
|
|
|
365
|
+
## Backends & deploy
|
|
366
|
+
|
|
367
|
+
Darkmown builds 100% static HTML — never per-request rendering. When you need a server, you write a plain serverless function; there is no Darkmown backend syntax to learn.
|
|
368
|
+
|
|
369
|
+
- A backend endpoint is a Web-standard handler in a top-level `api/` directory: `export default (request, context) => Response`. `api/echo.js` → `/api/echo`; `api/users/[id].js` → `/api/users/:id`.
|
|
370
|
+
- One shape runs everywhere: `darkmown dev` (local runner), Vercel (native Edge Functions), and Cloudflare Pages (the build emits a `dist/_worker.js`). `:fetch`/`:form` point at `/api/*` with no extra config.
|
|
371
|
+
- `darkmown deploy vercel` or `darkmown deploy cloudflare` builds and ships in one command, printing your URL.
|
|
372
|
+
- For a remote/custom backend, point `:fetch`/`:form` at an absolute URL and widen the CSP `connect-src` / `form-action`. Darkmown owns no server — it adapts to yours.
|
|
373
|
+
|
|
374
|
+
`darkmown init shop --template store` scaffolds a cart **and** its `api/checkout.js`; `--template dashboard` ships a `:fetch` view **and** `api/metrics.js`.
|
|
375
|
+
|
|
376
|
+
## Interactions — `:slider`, `:sortable`, `:carousel`
|
|
377
|
+
|
|
378
|
+
Pay-for-what-you-use: `:sortable`/`:carousel` compile to a tiny `/__wd/behaviors/*.js` module loaded only where used, budgeted separately from the ≤8 KB core runtime. `:slider` is compile-time only.
|
|
379
|
+
|
|
380
|
+
- `:slider name = v min=0 max=100 step=5` — a range input bound through `:bind`; range values coerce to Number.
|
|
381
|
+
- `:sortable` (a `@loop` clause) — drag-to-reorder a `:state`/`:store` list via Pointer Events, with keyboard (Arrow Up/Down) + screen-reader announcements; valid on a plain reactive loop only.
|
|
382
|
+
- `:carousel [autoplay=N]` — each direct child block is one slide (wrap each in its own block, e.g. `::: slide`); native scroll-snap plus prev/next, dots, and mouse drag. Autoplay respects `prefers-reduced-motion`.
|
|
383
|
+
|
|
384
|
+
See all three live on the [Interactions page](/interactions/).
|
|
385
|
+
|
|
365
386
|
## Theme toggle — `:theme`
|
|
366
387
|
|
|
367
388
|
For an explicit light/dark switch alongside the OS preference, declare `:theme` once and drive it with buttons. It registers a durable `theme` store and reflects onto `<html data-theme>`:
|
package/site/pages/folio/cart.wd
CHANGED
|
@@ -106,7 +106,7 @@ Tax (8%)
|
|
|
106
106
|
|
|
107
107
|
This is a real form. With the runtime it posts over `fetch` and shows the reply inline; with JavaScript off, the identical markup degrades to a native POST. The demo endpoint simply echoes your order back.
|
|
108
108
|
|
|
109
|
-
:form action="/
|
|
109
|
+
:form action="/api/echo" into receipt
|
|
110
110
|
:input name placeholder="Full name" required
|
|
111
111
|
:input email type=email placeholder="you@studio.com" required
|
|
112
112
|
:select shipping
|
|
@@ -172,7 +172,7 @@ Total with tax — **{ total | money }**
|
|
|
172
172
|
|
|
173
173
|
:button "Clear basket" -> cart clear
|
|
174
174
|
|
|
175
|
-
:form action="/
|
|
175
|
+
:form action="/api/echo" into receipt
|
|
176
176
|
:input name placeholder="Full name" required
|
|
177
177
|
:input email type=email placeholder="you@studio.com" required
|
|
178
178
|
:select shipping
|
|
@@ -247,7 +247,7 @@ A month-to-month list price would run **${ annual }/yr**. Choose yearly at check
|
|
|
247
247
|
Drop in your details and we'll spin up your **${ total }/mo** workspace — no charge for 14 days.
|
|
248
248
|
|
|
249
249
|
::: div .checkout-card
|
|
250
|
-
:form action="/
|
|
250
|
+
:form action="/api/echo" into receipt
|
|
251
251
|
:input name placeholder="Your name"
|
|
252
252
|
:input email placeholder="Work email"
|
|
253
253
|
:submit "Start 14-day trial"
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
main.app-interactions
|
|
2
|
+
max-width 760px
|
|
3
|
+
margin 0 auto
|
|
4
|
+
padding 2.5rem 1.5rem
|
|
5
|
+
|
|
6
|
+
.ix-eyebrow
|
|
7
|
+
text-transform uppercase
|
|
8
|
+
letter-spacing .08em
|
|
9
|
+
font-size .8rem
|
|
10
|
+
opacity .7
|
|
11
|
+
|
|
12
|
+
.ix-tagline
|
|
13
|
+
font-size 1.05rem
|
|
14
|
+
opacity .85
|
|
15
|
+
margin-top .5rem
|
|
16
|
+
|
|
17
|
+
.ix-panel
|
|
18
|
+
margin 1.5rem 0
|
|
19
|
+
padding 1.5rem
|
|
20
|
+
|
|
21
|
+
.ix-panel h2
|
|
22
|
+
margin-top 0
|
|
23
|
+
|
|
24
|
+
.ix-note
|
|
25
|
+
margin-top 1rem
|
|
26
|
+
font-size .9rem
|
|
27
|
+
opacity .7
|
|
28
|
+
|
|
29
|
+
.readout
|
|
30
|
+
margin-top .75rem
|
|
31
|
+
font-size 1.1rem
|
|
32
|
+
|
|
33
|
+
input[type=range]
|
|
34
|
+
width 100%
|
|
35
|
+
accent-color $accent
|
|
36
|
+
|
|
37
|
+
.sortlist .row
|
|
38
|
+
display flex
|
|
39
|
+
align-items center
|
|
40
|
+
gap .6rem
|
|
41
|
+
padding .7rem .9rem
|
|
42
|
+
margin .4rem 0
|
|
43
|
+
border 1px solid $line
|
|
44
|
+
radius 10px
|
|
45
|
+
background $paper
|
|
46
|
+
cursor grab
|
|
47
|
+
user-select none
|
|
48
|
+
|
|
49
|
+
.sortlist .row[data-wd-dragging]
|
|
50
|
+
opacity .6
|
|
51
|
+
cursor grabbing
|
|
52
|
+
|
|
53
|
+
.sortlist .row:focus-visible
|
|
54
|
+
outline 2px solid $accent
|
|
55
|
+
outline-offset 2px
|
|
56
|
+
|
|
57
|
+
.grip
|
|
58
|
+
color $muted
|
|
59
|
+
font-size 1.1rem
|
|
60
|
+
|
|
61
|
+
.wd-carousel
|
|
62
|
+
position relative
|
|
63
|
+
margin-top .5rem
|
|
64
|
+
|
|
65
|
+
.wd-carousel-track
|
|
66
|
+
display flex
|
|
67
|
+
overflow-x auto
|
|
68
|
+
scroll-snap-type x mandatory
|
|
69
|
+
scroll-behavior smooth
|
|
70
|
+
gap 1rem
|
|
71
|
+
|
|
72
|
+
.wd-carousel-track .slide
|
|
73
|
+
flex 0 0 100%
|
|
74
|
+
scroll-snap-align start
|
|
75
|
+
border 1px solid $line
|
|
76
|
+
radius 12px
|
|
77
|
+
padding 1.75rem
|
|
78
|
+
background $paper
|
|
79
|
+
|
|
80
|
+
.wd-carousel-prev, .wd-carousel-next
|
|
81
|
+
position absolute
|
|
82
|
+
top 40%
|
|
83
|
+
border 1px solid $line
|
|
84
|
+
background $paper
|
|
85
|
+
color $ink
|
|
86
|
+
radius 999px
|
|
87
|
+
width 2.25rem
|
|
88
|
+
height 2.25rem
|
|
89
|
+
cursor pointer
|
|
90
|
+
|
|
91
|
+
.wd-carousel-prev
|
|
92
|
+
left -.5rem
|
|
93
|
+
|
|
94
|
+
.wd-carousel-next
|
|
95
|
+
right -.5rem
|
|
96
|
+
|
|
97
|
+
.wd-carousel-dots
|
|
98
|
+
display flex
|
|
99
|
+
justify-content center
|
|
100
|
+
gap .5rem
|
|
101
|
+
margin-top .75rem
|
|
102
|
+
|
|
103
|
+
.wd-carousel-dot
|
|
104
|
+
width .6rem
|
|
105
|
+
height .6rem
|
|
106
|
+
padding 0
|
|
107
|
+
border 0
|
|
108
|
+
radius 999px
|
|
109
|
+
background $line
|
|
110
|
+
cursor pointer
|
|
111
|
+
|
|
112
|
+
.wd-carousel-dot[aria-current=true]
|
|
113
|
+
background $accent
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Interactions — sliders, drag & carousels in Markdown
|
|
3
|
+
description: :slider, :sortable, and :carousel — rich interactions that compile to tiny pay-for-what-you-use modules, declared in plain Markdown. Sliders ship zero JS; sortable and carousel each load one small module, only on the pages that use them.
|
|
4
|
+
image: https://darkmown.com/__wd/media/og-showcase.png
|
|
5
|
+
transitions: true
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
@include /nav.wd
|
|
9
|
+
@include /base.wd
|
|
10
|
+
|
|
11
|
+
<main class="wide app-interactions">
|
|
12
|
+
|
|
13
|
+
::: section #interactions
|
|
14
|
+
|
|
15
|
+
::: ix-head
|
|
16
|
+
::: eyebrow .ix-eyebrow
|
|
17
|
+
Pay-for-what-you-use interactions
|
|
18
|
+
:::
|
|
19
|
+
|
|
20
|
+
# *Interactions*
|
|
21
|
+
|
|
22
|
+
::: ix-tagline
|
|
23
|
+
A range slider, drag-to-reorder, and a carousel — all declared in Markdown. The
|
|
24
|
+
slider is compile-time (zero JavaScript). `:sortable` and `:carousel` each load
|
|
25
|
+
one tiny module from `/__wd/behaviors/`, **only** on this page.
|
|
26
|
+
:::
|
|
27
|
+
:::
|
|
28
|
+
|
|
29
|
+
::: card .ix-panel
|
|
30
|
+
## Slider
|
|
31
|
+
|
|
32
|
+
:slider volume = 60 min=0 max=100 step=5
|
|
33
|
+
|
|
34
|
+
::: readout
|
|
35
|
+
Volume: **{ volume }** / 100
|
|
36
|
+
:::
|
|
37
|
+
|
|
38
|
+
::: ix-note
|
|
39
|
+
`:slider` renders a bound range input. Range values coerce to a Number, so
|
|
40
|
+
`{ volume }` and any `:computed` math see `60`, not `"60"`.
|
|
41
|
+
:::
|
|
42
|
+
:::
|
|
43
|
+
|
|
44
|
+
::: card .ix-panel
|
|
45
|
+
## Drag to reorder
|
|
46
|
+
|
|
47
|
+
:store tasks = ["Sketch the idea", "Draft the page", "Review with the team", "Ship it"]
|
|
48
|
+
|
|
49
|
+
::: sortlist
|
|
50
|
+
@loop tasks into t sortable
|
|
51
|
+
::: row
|
|
52
|
+
<span class="grip" aria-hidden="true">⠿</span> { t }
|
|
53
|
+
:::
|
|
54
|
+
@endloop
|
|
55
|
+
:::
|
|
56
|
+
|
|
57
|
+
::: ix-note
|
|
58
|
+
Drag a row to reorder, or focus one and press ↑ / ↓. The order is rewritten on
|
|
59
|
+
the underlying `:store` through `window.wd`, so the keyed loop repaints.
|
|
60
|
+
:::
|
|
61
|
+
:::
|
|
62
|
+
|
|
63
|
+
::: card .ix-panel
|
|
64
|
+
## Carousel
|
|
65
|
+
|
|
66
|
+
:carousel
|
|
67
|
+
::: slide
|
|
68
|
+
### Native swipe
|
|
69
|
+
CSS scroll-snap does the swiping — touch works with zero JavaScript.
|
|
70
|
+
:::
|
|
71
|
+
::: slide
|
|
72
|
+
### Controls, added
|
|
73
|
+
The module adds prev/next buttons, dot navigation, and mouse drag.
|
|
74
|
+
:::
|
|
75
|
+
::: slide
|
|
76
|
+
### Loaded on demand
|
|
77
|
+
`carousel.js` ships only because this page uses `:carousel`.
|
|
78
|
+
:::
|
|
79
|
+
:endcarousel
|
|
80
|
+
:::
|
|
81
|
+
|
|
82
|
+
:::
|
|
83
|
+
|
|
84
|
+
</main>
|
|
85
|
+
|
|
86
|
+
@include /footer.wd
|