@zvndev/darkmown 2.6.1-next.11.g82bb8d7 → 2.7.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.
Files changed (73) hide show
  1. package/AGENTS.md +392 -77
  2. package/CHANGELOG.md +47 -0
  3. package/README.md +316 -49
  4. package/docs/cli.md +51 -17
  5. package/docs/spec-alignment.md +25 -4
  6. package/grammar/wd-directives.gbnf +13 -5
  7. package/package.json +7 -3
  8. package/site/_/base.skin +1 -1
  9. package/site/_/footer.wd +1 -1
  10. package/site/pages/api-errors/index.wd +81 -0
  11. package/site/pages/app.wd +1 -1
  12. package/site/pages/attr-binding/index.wd +84 -0
  13. package/site/pages/blog/darkmown-2-7-0.md +176 -0
  14. package/site/pages/blog/darkmown-2-7-0.skin +127 -0
  15. package/site/pages/blog/no-unsafe-eval.md +66 -0
  16. package/site/pages/blog/no-unsafe-eval.skin +127 -0
  17. package/site/pages/blog/zero-js-by-default.md +1 -1
  18. package/site/pages/branch-state/index.wd +68 -0
  19. package/site/pages/docs/index.wd +320 -19
  20. package/site/pages/index.wd +3 -1
  21. package/site/pages/playground/index.js +63 -56
  22. package/site/pages/pulse/index.wd +1 -1
  23. package/site/pages/settings/index.wd +97 -0
  24. package/site/pages/showcase/index.wd +37 -1
  25. package/site/pages/upload/index.wd +63 -0
  26. package/site/pages/url-state/index.wd +70 -0
  27. package/src/builder.js +222 -34
  28. package/src/catalog.js +217 -21
  29. package/src/cli.js +6 -3
  30. package/src/compiler/actions.js +80 -12
  31. package/src/compiler/body.js +104 -28
  32. package/src/compiler/collections.js +29 -13
  33. package/src/compiler/context.js +9 -2
  34. package/src/compiler/expr-ast.js +14 -0
  35. package/src/compiler/fetch.js +8 -4
  36. package/src/compiler/forms.js +133 -14
  37. package/src/compiler/frontmatter.js +28 -7
  38. package/src/compiler/includes.js +46 -6
  39. package/src/compiler/interpolation.js +56 -1
  40. package/src/compiler/loops.js +489 -35
  41. package/src/compiler/markdown.js +457 -44
  42. package/src/compiler/page.js +8 -3
  43. package/src/compiler/predicates.js +84 -14
  44. package/src/compiler/state.js +167 -18
  45. package/src/compiler/structure.js +159 -29
  46. package/src/errors.js +111 -3
  47. package/src/feeds.js +140 -7
  48. package/src/grammar.js +53 -10
  49. package/src/runtime.js +509 -130
  50. package/src/runtime.min.js +2 -0
  51. package/src/runtime.min.js.map +1 -0
  52. package/src/statics.js +46 -2
  53. package/src/templates/blog/README.md +2 -0
  54. package/src/templates/dashboard/README.md +2 -0
  55. package/src/templates/landing/README.md +2 -0
  56. package/src/templates/starter/README.md +1 -1
  57. package/src/templates/store/README.md +2 -0
  58. package/src/tools/validate.js +14 -4
  59. package/types/builder.d.ts +6 -2
  60. package/types/catalog.d.ts +12 -0
  61. package/types/compiler/actions.d.ts +1 -1
  62. package/types/compiler/body.d.ts +0 -3
  63. package/types/compiler/forms.d.ts +12 -1
  64. package/types/compiler/frontmatter.d.ts +8 -1
  65. package/types/compiler/includes.d.ts +16 -2
  66. package/types/compiler/interpolation.d.ts +23 -0
  67. package/types/compiler/loops.d.ts +47 -0
  68. package/types/compiler/markdown.d.ts +63 -1
  69. package/types/compiler/predicates.d.ts +7 -1
  70. package/types/compiler/state.d.ts +8 -2
  71. package/types/compiler/structure.d.ts +32 -1
  72. package/types/feeds.d.ts +68 -2
  73. package/types/statics.d.ts +25 -0
