@zvndev/darkmown 2.10.1 → 2.12.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 +8 -6
- package/CHANGELOG.md +16 -0
- package/README.md +45 -5
- package/docs/cli.md +20 -0
- package/docs/errors.md +255 -0
- package/docs/spec-alignment.md +11 -0
- package/package.json +2 -1
- package/site/pages/docs/index.wd +10 -5
- package/site/pages/index.wd +1 -0
- package/site/pages/playground/index.js +32 -33
- package/src/builder.js +118 -11
- package/src/catalog.js +8 -0
- package/src/cli.js +49 -4
- package/src/compiler/markdown.js +14 -2
- package/src/compiler/page.js +27 -15
- package/src/compiler.js +1 -1
- package/src/font-subset.js +335 -105
- package/src/mcp-tools.js +354 -0
- package/src/mcp.js +287 -0
- package/src/statics.js +172 -15
- package/types/builder.d.ts +5 -0
- package/types/compiler/page.d.ts +12 -3
- package/types/compiler.d.ts +4 -1
- package/types/font-subset.d.ts +54 -6
- package/types/statics.d.ts +42 -3
package/site/pages/docs/index.wd
CHANGED
|
@@ -391,11 +391,12 @@ tags: [sales, revenue, "q1, q2"]
|
|
|
391
391
|
|
|
392
392
|
`lang:` sets the document language on `<html lang>` per page (`lang: fr`, `lang: pt-BR`, …); it defaults to `en`. Every page also ships a visually-hidden-until-focused **skip-to-content link** targeting the main landmark — your own `<main>` is reused (an existing `id` wins as the target; an id-less one gets `id="main"`), and a page without one is wrapped in `<main id="main">`. Restyle it via `.wd-skip-link` in a skin. All compile-time — static pages stay zero-JS.
|
|
393
393
|
|
|
394
|
-
|
|
394
|
+
More head and publishing keys arrived in 2.8 and 2.11:
|
|
395
395
|
|
|
396
396
|
- **`icon: /path`** sets the favicon verbatim (`<link rel="icon">`, with a `type` for `.svg`/`.png`/`.ico`). Without it, the first of `site/pages/icon.svg`, `icon.png`, or `favicon.ico` wins; without either, the built-in mark stays the fallback.
|
|
397
397
|
- **`noindex: true`** builds the page but removes it from discovery: a robots `noindex,nofollow` meta, plus exclusion from `sitemap.xml`, `rss.xml`, and the generated `llms.txt`/`llms-full.txt`. On a collection entry with a `_schema.wd`, declare it as `noindex: boolean?`.
|
|
398
398
|
- **`skins: [/base.skin]`** (an inline array) loads shared `.skin` files for that page only, resolved like `@include` paths; a missing file, a non-`.skin` file, or a path outside `site/pages`/`site/_` is `[WD017]`. A shared skin whose first line is `scoped` scopes to the whole page body, so keep to one scoped skin per page.
|
|
399
|
+
- **`md_source: true`** (home page only) publishes every page's byte-exact markdown source at `<route>.md` (`/docs/` also answers `/docs.md`, with a `<link rel="alternate" type="text/markdown">` head tag and a `text/markdown` content type). A page opts out with `md_source: false`; drafts never emit, and the sitemap and feeds never list the `.md` files. Off by default.
|
|
399
400
|
|
|
400
401
|
**Cascade order changed in 2.8.** Stylesheet links emit the highlight sheet first (code pages), then include skins, then `skins:` entries in listed order, and **the page's own colocated `.skin` last**, so the page wins ties at equal specificity. Before 2.8 the page's skin came first; a page rule that unknowingly relied on losing to an include rule now wins.
|
|
401
402
|
|
|
@@ -474,13 +475,13 @@ Mark a page `draft: true` and it stays out of production: `darkmown build` leave
|
|
|
474
475
|
|
|
475
476
|
## Instant navigation
|
|
476
477
|
|
|
477
|
-
Add `transitions: true` to a page's frontmatter for **instant, flash-free navigation
|
|
478
|
+
Add `transitions: true` to a page's frontmatter for **instant, flash-free navigation**: zero JavaScript. It emits an opacity-only cross-fade view transition for the page swap (old and new animate simultaneously at 160ms with one shared easing; the earlier directional slide read as a flicker) and a `<script type="speculationrules">` prerender hint, so the browser renders the next same-origin page on hover/pointerdown and the click activates an already-painted page (no white render-gap). It honors `prefers-reduced-motion`, and only same-origin pages that both opt in transition. Off by default; opt out with `transitions: false`, or exclude a single link from prerendering with `{.no-prefetch}`. (Chrome turns prerendering off while DevTools is open, so test the built site with DevTools closed.)
|
|
478
479
|
|
|
479
480
|
## Images
|
|
480
481
|
|
|
481
482
|
You don't size images by hand. Every `<img>` the compiler emits gets its intrinsic `width`/`height` read from the source file (so the page doesn't reflow as images decode), `decoding="async"`, and a priority split — the first image stays eager with `fetchpriority="high"`, the rest `loading="lazy"`. Anything you set yourself wins. The compiler measures, it doesn't resize, so keep source images web-sized.
|
|
482
483
|
|
|
483
|
-
**Responsive variants, generated at build time.** With sharp installed (`npm i -D sharp`, an optional dependency; builds work without it), every raster photo in `site/pages` over 700px wide is resized to 640w and 1280w siblings in `dist/` (only widths below the intrinsic size, never upscaled, EXIF orientation applied), and the Markdown image gains a `srcset` with the original kept as `src`. Export `name@<width>.ext` yourself and your file wins that width verbatim, measured from its own header; generation fills only the widths you left uncovered, and other sizes are a hand export away (the compiler probes 64 through 3840). Skipped silently: small images, `@width` files, hidden or symlinked paths, svg/gif/avif, and shelf images (the generator covers page-colocated images only). Without sharp the build behaves as before plus one `npm i -D sharp` hint per build; a corrupt image or a sharp failure on one file is a hint naming the file, never a build failure. Resizes are cached in `node_modules/.cache/darkmown-images/` by content hash + width + format, so second builds and dev rebuilds do no resizing for unchanged images, and the build summary reports `image variants (N generated, M cached)` when variants materialized. `sizes`
|
|
484
|
+
**Responsive variants, generated at build time.** With sharp installed (`npm i -D sharp`, an optional dependency; builds work without it), every raster photo in `site/pages` over 700px wide is resized to 640w and 1280w siblings in `dist/` (only widths below the intrinsic size, never upscaled, EXIF orientation applied), and the Markdown image gains a `srcset` with the original kept as `src`. Export `name@<width>.ext` yourself and your file wins that width verbatim, measured from its own header; generation fills only the widths you left uncovered, and other sizes are a hand export away (the compiler probes 64 through 3840). Skipped silently: small images, `@width` files, hidden or symlinked paths, svg/gif/avif, and shelf images (the generator covers page-colocated images only). Without sharp the build behaves as before plus one `npm i -D sharp` hint per build; a corrupt image or a sharp failure on one file is a hint naming the file, never a build failure. Resizes are cached in `node_modules/.cache/darkmown-images/` by content hash + width + format, so second builds and dev rebuilds do no resizing for unchanged images, and the build summary reports `image variants (N generated, M cached)` when variants materialized. The build never invents a `sizes` value, but you can set one on the Markdown image itself: `{sizes="(max-width: 640px) 100vw, 640px"}` passes it through onto the `<img>`, composing with the wired `srcset` (raw `<img srcset sizes>` on an `html: true` page remains the escape hatch). Bound (``), remote, `data:`, query/hash, page-relative, and extensionless srcs are never wired, and neither are raw-HTML `<img>` tags. A `.md` page gets the hardening but no srcset.
|
|
484
485
|
|
|
485
486
|
## Static assets
|
|
486
487
|
|
|
@@ -900,7 +901,7 @@ Two CSS at-rules compile as self-contained blocks in a `.skin`, because their in
|
|
|
900
901
|
|
|
901
902
|
`@font-face` takes plain declarations, several blocks per skin allowed. `@keyframes <name>` requires a plain identifier; each indented child is a keyframe selector (`from`, `to`, a percentage, or a comma list) with its declarations one level deeper, and anything else is a compile error, so a keyframe selector is never misread as a declaration. Aliases and `$token` refs work inside both, both can nest under an `@media` line (the home for `prefers-reduced-motion` animation guards), and both stay **global** in a `scoped` skin, same as `tokens` blocks.
|
|
902
903
|
|
|
903
|
-
**Build-time font subsetting.** With the wasm toolchain installed (`npm i -D harfbuzzjs wawoff2`, both optional dependencies), a post-pass subsets every project-local `.ttf`/`.otf`/`.woff` referenced by an `@font-face src url(...)` to the site's character set (every code point in the emitted HTML, plus a base set of ASCII, Latin-1, punctuation, arrows, and ★ ☆ ✓ ✕ ✗), encodes it as woff2, emits `<name>-subset.woff2` beside the original, and rewrites the dist CSS so `src:` lists the subset first; the source `.skin` is never touched, and layout features and variable axes survive. Without the toolchain the build is unchanged plus one hint per build, a font that fails to parse ships as-is with its own hint, and the build never fails over a font. Subsets cache in `node_modules/.cache/darkmown-fonts/` keyed by font and charset hashes, and the CSS rewrite is idempotent, so incremental dev builds are safe. The build summary reports `fonts (3 subsetted, 0 cached)` when the post-pass did work.
|
|
904
|
+
**Build-time font subsetting.** With the wasm toolchain installed (`npm i -D harfbuzzjs wawoff2`, both optional dependencies), a post-pass subsets every project-local `.ttf`/`.otf`/`.woff` referenced by an `@font-face src url(...)` to the site's character set (every code point in the emitted HTML, plus a base set of ASCII, Latin-1, punctuation, arrows, and ★ ☆ ✓ ✕ ✗), encodes it as woff2, emits `<name>-subset.woff2` beside the original, and rewrites the dist CSS so `src:` lists the subset first; the source `.skin` is never touched, and layout features and variable axes survive. Without the toolchain the build is unchanged plus one hint per build, a font that fails to parse ships as-is with its own hint, and the build never fails over a font. Subsets cache in `node_modules/.cache/darkmown-fonts/` keyed by font and charset hashes, and the CSS rewrite is idempotent, so incremental dev builds are safe. The build summary reports `fonts (3 subsetted, 0 cached)` when the post-pass did work. The same pass also injects one `<link rel="preload" as="font" crossorigin>` per project font into every page whose own stylesheets declare it (leading with the subset woff2 when subsetting ran, else the original), so first paint skips the fallback-font swap.
|
|
904
905
|
|
|
905
906
|
## Inline attributes
|
|
906
907
|
|
|
@@ -910,7 +911,7 @@ A trailing `{.class .class #id}` attaches classes or an id to the inline element
|
|
|
910
911
|
[Get started](/start/){.btn .lg}
|
|
911
912
|
```
|
|
912
913
|
|
|
913
|
-
The block must follow the element with no space. It works on links, images, and emphasis, and it does **not** attach to inline code. It never collides with `{ name }` interpolation, because an interpolation always starts with a name, never a
|
|
914
|
+
The block must follow the element with no space. It works on links, images, and emphasis, and it does **not** attach to inline code. It never collides with `{ name }` interpolation, because an interpolation always starts with a name, never a `.`, `#`, or `sizes=`. On an image the block also accepts `sizes="…"` (double-quoted, media conditions with spaces and commas fine), passed through HTML-escaped alongside the classes and id, with or without a wired `srcset`; on a link or emphasis the token is ignored. This page uses it: a link marked `{.no-prefetch}` is excluded from the `transitions: true` prerender hint.
|
|
914
915
|
|
|
915
916
|
**Consecutive raw-HTML lines collapse into one paragraph.** On an `html: true` page, a run of lines that each start with an inline-level or unknown tag (`<a class="card">`, `<picture>`, a custom element) parses as ONE paragraph, so the elements render inline inside a single `<p>` instead of as layout siblings; a card grid collapses to one column this way. Lines starting with a block-level tag (`<div>`, `<section>`, …) never hit this, because CommonMark hoists them into an HTML block. The compiler warns once per run when 2+ consecutive lines of one paragraph each open with a component-looking tag (one carrying a `class` or `id`; classless prose links stay quiet):
|
|
916
917
|
|
|
@@ -1021,6 +1022,10 @@ session.call("apply", { edits: [{ op: "replace", symbol: "state:count", text: ":
|
|
|
1021
1022
|
session.call("validate", {});
|
|
1022
1023
|
```
|
|
1023
1024
|
|
|
1025
|
+
### MCP server
|
|
1026
|
+
|
|
1027
|
+
`darkmown mcp` runs a hand-rolled, zero-dependency MCP server on stdio (protocol 2024-11-05), so an MCP-capable agent gets the language as tools instead of shelling out: `catalog`, `grammar`, `validate` (compile-check `.wd` source in memory with coded errors), `error` (a WD code's cause, fix, and example), and `doc` (fuzzy section search over the shipped README/AGENTS.md), plus the shipped docs as `darkmown://docs/{readme,agents,changelog,errors,cli}` resources. stdout is protocol frames only. Point a client at it with `{ "mcpServers": { "darkmown": { "command": "npx", "args": ["-y", "@zvndev/darkmown", "mcp"] } } }`.
|
|
1028
|
+
|
|
1024
1029
|
## Compile error codes
|
|
1025
1030
|
|
|
1026
1031
|
Every author-facing compile error opens with a stable `WDxxx` code, so it is searchable and matchable by tooling without parsing prose:
|
package/site/pages/index.wd
CHANGED
|
@@ -3,6 +3,7 @@ title: Darkmown: Markdown that runs
|
|
|
3
3
|
description: A .md file is a page. Rename it to .wd and the same file gets loops, state, fetched data, and forms, with no components, no JSX, and no build step. Pages with no state ship zero framework JavaScript.
|
|
4
4
|
image: https://darkmown.com/__wd/media/og.png
|
|
5
5
|
site_url: https://darkmown.com
|
|
6
|
+
md_source: true
|
|
6
7
|
transitions: true
|
|
7
8
|
schema: WebSite
|
|
8
9
|
---
|