@zvndev/darkmown 2.5.1-next.10.g0f7c9b8 → 2.6.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 +9 -0
- package/README.md +23 -8
- package/docs/spec-alignment.md +1 -1
- package/package.json +1 -1
- package/site/pages/docs/index.wd +3 -1
- package/site/pages/store.wd +21 -0
- package/src/catalog.js +5 -4
- package/src/compiler/interpolation.js +2 -1
- package/src/compiler/state.js +59 -11
- package/src/errors.js +10 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
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
|
+
## 2.6.0 (2026-08-18)
|
|
6
|
+
|
|
7
|
+
A correctness release for the persistence surface, prompted by a report from outside the framework: the on-device planner research drives the real compiler as a ground-truth oracle, and while chasing a case where its checker accepted an edit the compiled page rejected, it found that Darkmown had two vocabularies for one concept and silently corrupted data when you picked the wrong one. No runtime change (7654 B gzipped, unchanged) and no breaking changes.
|
|
8
|
+
|
|
9
|
+
- **BUGFIX: a persistence token on the wrong keyword was folded into the value.** `:store count = 0 persist` compiled clean and seeded the **string** `"0 persist"`. So did `:state count = 0 ephemeral`. Each handler stripped only its own token, so the other one stayed glued to the value and `parseStateValue`'s bare-string fallback (the one that makes `:state title = Hello world` work) turned a typo into data. Nothing failed at compile time, at load, or at first render; the page broke later, on the first `count++`, far from the line that caused it. That is the one failure mode a compile-error repair loop cannot see, which is why this is fixed as a language change rather than a new error message: the author wrote something unambiguous, so the compiler now honours it.
|
|
10
|
+
- **`persist` and `ephemeral` are one vocabulary, on every keyword that has a value.** `persist` means "survives a reload" and `ephemeral` means "does not", and both are now accepted on `:state`, `:store`, and `:theme`. The keyword still picks the default (`:state` is ephemeral, `:store` and `:theme` persist); the token, when present, always means what it says. Writing the token that already matches the default is redundant but never wrong, so `:store cart = [] persist` compiles and states its intent on the line itself. Nothing that compiled before compiles differently, except the two corrupting forms above, which now do what they say. A value that genuinely ends in one of these words is quoted, the same escape hatch `:state` has always had.
|
|
11
|
+
- **BUGFIX: `:computed … persist` reported a symbol the author never wrote.** The token was swallowed into the expression and surfaced as `[WD234] :computed references unknown state "persist"`, which names a phantom and points the fix exactly the wrong way. An agent reading that message will declare a state called `persist`, which compiles and is nonsense. It is now `WD211`, which names the real mistake: computed values are derived rather than stored, so persist the state they derive from instead. A page that genuinely declares state by that name still compiles as an ordinary reference, because the check is the same lookup the expression walker does.
|
|
12
|
+
- **`WD004` now says where the persistence token goes on a multi-line literal.** It belongs after the closing bracket on the last line, not on the declaration line. That already worked and was documented nowhere, and guessing it wrong is what produced the unbalanced-literal error in the first place. The message now closes the loop unaided, and the `:state`/`:store` docs say it outright.
|
|
13
|
+
|
|
5
14
|
## 2.5.0 (2026-08-16)
|
|
6
15
|
|
|
7
16
|
Everything Darkmown has shipped for AI authoring so far has been *description*: a directive catalog, a cheatsheet, a corpus, a GBNF grammar. All of it teaches a model to write a `.wd` file from scratch. None of it helps a model edit one it did not write, which is the operation an AI editor actually performs. This release adds the other half: a tool surface the model calls instead of rewriting whole files. No runtime change (7654 B gzipped, unchanged) and no breaking changes.
|
package/README.md
CHANGED
|
@@ -774,15 +774,30 @@ You have { cart } items.
|
|
|
774
774
|
|
|
775
775
|
The literal must balance with no blank line inside it (a blank line ends the value); an unterminated literal is a compile error. Quote genuinely literal bracket text — `:state tag = "[draft]"`.
|
|
776
776
|
|
|
777
|
+
A `persist`/`ephemeral` token goes **after the closing bracket on the last line**, not on the declaration line:
|
|
778
|
+
|
|
779
|
+
```wd
|
|
780
|
+
:state products = [
|
|
781
|
+
{"name": "Mug"}
|
|
782
|
+
] persist
|
|
783
|
+
```
|
|
784
|
+
|
|
777
785
|
The declared value is a **seed**: it is used only the first time, when the store is absent from storage. After that the persisted value wins, so visitors keep their data.
|
|
778
786
|
|
|
779
|
-
###
|
|
787
|
+
### Choosing persistence
|
|
780
788
|
|
|
781
|
-
|
|
789
|
+
`persist` means "survives a reload" and `ephemeral` means "does not". Both are accepted on `:state`, `:store`, and `:theme`; the keyword only picks the default:
|
|
782
790
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
791
|
+
| Declaration | Persisted? |
|
|
792
|
+
| --- | --- |
|
|
793
|
+
| `:state count = 0` | no (the default) |
|
|
794
|
+
| `:state count = 0 persist` | yes, in localStorage for this page |
|
|
795
|
+
| `:store cart = []` | yes (the default), and synced across tabs |
|
|
796
|
+
| `:store sidebarOpen = false ephemeral` | no, in-memory for the session |
|
|
797
|
+
|
|
798
|
+
Writing the token that matches the default is redundant but never wrong, so `:store cart = [] persist` compiles and means exactly what it says. If a value genuinely ends in one of these words, quote it: `:state note = "0 persist"` seeds the string.
|
|
799
|
+
|
|
800
|
+
`:computed` takes neither: computed values are derived rather than stored, so persist the state they derive from instead (that mistake is a compile error, `WD211`).
|
|
786
801
|
|
|
787
802
|
> **Pitfall:** A store name must be unique. Declaring the same name as both a `:store` and a `:state` on one page is a compile error.
|
|
788
803
|
|
|
@@ -1066,8 +1081,8 @@ import { compileFromMemory } from "@zvndev/darkmown";
|
|
|
1066
1081
|
try {
|
|
1067
1082
|
compileFromMemory({ "site/pages/index.wd": ":state x" }, "site/pages/index.wd");
|
|
1068
1083
|
} catch (err) {
|
|
1069
|
-
err.message; // "[WD201] Malformed :state in …/index.wd:1: :state x. Use: :state name = value [persist] — e.g. :state count = 0"
|
|
1070
|
-
err.wd; // { code: "WD201", file: "…/index.wd", line: 1, hint: ":state name = value [persist] — e.g. …", example: ":state count = 0" }
|
|
1084
|
+
err.message; // "[WD201] Malformed :state in …/index.wd:1: :state x. Use: :state name = value [persist|ephemeral] — e.g. :state count = 0"
|
|
1085
|
+
err.wd; // { code: "WD201", file: "…/index.wd", line: 1, hint: ":state name = value [persist|ephemeral] — e.g. …", example: ":state count = 0" }
|
|
1071
1086
|
}
|
|
1072
1087
|
```
|
|
1073
1088
|
|
|
@@ -1079,7 +1094,7 @@ Every author-facing compile error opens with a stable `WDxxx` code, so it is sea
|
|
|
1079
1094
|
|
|
1080
1095
|
```
|
|
1081
1096
|
[WD201] Malformed :state in site/pages/index.wd:1: :state x.
|
|
1082
|
-
Use: :state name = value [persist] — e.g. :state count = 0
|
|
1097
|
+
Use: :state name = value [persist|ephemeral] — e.g. :state count = 0
|
|
1083
1098
|
```
|
|
1084
1099
|
|
|
1085
1100
|
The full list lives in [`docs/errors.md`](docs/errors.md) — code, cause, fix, and a compilable example each. Codes are grouped by subsystem:
|
package/docs/spec-alignment.md
CHANGED
|
@@ -63,7 +63,7 @@ The 1.3.0 cycle shipped the crawler *files* (sitemap, rss, robots). This closes
|
|
|
63
63
|
|
|
64
64
|
- **`:fetch name from "url"`:** declarative data loading into reactive state, with `name_error` for failures. Shelf `.json` publishes to `/__wd/data/` so it works on static hosts.
|
|
65
65
|
- **`:form` two ways:** `:form into name` captures submits into client state with zero backend; `:form action="/url"` emits a plain native form and ships no JS — the progressive-enhancement story from the original doc.
|
|
66
|
-
- **`:state x = v persist`:** localStorage-backed state. This is the honest client-side slice of the original `:cart` primitive; server sync remains future work.
|
|
66
|
+
- **`:state x = v persist`:** localStorage-backed state. This is the honest client-side slice of the original `:cart` primitive; server sync remains future work. Since 2.6.0 the persistence vocabulary is one vocabulary rather than two: `persist` and `ephemeral` are both accepted on `:state`, `:store`, and `:theme` with their literal meanings, and the keyword only picks the default. Before that each handler stripped only its own token and the other one folded into the value, so `:store count = 0 persist` silently seeded the string `"0 persist"` and first failed at the next `count++`. `:computed` accepts neither and says so (`WD211`) instead of reporting a phantom unknown state.
|
|
67
67
|
- **`:if item.path` inside reactive loops:** per-row conditional branches, filled at compile time and re-filled by the keyed patcher. Conditionals **nest** — an inner `:if` is resolved after the outer branch, both at build time (balanced-region pre-render) and at runtime (recursive fill).
|
|
68
68
|
- **`window.wd` escape hatch:** `get`/`set`/`state`/`render` exposed to colocated `.js` — the adapted form of the original `<script scope>` "infinite extension" idea, aligned with Darkmown's colocation model instead of inline scripts.
|
|
69
69
|
|
package/package.json
CHANGED
package/site/pages/docs/index.wd
CHANGED
|
@@ -533,7 +533,9 @@ You have { cart } items.
|
|
|
533
533
|
- The declared value is a **seed**, used only the first time — afterward the persisted value wins.
|
|
534
534
|
- Same value grammar and the same button actions as `:state`.
|
|
535
535
|
- An array/object seed may span **multiple lines** for readability — open the `[`/`{` on the directive line and let it run until it closes (no blank line inside; an unterminated literal is a compile error). Quote literal bracket text: `:state tag = "[draft]"`.
|
|
536
|
-
-
|
|
536
|
+
- On a multi-line seed, a `persist`/`ephemeral` token goes **after the closing bracket on the last line**, not on the declaration line.
|
|
537
|
+
- `persist` means "survives a reload", `ephemeral` means "does not", and both are accepted on `:state`, `:store`, and `:theme`. The keyword only picks the default. `:store sidebarOpen = false ephemeral` is an in-memory store; `:state cart = [] persist` is a persisted one. Writing the token that matches the default is redundant but never wrong.
|
|
538
|
+
- `:computed` takes neither: computed values are derived rather than stored, so persist the state they derive from instead (`WD211`).
|
|
537
539
|
|
|
538
540
|
## Forms and persistence
|
|
539
541
|
|
package/site/pages/store.wd
CHANGED
|
@@ -52,4 +52,25 @@ clean.
|
|
|
52
52
|
|
|
53
53
|
Draft says: **{ draft }**
|
|
54
54
|
|
|
55
|
+
## One vocabulary, either keyword
|
|
56
|
+
|
|
57
|
+
`persist` means "survives a reload" and `ephemeral` means "does not". Both are
|
|
58
|
+
accepted on `:state`, `:store`, and `:theme`. The **keyword** only picks the
|
|
59
|
+
default, and the **token** always means what it says. So these two counters
|
|
60
|
+
behave identically, and each one states its intent on the line itself.
|
|
61
|
+
|
|
62
|
+
::: section #twins
|
|
63
|
+
:state kept = 0 persist
|
|
64
|
+
:store also = 0 persist
|
|
65
|
+
|
|
66
|
+
Persisted `:state`: **{ kept }**, persisted `:store`: **{ also }**
|
|
67
|
+
|
|
68
|
+
:button "Count the state" -> kept++
|
|
69
|
+
:button "Count the store" -> also++
|
|
70
|
+
|
|
71
|
+
Bump both, reload, and the pair comes back. Writing the token that already
|
|
72
|
+
matches the default is redundant but never wrong, which is the point: a
|
|
73
|
+
declaration you can read without remembering which keyword defaults which way.
|
|
74
|
+
:::
|
|
75
|
+
|
|
55
76
|
</main>
|
package/src/catalog.js
CHANGED
|
@@ -131,16 +131,17 @@ const DIRECTIVES = [
|
|
|
131
131
|
{
|
|
132
132
|
name: ":state",
|
|
133
133
|
kind: "line",
|
|
134
|
-
syntax: ":state name = value [persist]",
|
|
135
|
-
description:
|
|
134
|
+
syntax: ":state name = value [persist|ephemeral]",
|
|
135
|
+
description:
|
|
136
|
+
"Declare page-scoped reactive state. Ephemeral by default; persist keeps it across reloads.",
|
|
136
137
|
example: STATE_EXAMPLE,
|
|
137
138
|
reactive: "reactive"
|
|
138
139
|
},
|
|
139
140
|
{
|
|
140
141
|
name: ":store",
|
|
141
142
|
kind: "line",
|
|
142
|
-
syntax: ":store name = value [ephemeral]",
|
|
143
|
-
description: "Declare a page-global store
|
|
143
|
+
syntax: ":store name = value [persist|ephemeral]",
|
|
144
|
+
description: "Declare a page-global store. Persisted by default; ephemeral opts out.",
|
|
144
145
|
example: STORE_EXAMPLE,
|
|
145
146
|
reactive: "reactive"
|
|
146
147
|
},
|
|
@@ -154,7 +154,8 @@ export function parseStateValue(raw, where) {
|
|
|
154
154
|
`Unbalanced JSON value "${shown}"${where ? ` in ${where}` : ""}: this opens "${open}" but ` +
|
|
155
155
|
`never closes it. :state/:store/:theme accept a multi-line array/object, but the literal ` +
|
|
156
156
|
`must balance (a "${close}" is missing) with no blank lines inside it. ` +
|
|
157
|
-
`
|
|
157
|
+
`A persist/ephemeral token goes AFTER the closing "${close}" on the last line, ` +
|
|
158
|
+
`not on the declaration line. Quote it for literal bracket text (e.g. = "[draft]").`,
|
|
158
159
|
{ code: "WD004" }
|
|
159
160
|
);
|
|
160
161
|
}
|
package/src/compiler/state.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import { at, lineOf, recordSymbol, wdError } from "./context.js";
|
|
12
12
|
import { astOf, evalAst } from "./expr-ast.js";
|
|
13
|
-
import { escapeHtml, parseStateValue, safeScriptJson } from "./interpolation.js";
|
|
13
|
+
import { escapeHtml, parseStateValue, resolveStateKey, safeScriptJson } from "./interpolation.js";
|
|
14
14
|
import { compileComputedExpr } from "./predicates.js";
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -22,7 +22,29 @@ export const STATE_EXAMPLE = ":state count = 0";
|
|
|
22
22
|
export const STORE_EXAMPLE = ":store cart = []";
|
|
23
23
|
export const COMPUTED_EXAMPLE = ":computed total = items.length * 4";
|
|
24
24
|
export const THEME_EXAMPLE = ":theme";
|
|
25
|
-
const STATE_USE = `Use: :state name = value [persist] — e.g. ${STATE_EXAMPLE}`;
|
|
25
|
+
const STATE_USE = `Use: :state name = value [persist|ephemeral] — e.g. ${STATE_EXAMPLE}`;
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// PERSISTENCE IS ONE VOCABULARY, NOT TWO.
|
|
29
|
+
//
|
|
30
|
+
// `persist` means "survives a reload" and `ephemeral` means "does not", on every
|
|
31
|
+
// keyword that has a value. The keyword picks the DEFAULT — `:state` is
|
|
32
|
+
// ephemeral, `:store` and `:theme` persist — and the token, when present, always
|
|
33
|
+
// means what it says.
|
|
34
|
+
//
|
|
35
|
+
// It did not always. Each handler used to strip only its own token, so the other
|
|
36
|
+
// one stayed glued to the value and `parseStateValue`'s bare-string fallback
|
|
37
|
+
// turned it into data: `:store count = 0 persist` seeded the STRING "0 persist",
|
|
38
|
+
// compiled green, and first failed later at `count++`, far from the line that
|
|
39
|
+
// caused it. That is the one failure mode a compile-error repair loop cannot see,
|
|
40
|
+
// which is why this is a vocabulary change and not a new error message: the
|
|
41
|
+
// author wrote something unambiguous, so the compiler honours it instead of
|
|
42
|
+
// asking again.
|
|
43
|
+
//
|
|
44
|
+
// Text that genuinely ends in one of these words still quotes to keep it, the
|
|
45
|
+
// same escape hatch `:state` has always had.
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
const PERSISTENCE = String.raw`(?:\s+(persist|ephemeral))?`;
|
|
26
48
|
|
|
27
49
|
/**
|
|
28
50
|
* @param {string} line
|
|
@@ -31,7 +53,9 @@ const STATE_USE = `Use: :state name = value [persist] — e.g. ${STATE_EXAMPLE}`
|
|
|
31
53
|
* @returns {string}
|
|
32
54
|
*/
|
|
33
55
|
export function handleState(line, ctx, index) {
|
|
34
|
-
const match = line.match(
|
|
56
|
+
const match = line.match(
|
|
57
|
+
new RegExp(String.raw`^:state\s+([A-Za-z_$][\w$]*)\s*=\s*(.+?)${PERSISTENCE}$`)
|
|
58
|
+
);
|
|
35
59
|
if (!match)
|
|
36
60
|
throw wdError(`Malformed :state in ${at(ctx, index)}: ${line}. ${STATE_USE}`, {
|
|
37
61
|
code: "WD201",
|
|
@@ -43,7 +67,7 @@ export function handleState(line, ctx, index) {
|
|
|
43
67
|
const value = parseStateValue(match[2], at(ctx, index));
|
|
44
68
|
const key = declareState(match[1], value, ctx);
|
|
45
69
|
recordSymbol(ctx, index, { kind: "state", name: key, detail: `${key} = ${match[2].trim()}` });
|
|
46
|
-
const persistAttr = match[3] ? ` data-wd-persist="${key}"` : "";
|
|
70
|
+
const persistAttr = match[3] === "persist" ? ` data-wd-persist="${key}"` : "";
|
|
47
71
|
return `<script type="application/json" data-wd-state${persistAttr}>${safeScriptJson({ [key]: value })}</script>`;
|
|
48
72
|
}
|
|
49
73
|
|
|
@@ -88,22 +112,24 @@ export function declareState(name, value, ctx) {
|
|
|
88
112
|
* @returns {string}
|
|
89
113
|
*/
|
|
90
114
|
export function handleStore(line, ctx, index) {
|
|
91
|
-
const match = line.match(
|
|
115
|
+
const match = line.match(
|
|
116
|
+
new RegExp(String.raw`^:store\s+([A-Za-z_$][\w$]*)\s*=\s*(.+?)${PERSISTENCE}$`)
|
|
117
|
+
);
|
|
92
118
|
if (!match)
|
|
93
119
|
throw wdError(
|
|
94
|
-
`Malformed :store in ${at(ctx, index)}: ${line}. Use: :store name = value [ephemeral] — e.g. ${STORE_EXAMPLE}`,
|
|
120
|
+
`Malformed :store in ${at(ctx, index)}: ${line}. Use: :store name = value [persist|ephemeral] — e.g. ${STORE_EXAMPLE}`,
|
|
95
121
|
{
|
|
96
122
|
code: "WD205",
|
|
97
123
|
file: ctx.file,
|
|
98
124
|
line: lineOf(ctx, index),
|
|
99
|
-
hint: `:store name = value [ephemeral] — e.g. ${STORE_EXAMPLE}`,
|
|
125
|
+
hint: `:store name = value [persist|ephemeral] — e.g. ${STORE_EXAMPLE}`,
|
|
100
126
|
example: STORE_EXAMPLE
|
|
101
127
|
}
|
|
102
128
|
);
|
|
103
129
|
const value = parseStateValue(match[2], at(ctx, index));
|
|
104
130
|
const name = declareStore(match[1], value, ctx);
|
|
105
131
|
recordSymbol(ctx, index, { kind: "store", name, detail: `${name} = ${match[2].trim()}` });
|
|
106
|
-
const ephemeral = match[3] ? " data-wd-store-ephemeral" : "";
|
|
132
|
+
const ephemeral = match[3] === "ephemeral" ? " data-wd-store-ephemeral" : "";
|
|
107
133
|
return `<script type="application/json" data-wd-store="${name}"${ephemeral}>${safeScriptJson(value)}</script>`;
|
|
108
134
|
}
|
|
109
135
|
|
|
@@ -171,7 +197,26 @@ export function handleComputed(line, ctx, index) {
|
|
|
171
197
|
hint: COMPUTED_EXAMPLE,
|
|
172
198
|
example: COMPUTED_EXAMPLE
|
|
173
199
|
});
|
|
174
|
-
const
|
|
200
|
+
const raw = match[2].trim();
|
|
201
|
+
// A trailing bare `persist`/`ephemeral` used to be swallowed into the
|
|
202
|
+
// expression and surface as WD234 "references unknown state \"persist\"",
|
|
203
|
+
// which names something the author never wrote and points the fix the wrong
|
|
204
|
+
// way: a repair loop reading it will declare a state called `persist`, which
|
|
205
|
+
// compiles and is nonsense. `resolveStateKey` is the same lookup the
|
|
206
|
+
// expression walker does, so a page that really does declare state by that
|
|
207
|
+
// name still compiles as an ordinary reference.
|
|
208
|
+
const stray = raw.match(/\s+(persist|ephemeral)$/);
|
|
209
|
+
if (stray && !resolveStateKey(stray[1], ctx)) {
|
|
210
|
+
const hint = `:computed values are derived, not stored, so they cannot ${stray[1] === "persist" ? "persist" : "be ephemeral"}. Persist the state they derive from instead — e.g. :state count = 0 persist`;
|
|
211
|
+
throw wdError(`Persistence token on :computed in ${at(ctx, index)}: ${line}. ${hint}`, {
|
|
212
|
+
code: "WD211",
|
|
213
|
+
file: ctx.file,
|
|
214
|
+
line: lineOf(ctx, index),
|
|
215
|
+
hint,
|
|
216
|
+
example: COMPUTED_EXAMPLE
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
const expr = compileComputedExpr(raw, ctx);
|
|
175
220
|
// Parse the validated expression to the compact AST the runtime walks. A parse
|
|
176
221
|
// failure means the RHS is syntactically malformed (e.g. `|| <`) — a compile
|
|
177
222
|
// error, not a silently-broken binding.
|
|
@@ -214,7 +259,9 @@ export function handleComputed(line, ctx, index) {
|
|
|
214
259
|
* @returns {string}
|
|
215
260
|
*/
|
|
216
261
|
export function handleTheme(line, ctx, index) {
|
|
217
|
-
const match = line.match(
|
|
262
|
+
const match = line.match(
|
|
263
|
+
new RegExp(String.raw`^:theme(?:\s+([A-Za-z_$][\w$]*))?(?:\s*=\s*(.+?))?${PERSISTENCE}$`)
|
|
264
|
+
);
|
|
218
265
|
if (!match) {
|
|
219
266
|
throw wdError(
|
|
220
267
|
`Malformed :theme in ${at(ctx, index)}: ${line}. Use: :theme (or :theme name = "auto") — e.g. ${THEME_EXAMPLE}`,
|
|
@@ -235,5 +282,6 @@ export function handleTheme(line, ctx, index) {
|
|
|
235
282
|
name: storeName,
|
|
236
283
|
detail: `${storeName} = ${JSON.stringify(seed)}`
|
|
237
284
|
});
|
|
238
|
-
|
|
285
|
+
const ephemeral = match[3] === "ephemeral" ? " data-wd-store-ephemeral" : "";
|
|
286
|
+
return `<script type="application/json" data-wd-store="${storeName}"${ephemeral}>${safeScriptJson(seed)}</script><span data-wd-theme="${storeName}" hidden></span>`;
|
|
239
287
|
}
|
package/src/errors.js
CHANGED
|
@@ -367,7 +367,7 @@ const ENTRIES = [
|
|
|
367
367
|
{
|
|
368
368
|
code: "WD201",
|
|
369
369
|
title: "Malformed `:state`",
|
|
370
|
-
cause: "The line does not read `:state name = value [persist]`.",
|
|
370
|
+
cause: "The line does not read `:state name = value [persist|ephemeral]`.",
|
|
371
371
|
fix: "Give the state a name and an initial value.",
|
|
372
372
|
example: STATE_EXAMPLE
|
|
373
373
|
},
|
|
@@ -393,7 +393,7 @@ const ENTRIES = [
|
|
|
393
393
|
{
|
|
394
394
|
code: "WD205",
|
|
395
395
|
title: "Malformed `:store`",
|
|
396
|
-
cause: "The line does not read `:store name = value [ephemeral]`.",
|
|
396
|
+
cause: "The line does not read `:store name = value [persist|ephemeral]`.",
|
|
397
397
|
fix: "Give the store a name and an initial value.",
|
|
398
398
|
example: STORE_EXAMPLE
|
|
399
399
|
},
|
|
@@ -432,6 +432,13 @@ const ENTRIES = [
|
|
|
432
432
|
fix: "Use a bare `:theme`, or name the store and seed it.",
|
|
433
433
|
example: THEME_EXAMPLE
|
|
434
434
|
},
|
|
435
|
+
{
|
|
436
|
+
code: "WD211",
|
|
437
|
+
title: "Persistence token on `:computed`",
|
|
438
|
+
cause: "The expression ends in a bare `persist` or `ephemeral`, which is swallowed into it.",
|
|
439
|
+
fix: "Computed values are derived, not stored. Persist the state they derive from instead.",
|
|
440
|
+
example: COMPUTED_EXAMPLE
|
|
441
|
+
},
|
|
435
442
|
{
|
|
436
443
|
code: "WD220",
|
|
437
444
|
title: "Malformed `where` condition",
|
|
@@ -1109,7 +1116,7 @@ export function errorsMarkdown() {
|
|
|
1109
1116
|
out.push("");
|
|
1110
1117
|
out.push("```");
|
|
1111
1118
|
out.push("[WD201] Malformed :state in site/pages/index.wd:1: :state x.");
|
|
1112
|
-
out.push(" Use: :state name = value [persist] — e.g. :state count = 0");
|
|
1119
|
+
out.push(" Use: :state name = value [persist|ephemeral] — e.g. :state count = 0");
|
|
1113
1120
|
out.push("```");
|
|
1114
1121
|
out.push("");
|
|
1115
1122
|
out.push(
|