package/src/grammar.js CHANGED
@@ -18,7 +18,7 @@
18
18
  // while staying small and maintainable.
19
19
  // ---------------------------------------------------------------------------
20
20
 
21
- import { directiveCatalog } from "./catalog.js";
21
+ import { CATALOG_ACTION_GRAMMAR, directiveCatalog } from "./catalog.js";
22
22
 
23
23
  /** Slugify a directive token into a GBNF rule name (`@loop` → `dir-loop`). */
24
24
  /** @type {Record<string, string>} */
@@ -57,6 +57,19 @@ const STRUCTURED = new Set(["@loop", ":if", ":button", ":::"]);
57
57
  /** A GBNF alternation literal list, e.g. `"a" | "b"`. */
58
58
  const alt = (/** @type {string[]} */ items) => items.map((s) => `"${s}"`).join(" | ");
59
59
 
60
+ /**
61
+ * One alternative of the `action-op` rule, from a catalog action's shape.
62
+ * `name++` (suffix, no space) vs `name toggle [v]` / `name append v` (infix).
63
+ * @param {{ token: string, place: "suffix" | "infix", operand: "none" | "required" | "optional" }} op
64
+ * @returns {string}
65
+ */
66
+ function actionAlternative({ token, place, operand }) {
67
+ if (place === "suffix") return `"${token}"`;
68
+ const tail =
69
+ operand === "required" ? " sp value" : operand === "optional" ? " ( sp value )?" : "";
70
+ return `sp "${token}"${tail}`;
71
+ }
72
+
60
73
  /**
61
74
  * Generate the GBNF grammar for `.wd` directive lines from the directive catalog.
62
75
  * @returns {string}
@@ -72,14 +85,25 @@ export function generateGrammar() {
72
85
  "# Constrains a directive line (or a { path | pipe } interpolation). Do not edit by hand."
73
86
  );
74
87
  lines.push("");
75
- lines.push("root ::= directive | binding");
88
+ // A `.wd` line is an opener, a BLOCK CLOSER (or mid-block marker), or an
89
+ // interpolation. The closers were missing, so constrained decoding could open
90
+ // an `@loop` it was then unable to close.
91
+ lines.push("root ::= directive | closer | binding");
76
92
  lines.push(`directive ::= ${dirRules.join(" | ")}`);
93
+ lines.push(
94
+ 'closer ::= "@endloop" | "@empty" | ":endif" | ":endform" | ":endcarousel" | ":::"' +
95
+ ' | ":else if" sp condition ( sp joiner sp condition )* | ":else if" sp path | ":else"'
96
+ );
77
97
  lines.push("");
78
98
 
79
99
  // Structured directives.
80
100
  lines.push('dir-loop ::= "@loop" sp operand sp "into" sp ident loop-clauses');
81
101
  lines.push('dir-if ::= ":if" sp ( condition (sp joiner sp condition)* | path )');
82
- lines.push('dir-button ::= ":button" sp dqstring sp "->" sp action-chain');
102
+ // The label, then the accessibility attributes the compiler whitelists, then
103
+ // the arrow. Attributes are OPTIONAL and repeatable; the whitelist is spelled
104
+ // out here (rather than left to `restofline`) so a constrained model can emit
105
+ // `role`/`aria-…`/`title` and cannot invent `onclick=` or `class=`.
106
+ lines.push('dir-button ::= ":button" sp dqstring ( sp a11y-attr )* sp "->" sp action-chain');
83
107
  lines.push('dir-container ::= ":::" restofline');
84
108
  lines.push("");
85
109
 
@@ -91,23 +115,37 @@ export function generateGrammar() {
91
115
  lines.push("");
92
116
 
93
117
  // @loop clause vocabulary (mirrors the fixed clause order in the catalog).
118
+ // `sort by`, `offset` and `limit` each accept a REACTIVE form as well as a
119
+ // literal — `sort by { sortKey } { sortDir }` for clickable-header sort, and
120
+ // `limit pageSize` for a state-driven page size. The grammar used to allow
121
+ // only the literal form, which made those shipped features unreachable under
122
+ // constrained decoding.
94
123
  lines.push("loop-clauses ::= ( sp loop-clause )*");
95
124
  lines.push(
96
- 'loop-clause ::= where-clause | "sort by" sp path ( sp ( "asc" | "desc" ) )? | "reverse" | "offset" sp number | "limit" sp number | "paginate" sp number | "sortable"'
125
+ 'loop-clause ::= where-clause | "sort by" sp sort-key ( sp sort-dir )? | "reverse"' +
126
+ ' | "offset" sp count | "limit" sp count | "paginate" sp number | "sortable"'
97
127
  );
128
+ lines.push("sort-key ::= path | statebinding");
129
+ lines.push('sort-dir ::= "asc" | "desc" | statebinding');
130
+ lines.push("count ::= number | ident");
131
+ lines.push('statebinding ::= "{" wsopt ident wsopt "}"');
98
132
  lines.push('where-clause ::= "where" sp condition ( sp joiner sp condition )*');
99
- lines.push("condition ::= operand sp op sp operand");
133
+ // A leading `not` negates one condition (it is a PREFIX, not a joiner — the
134
+ // catalog lists it under predicateJoiners, so it is derived from there rather
135
+ // than filtered away).
136
+ const negate = cat.predicateJoiners.includes("not") ? '( "not" sp )? ' : "";
137
+ lines.push(`condition ::= ${negate}operand sp op sp operand`);
100
138
  lines.push(`op ::= ${alt(cat.predicateOps.map((o) => o.name))}`);
101
139
  lines.push(`joiner ::= ${alt(cat.predicateJoiners.filter((j) => j !== "not"))}`);
102
140
  lines.push("");
103
141
 
104
- // Button-action vocabulary. Each operator token below is asserted present
105
- // against the catalog's action tokens in tests/grammar.test.js.
142
+ // Button-action vocabulary, DERIVED from the catalog's action ops (token +
143
+ // shape) so the grammar cannot list a different set from `llms.txt` and the
144
+ // `WD311` hint. It used to be one hand-written string; that is how `refetch`
145
+ // ended up in the grammar and nowhere else.
106
146
  lines.push('action-chain ::= action ( wsopt ";" wsopt action )*');
107
147
  lines.push("action ::= ident action-op");
108
- lines.push(
109
- 'action-op ::= "++" | "--" | sp "+=" sp value | sp "-=" sp value | sp "=" sp value | sp "toggle" ( sp value )? | sp "append" sp value | sp "prepend" sp value | sp "remove" sp value | sp "clear" | sp "merge" sp ident | sp "delete" sp value | sp "reset" | sp "refetch"'
110
- );
148
+ lines.push(`action-op ::= ${CATALOG_ACTION_GRAMMAR.map(actionAlternative).join(" | ")}`);
111
149
  lines.push("");
112
150
 
113
151
  // Interpolation binding with format pipes.
@@ -116,6 +154,11 @@ export function generateGrammar() {
116
154
  lines.push(`pipe ::= ${alt(cat.formatPipes.map((p) => p.name))}`);
117
155
  lines.push("");
118
156
 
157
+ // Accessibility attributes (`::: ` headers and `:button` share the whitelist).
158
+ lines.push('a11y-attr ::= a11y-name "=" dqstring');
159
+ lines.push('a11y-name ::= "role" | "title" | "aria-" [a-z] [a-z0-9-]*');
160
+ lines.push("");
161
+
119
162
  // Terminals.
120
163
  lines.push("operand ::= path | dqstring | number");
121
164
  lines.push("value ::= dqstring | number | path");