@weft-js/cli 0.1.0 → 0.1.2

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 (171) hide show
  1. package/README.md +38 -14
  2. package/bin/weft.mjs +8 -1
  3. package/docs/async.md +16 -6
  4. package/docs/browser.md +22 -8
  5. package/docs/config.md +39 -9
  6. package/docs/core.md +30 -0
  7. package/docs/deploy.md +108 -0
  8. package/docs/design.md +12 -3
  9. package/docs/entrypoints.md +79 -0
  10. package/docs/fonts.md +12 -2
  11. package/docs/icons.md +13 -7
  12. package/docs/lists.md +3 -1
  13. package/docs/meta.md +13 -3
  14. package/docs/plugins.md +32 -9
  15. package/docs/props.md +13 -2
  16. package/docs/reactivity.md +26 -0
  17. package/docs/rules.md +19 -12
  18. package/docs/rust.md +47 -17
  19. package/docs/start-short.md +3 -2
  20. package/docs/start.md +226 -52
  21. package/docs/state.md +33 -7
  22. package/docs/style.md +31 -8
  23. package/docs/surface.md +61 -15
  24. package/docs/testing.md +15 -5
  25. package/docs/theme.md +182 -0
  26. package/docs/ui-state.md +19 -6
  27. package/docs/verify.md +13 -0
  28. package/package.json +13 -4
  29. package/src/add/config.mjs +93 -0
  30. package/src/add/errors.mjs +2 -0
  31. package/src/add/setup.mjs +88 -0
  32. package/src/add-rust.mjs +82 -27
  33. package/src/add.mjs +209 -181
  34. package/src/agents-md.mjs +144 -43
  35. package/src/alias.mjs +16 -5
  36. package/src/audit/page.mjs +313 -0
  37. package/src/audit/report.mjs +69 -0
  38. package/src/audit/utils.mjs +5 -0
  39. package/src/audit.mjs +174 -188
  40. package/src/browser/cli.mjs +24 -5
  41. package/src/browser/client.mjs +189 -25
  42. package/src/browser/commands.mjs +166 -51
  43. package/src/catalog/audit.mjs +129 -0
  44. package/src/catalog/auth.mjs +40 -0
  45. package/src/catalog/compiler.mjs +337 -0
  46. package/src/catalog/contracts.mjs +18 -0
  47. package/src/catalog/database.mjs +52 -0
  48. package/src/catalog/deploy.mjs +117 -0
  49. package/src/catalog/design.mjs +159 -0
  50. package/src/catalog/forms.mjs +25 -0
  51. package/src/catalog/hydration.mjs +29 -0
  52. package/src/catalog/icons.mjs +24 -0
  53. package/src/catalog/media.mjs +163 -0
  54. package/src/catalog/metadata.mjs +116 -0
  55. package/src/catalog/plugins.mjs +228 -0
  56. package/src/catalog/realtime.mjs +195 -0
  57. package/src/catalog/rust.mjs +75 -0
  58. package/src/catalog/server-functions.mjs +92 -0
  59. package/src/catalog/state.mjs +39 -0
  60. package/src/catalog/style-groups.mjs +54 -0
  61. package/src/catalog/tests.mjs +127 -0
  62. package/src/catalog/text.mjs +200 -0
  63. package/src/catalog/ui-state.mjs +42 -0
  64. package/src/catalog.mjs +68 -444
  65. package/src/check/runtime.mjs +280 -0
  66. package/src/check.mjs +199 -207
  67. package/src/cli/authoring.mjs +213 -0
  68. package/src/cli/project.mjs +302 -0
  69. package/src/cli.mjs +228 -224
  70. package/src/config.mjs +1 -1
  71. package/src/contributions.mjs +75 -25
  72. package/src/db-layer.mjs +13 -5
  73. package/src/deploy/cdn.mjs +142 -0
  74. package/src/deploy/files.mjs +266 -0
  75. package/src/deploy/fit.mjs +166 -0
  76. package/src/deploy/hosts.mjs +126 -0
  77. package/src/deploy/index/cli.mjs +20 -0
  78. package/src/deploy/index/environment.mjs +69 -0
  79. package/src/deploy/index/steps.mjs +87 -0
  80. package/src/deploy/index.mjs +387 -0
  81. package/src/deploy/needs.mjs +149 -0
  82. package/src/design-checks/accessible-names.mjs +62 -0
  83. package/src/design-checks/contrast.mjs +43 -0
  84. package/src/design-checks/csp.mjs +60 -0
  85. package/src/design-checks/files.mjs +59 -0
  86. package/src/design-checks/heading-links.mjs +61 -0
  87. package/src/design-checks/interaction.mjs +65 -0
  88. package/src/design-checks/jsx.mjs +67 -0
  89. package/src/design-checks/layout-values.mjs +68 -0
  90. package/src/design-checks/native-controls.mjs +48 -0
  91. package/src/design-checks/outline.mjs +39 -0
  92. package/src/design-checks/reporter.mjs +37 -0
  93. package/src/design-checks/rules.mjs +274 -0
  94. package/src/design-checks/values.mjs +262 -0
  95. package/src/design-checks.mjs +10 -400
  96. package/src/diag.mjs +32 -8
  97. package/src/docs.mjs +138 -42
  98. package/src/doctor.mjs +20 -8
  99. package/src/files.mjs +18 -5
  100. package/src/fix.mjs +10 -2
  101. package/src/gate/after.mjs +40 -0
  102. package/src/gate/before.mjs +54 -0
  103. package/src/gate/classify.mjs +72 -0
  104. package/src/gate/mutation.mjs +209 -0
  105. package/src/gate/resolve-cases.mjs +22 -0
  106. package/src/gate/result.mjs +63 -0
  107. package/src/gate/run.mjs +75 -0
  108. package/src/gate.mjs +104 -202
  109. package/src/infer/box-props.cjs +140 -0
  110. package/src/infer/core-boxes.cjs +46 -0
  111. package/src/infer/derived-writes.cjs +95 -0
  112. package/src/infer/engine/checks.cjs +193 -0
  113. package/src/infer/engine/flow.cjs +293 -0
  114. package/src/infer/engine/report.cjs +158 -0
  115. package/src/infer/engine/utils.cjs +15 -0
  116. package/src/infer/engine.cjs +108 -368
  117. package/src/infer/facts/sites.cjs +114 -0
  118. package/src/infer/facts.cjs +210 -117
  119. package/src/infer/hosts.cjs +69 -22
  120. package/src/infer/lsp.mjs +149 -40
  121. package/src/infer/session.cjs +73 -17
  122. package/src/infer/shadow.cjs +13 -5
  123. package/src/infer/worker.cjs +57 -9
  124. package/src/jsx-scan.mjs +63 -21
  125. package/src/load-config/schema.mjs +281 -0
  126. package/src/load-config.mjs +144 -116
  127. package/src/map.mjs +93 -26
  128. package/src/mcp/browser-tools.mjs +155 -0
  129. package/src/mcp.mjs +147 -69
  130. package/src/meta-table.mjs +43 -8
  131. package/src/mutate.mjs +120 -36
  132. package/src/plugin-checks.mjs +52 -23
  133. package/src/plugins.mjs +125 -39
  134. package/src/project.mjs +170 -79
  135. package/src/props-checks.mjs +24 -6
  136. package/src/props-listener.mjs +27 -9
  137. package/src/react-checks.mjs +35 -4
  138. package/src/server-state-checks.mjs +34 -0
  139. package/src/share-checks.mjs +121 -37
  140. package/src/site.mjs +33 -6
  141. package/src/slots.mjs +104 -19
  142. package/src/start/build.mjs +116 -0
  143. package/src/start/routes.mjs +38 -0
  144. package/src/start.mjs +194 -92
  145. package/src/state-checks/core-state.mjs +223 -0
  146. package/src/state-checks/modules.mjs +17 -0
  147. package/src/state-checks/scan.mjs +232 -0
  148. package/src/state-checks/server-graph.mjs +65 -0
  149. package/src/state-checks.mjs +109 -133
  150. package/src/style/find.mjs +121 -0
  151. package/src/style.mjs +133 -127
  152. package/src/surface-checks.mjs +212 -46
  153. package/src/tailwind.mjs +5 -4
  154. package/src/tasks.mjs +189 -27
  155. package/src/test-rules/context.mjs +109 -0
  156. package/src/test-rules/file-rules.mjs +279 -0
  157. package/src/test-rules/rule-helpers.mjs +244 -0
  158. package/src/test-rules/syntax.mjs +223 -0
  159. package/src/test-rules/test-rules.mjs +127 -0
  160. package/src/testdiff.mjs +94 -32
  161. package/src/tests.mjs +31 -483
  162. package/src/text-checks.mjs +229 -69
  163. package/src/tokens.mjs +12 -4
  164. package/src/trust/scan.mjs +95 -0
  165. package/src/trust.mjs +214 -102
  166. package/src/weft.mjs +41 -9
  167. package/src/workspace.mjs +96 -19
  168. package/ts-plugin/context.js +57 -0
  169. package/ts-plugin/index.js +4 -124
  170. package/ts-plugin/mapping.js +65 -0
  171. package/ts-plugin/proxy.js +144 -0
@@ -0,0 +1,129 @@
1
+ /** audit diagnostic entries in catalog order. */
2
+ export const audit = [
3
+ // The rendered page (`weft audit`, and the end of `weft build`): what Lighthouse, a crawler and a screen reader see
4
+ {
5
+ code: 'L000',
6
+ severity: 'warning',
7
+ by: 'cli',
8
+ catches:
9
+ "The built pages weren't audited: no Chrome or Chromium on this machine, or no prerendered pages",
10
+ fix: 'Install Chrome, or set `WEFT_CHROME`; `weft audit --url <deploy>` audits a running site',
11
+ },
12
+ {
13
+ code: 'L001',
14
+ severity: 'error',
15
+ by: 'cli',
16
+ catches:
17
+ "A control (button, link, field, frame) with no accessible name in the rendered page, at a phone's width or a desktop's: often CSS that hid its only text",
18
+ fix: 'Give it a name: `label` on a warp `Button` or `aria-label` on an element. For a button that shows its text on wide screens, `collapse="phone"` (or `"tablet"`) hides the text there and keeps it as the name; `display: none` takes the name with it.',
19
+ },
20
+ {
21
+ code: 'L002',
22
+ severity: 'error',
23
+ by: 'cli',
24
+ catches: 'An image with no text alternative in the rendered page',
25
+ fix: '`alt` on an `<img>` (`alt=""` if it shows nothing a reader needs), `label` on warp\'s `Icon` and `Image`.',
26
+ },
27
+ {
28
+ code: 'L003',
29
+ severity: 'error',
30
+ by: 'cli',
31
+ catches:
32
+ 'Text under WCAG contrast (4.5:1, or 3:1 when large) against what is actually behind it in the rendered page',
33
+ fix: "Text needs 4.5:1 against what is behind it (3:1 at 24px, or 18.7px bold). Use warp's text roles (`--warp-color-text`, `--warp-color-text-subtle`) on warp surfaces instead of a raw or faded colour.",
34
+ },
35
+ {
36
+ code: 'L004',
37
+ severity: 'error',
38
+ by: 'cli',
39
+ catches:
40
+ 'Another axe-core WCAG A/AA rule the rendered page breaks (the rule Lighthouse runs for Accessibility and Agentic Browsing)',
41
+ fix: "Each finding names the axe rule, its first remedy and the rule's page.",
42
+ },
43
+ {
44
+ code: 'L010',
45
+ severity: 'error',
46
+ by: 'cli',
47
+ catches: 'An uncaught exception or a `console.error` while the page loads',
48
+ fix: 'Fix the error the page throws or logs; `weft browser open <route>` then `weft browser snapshot` shows it with the page.',
49
+ },
50
+ {
51
+ code: 'L011',
52
+ severity: 'error',
53
+ by: 'cli',
54
+ catches: 'The browser logged an error loading the page: usually a missing file (404)',
55
+ fix: 'Usually a file the page asks for that is missing (a 404): fix the URL or ship the file.',
56
+ },
57
+ {
58
+ code: 'L012',
59
+ severity: 'error',
60
+ by: 'cli',
61
+ catches: 'A deprecated browser API in use (Lighthouse Best Practices)',
62
+ fix: "Replace the deprecated API; Chrome's message names what replaces it.",
63
+ },
64
+ {
65
+ code: 'L013',
66
+ severity: 'error',
67
+ by: 'cli',
68
+ catches:
69
+ 'An issue Chrome logs and Lighthouse counts: mixed content, a blocked cookie, a CSP or CORS failure',
70
+ fix: 'Open the page in Chrome and read the Issues panel for the request or element it names.',
71
+ },
72
+ {
73
+ code: 'L020',
74
+ severity: 'error',
75
+ by: 'cli',
76
+ catches: 'A built page with no `<title>`',
77
+ fix: '`meta: { title }` on the route, or `site.title` in weft.config.ts.',
78
+ },
79
+ {
80
+ code: 'L021',
81
+ severity: 'warning',
82
+ by: 'cli',
83
+ catches: 'A built page with no meta description',
84
+ fix: '`meta: { description }` on the route, or `site.description` in weft.config.ts.',
85
+ },
86
+ {
87
+ code: 'L022',
88
+ severity: 'error',
89
+ by: 'cli',
90
+ catches: '`<html>` with no `lang`',
91
+ fix: '`site.locale` in weft.config.ts sets it.',
92
+ },
93
+ {
94
+ code: 'L023',
95
+ severity: 'error',
96
+ by: 'cli',
97
+ catches: 'No `width=device-width` viewport meta: phones render at 980px and shrink the page',
98
+ fix: 'weft writes it; a custom `head` or document must keep it.',
99
+ },
100
+ {
101
+ code: 'L024',
102
+ severity: 'warning',
103
+ by: 'cli',
104
+ catches:
105
+ 'A page told search engines not to index it through markup or HTTP headers; an error when public indexing is expected',
106
+ fix: 'Choose indexing intent with `build.audit.indexing` or `weft audit --indexing public|private`. For public search, remove noindex from both the page and host/proxy, then audit the production URL. Preserve intentional private exclusions.',
107
+ },
108
+ {
109
+ code: 'L025',
110
+ severity: 'error',
111
+ by: 'cli',
112
+ catches: 'A page answered with a 4xx or 5xx status',
113
+ fix: 'The route errors or is missing: run it with `weft dev` and read the server log.',
114
+ },
115
+ {
116
+ code: 'L030',
117
+ severity: 'warning',
118
+ by: 'cli',
119
+ catches: "The page scrolls sideways at a phone's width",
120
+ fix: 'Let the named element shrink: `max-inline-size: 100%`, `min-inline-size: 0` on a flex or grid child, or `overflow-x: auto` on wide content, with `tabindex="0"`, a role and a name so a keyboard can scroll it (warp\'s `Table` does this).',
121
+ },
122
+ {
123
+ code: 'L031',
124
+ severity: 'warning',
125
+ by: 'cli',
126
+ catches: 'The layout moved while the page loaded (CLS over 0.05; an error over 0.1)',
127
+ fix: "Reserve the space before it fills: `width` and `height` on images and video, a fixed `block-size` for what loads late, and fonts through weft's text() plugin (it matches fallback metrics).",
128
+ },
129
+ ]
@@ -0,0 +1,40 @@
1
+ /** auth diagnostic entries in catalog order. */
2
+ export const auth = [
3
+ {
4
+ code: 'A001',
5
+ severity: 'error',
6
+ by: 'start',
7
+ catches: 'The auth secret is missing or under 32 characters on a production server',
8
+ fix: "Set `BETTER_AUTH_SECRET` (`openssl rand -base64 32`) in the server's environment",
9
+ },
10
+ {
11
+ code: 'A002',
12
+ severity: 'error',
13
+ by: 'start',
14
+ catches: "A function with `auth: 'user'` was called with no session (401)",
15
+ fix: 'Sign in first, or guard the route with `redirect()` in its `load`',
16
+ },
17
+ {
18
+ code: 'A003',
19
+ severity: 'error',
20
+ by: 'start',
21
+ catches:
22
+ "`auth: 'user'`, but `ctx` has no `user`: the context file never asked for the session",
23
+ fix: 'Spread `session(auth, request)` into what `src/context.server.ts` returns',
24
+ },
25
+ {
26
+ code: 'A004',
27
+ severity: 'warning',
28
+ by: 'start',
29
+ catches:
30
+ 'A mutation changed the session cookie without a redirect: the tab keeps what the last session cached',
31
+ fix: "`redirect()` after it: that's a full load, which drops the client cache",
32
+ },
33
+ {
34
+ code: 'A005',
35
+ severity: 'error',
36
+ by: 'start',
37
+ catches: 'The database provider has no Better Auth adapter',
38
+ fix: 'Pass `options.database` to `createAuth`, or use a provider that has one',
39
+ },
40
+ ]
@@ -0,0 +1,337 @@
1
+ /** Compiler diagnostic entries in catalog order. */
2
+ export const compilerDiagnostics = [
3
+ {
4
+ code: 'W000',
5
+ severity: 'error',
6
+ by: 'compiler',
7
+ catches: 'Syntax error; nothing else in the file is checked',
8
+ fix: 'Fix the syntax error first',
9
+ },
10
+ {
11
+ code: 'W080',
12
+ severity: 'error',
13
+ by: 'cli',
14
+ catches:
15
+ 'A component with an untyped parameter that nothing renders: its props have nothing to be inferred from',
16
+ fix: 'Render it, delete it, or annotate the parameter',
17
+ },
18
+ {
19
+ code: 'W081',
20
+ severity: 'error',
21
+ by: 'cli',
22
+ catches: 'An exported component with no written prop type',
23
+ fix: '`weft fix` writes the inferred type (`weft dev` does this on save)',
24
+ },
25
+ {
26
+ code: 'W082',
27
+ severity: 'error',
28
+ by: 'cli',
29
+ catches: 'A generated `/*inferred*/` prop type no longer matches the callers',
30
+ fix: '`weft fix` rewrites it, or delete the comment to keep the type you have',
31
+ },
32
+ {
33
+ code: 'W083',
34
+ severity: 'warning',
35
+ by: 'cli',
36
+ catches: 'An `any` weakens an inferred prop; names where the `any` came from',
37
+ fix: 'Give the source a type, or annotate the prop',
38
+ },
39
+ {
40
+ code: 'W084',
41
+ severity: 'error',
42
+ by: 'cli',
43
+ catches: "One caller passes a type the component body can't handle, and the others agree",
44
+ fix: 'Change the caller, or make the component handle both',
45
+ },
46
+ {
47
+ code: 'W085',
48
+ severity: 'warning',
49
+ by: 'cli',
50
+ catches: "An inferred prop names a type the component's scope can't see",
51
+ fix: 'Move the type to module scope or export it, or annotate the parameter',
52
+ },
53
+ {
54
+ code: 'W001',
55
+ severity: 'error',
56
+ by: 'compiler',
57
+ catches:
58
+ 'A prop or `.value` used to compute something in the component body outside `derived`/`watch`/JSX/handlers (the body runs once). `const x = props.y` and destructuring only name the prop and stay live',
59
+ fix: 'Wrap in `derived`, move into `watch`, or `untrack()` if a snapshot is intended',
60
+ example: 'export function A({ items }: any) { const n = items.length + 1; return <p>{n}</p> }',
61
+ },
62
+ {
63
+ code: 'W002',
64
+ severity: 'error',
65
+ by: 'compiler',
66
+ catches: 'Writing state inside `derived`',
67
+ fix: 'Move the write to a handler, or make the target `derived`',
68
+ example:
69
+ "import { state, derived } from '@weft-js/core'\nexport function A() { const a = state(0); const b = state(0); const d = derived(() => { b.value = a.value; return a.value }); return <p>{d.value}</p> }",
70
+ },
71
+ {
72
+ code: 'W003',
73
+ severity: 'error',
74
+ by: 'compiler',
75
+ catches: '`watch` writes state that its own source reads (cycle)',
76
+ fix: 'Use `derived`',
77
+ example:
78
+ "import { state, watch } from '@weft-js/core'\nexport function A() { const a = state(0); watch(() => a.value, v => { a.value = v + 1 }); return <p>{a.value}</p> }",
79
+ },
80
+ {
81
+ code: 'W004',
82
+ severity: 'warning',
83
+ by: 'compiler',
84
+ catches: '`watch` that only copies one value into another',
85
+ fix: 'Replace with `derived`',
86
+ example:
87
+ "import { state, watch } from '@weft-js/core'\nexport function A() { const a = state(0); const b = state(0); watch(() => a.value, v => { b.value = v }); return <p>{b.value}</p> }",
88
+ },
89
+ {
90
+ code: 'W005',
91
+ severity: 'error',
92
+ by: 'compiler',
93
+ catches: "`state(prop)`: React's `useState(prop)` bug, the box never follows the prop",
94
+ fix: '`state.linked(() => prop)`, or `untrack` for a one-time copy',
95
+ example:
96
+ "import { state } from '@weft-js/core'\nexport function A({ v }: any) { const s = state(v); return <p>{s.value}</p> }",
97
+ },
98
+ {
99
+ code: 'W006',
100
+ severity: 'error',
101
+ by: 'compiler',
102
+ catches: "Nested prop destructuring (`({ user: { name } })`) can't stay live",
103
+ fix: 'Read at the use site: `user.name`',
104
+ example: 'export function A({ user: { name } }: any) { return <p>{name}</p> }',
105
+ },
106
+ {
107
+ code: 'W007',
108
+ severity: 'error',
109
+ by: 'cli',
110
+ catches: 'Assigning to a `Derived` (maps TS2540)',
111
+ fix: 'Make it `state` or `state.linked`',
112
+ },
113
+ {
114
+ code: 'W008',
115
+ severity: 'error',
116
+ by: 'cli',
117
+ catches: 'Box/value mismatch at a prop (a `State<T>` where `T` is expected, or the reverse)',
118
+ fix: 'Pass `count.value` / pass `count`',
119
+ },
120
+ {
121
+ code: 'W010',
122
+ severity: 'error',
123
+ by: 'compiler',
124
+ catches: '`.map` over objects without `key`',
125
+ fix: '`key={item.id}` on the row root',
126
+ example:
127
+ 'export function A({ xs }: any) { return <ul>{xs.map((x: any) => <li>{x.name}</li>)}</ul> }',
128
+ },
129
+ {
130
+ code: 'W011',
131
+ severity: 'error',
132
+ by: 'compiler',
133
+ catches: 'A number on the left of `&&` (renders `0`)',
134
+ fix: '`cond > 0 &&`',
135
+ example: 'export function A({ xs }: any) { return <div>{xs.length && <p>has</p>}</div> }',
136
+ },
137
+ {
138
+ code: 'W012',
139
+ severity: 'error',
140
+ by: 'compiler',
141
+ catches: '`derived(async …)`, or reads after `await` in `resource` (untracked)',
142
+ fix: 'Use `resource`; read before the `await`',
143
+ example:
144
+ "import { derived } from '@weft-js/core'\nexport function A() { const d = derived(async () => 1); return <p>{d.value}</p> }",
145
+ },
146
+ {
147
+ code: 'W013',
148
+ severity: 'error',
149
+ by: 'compiler',
150
+ catches: 'Early `return` inside a JSX `.map` callback',
151
+ fix: 'Filter first: `xs.filter(…).map(…)`',
152
+ example:
153
+ 'export function A({ xs }: any) { return <ul>{xs.map((x: any) => { if (!x.ok) return null; return <li key={x.id}>{x.name}</li> })}</ul> }',
154
+ },
155
+ {
156
+ code: 'W014',
157
+ severity: 'error',
158
+ by: 'compiler',
159
+ catches:
160
+ 'Identity-breaking `.map(p => ({ ...p, … }))` feeding a keyed list: every row re-runs on every change (in-file, and across files via props in `weft check`)',
161
+ fix: 'Replace only the changed item (`p.id === id ? { ...p, price } : p`), or read the changing field live',
162
+ example:
163
+ "import { state } from '@weft-js/core'\nexport function A() { const xs = state([{ id: 'a', price: 1 }]); const push = (price: number) => { xs.value = xs.value.map(p => ({ ...p, price })) }; return <ul onClick={() => push(2)}>{xs.value.map(p => <li key={p.id}>{p.price}</li>)}</ul> }",
164
+ },
165
+ {
166
+ code: 'W015',
167
+ severity: 'error',
168
+ by: 'compiler',
169
+ catches: 'A box rendered without `.value` in JSX (a child, or a native attribute)',
170
+ fix: 'Add `.value`',
171
+ example:
172
+ "import { state } from '@weft-js/core'\nexport function A() { const n = state(0); return <p>{n}</p> }",
173
+ },
174
+ {
175
+ code: 'W016',
176
+ severity: 'warning',
177
+ by: 'compiler',
178
+ catches: 'A non-reactive call passed as a component prop runs again whenever the prop is read',
179
+ fix: 'Create the value once before JSX, unless the call must run again',
180
+ example: 'render(() => <App api={createFakeApi()} />, root)',
181
+ },
182
+ {
183
+ code: 'W017',
184
+ severity: 'warning',
185
+ by: 'compiler',
186
+ catches:
187
+ 'One entry of a whole object read per row (`cart[p.id]`, `cart.value[p.id]` in a keyed-list row or its props, `box.value[prop]` in a row component): every change re-runs every row',
188
+ fix: 'Read the entry from the box: `cart.get(p.id)`; to a row component pass the box (`State<Cart>`) and read `.get(id)` there',
189
+ example:
190
+ 'export function A({ xs, cart }: any) { return <ul>{xs.map((x: any) => <li key={x.id}>{cart[x.id]}</li>)}</ul> }',
191
+ },
192
+ {
193
+ code: 'W018',
194
+ severity: 'warning',
195
+ by: 'compiler',
196
+ catches:
197
+ '`const x = untrack(() => …reactive…)` in a component body that only the JSX reads: it renders the setup-time value and never updates',
198
+ fix: '`derived(() => …)` and render `x.value`; `untrack` only for a value meant to stay fixed',
199
+ example:
200
+ "import { untrack, type State } from '@weft-js/core'\nexport function A({ id, live }: { id: string; live: State<Record<string, number>> }) { const n = untrack(() => live.get(id)); return <p>{n}</p> }",
201
+ },
202
+ {
203
+ code: 'W019',
204
+ severity: 'error',
205
+ by: 'compiler',
206
+ catches: 'Unsupported row pattern',
207
+ fix: 'Read from one item param',
208
+ example:
209
+ 'export function L({ xs }) { return <ul>{xs.map(({ id, ...more }) => <li key={id}>{id}</li>)}</ul> }',
210
+ },
211
+ {
212
+ code: 'W020',
213
+ severity: 'error',
214
+ by: 'compiler',
215
+ catches:
216
+ 'React hook imports and unbound standard hook names; imported custom `useX()` calls in components and local wrappers with resolved hook calls in their bodies; dependency arrays on core `watch`, `derived`, or `memo`. Local names and parameters alone do not establish a hook',
217
+ fix: 'Use `state` and `.value`, `watch(source, callback)` with `onCleanup`, `derived` and `.value`, `ref={el}`, or plain functions. Core `memo` returns a callable: read `value()`. Reads track dependencies. Only an empty dependency array on `derived` or `memo` has a safe autofix',
218
+ example:
219
+ "import { useState } from 'react'\nexport function A() { const [n] = useState(0); return <p>{n}</p> }",
220
+ },
221
+ {
222
+ code: 'W030',
223
+ severity: 'error',
224
+ by: 'cli',
225
+ catches: 'Module-level `state` reachable from the server entry graph (leaks across requests)',
226
+ fix: '`context`, or mark `state.global`',
227
+ },
228
+ {
229
+ code: 'W040',
230
+ severity: 'error',
231
+ by: 'compiler',
232
+ catches:
233
+ 'Early `return` in a component body that depends on state (runs once, never re-checked)',
234
+ fix: 'Branch in JSX',
235
+ example:
236
+ "import { state } from '@weft-js/core'\nexport function A() { const n = state(0); if (n.value > 1) return null; return <p>{n.value}</p> }",
237
+ },
238
+ {
239
+ code: 'W041',
240
+ severity: 'error',
241
+ by: 'compiler',
242
+ catches: '`let` variable mutated in a handler and read in JSX (never updates)',
243
+ fix: 'Make it `state`',
244
+ example:
245
+ 'export function A() { let n = 0; return <button onClick={() => { n++ }}>{n}</button> }',
246
+ },
247
+ {
248
+ code: 'W044',
249
+ severity: 'warning',
250
+ by: 'cli',
251
+ catches:
252
+ 'A `key` read from `crypto.randomUUID()`, `Date.now()`, `Math.random()` or `performance.now()`: it differs between server and client, so the row is rebuilt on hydration and on every render. At runtime the same miss logs `a key differs between server and client`',
253
+ fix: 'Key on a stable id from the data',
254
+ },
255
+ {
256
+ code: 'W043',
257
+ severity: 'warning',
258
+ by: 'cli',
259
+ catches:
260
+ '`data-x={true}` or `data-x={false}` on a native element: `data-*` booleans are presence flags, so these write `data-x=""` or nothing, not React\'s `"true"` and `"false"`',
261
+ fix: '`data-x` for a flag (`weft fix`), or `data-x="true"` for the word. CSS matches `[data-x]`',
262
+ },
263
+ {
264
+ code: 'W050',
265
+ severity: 'error',
266
+ by: 'compiler',
267
+ catches:
268
+ 'Raw `addEventListener`, `setInterval`, `new WebSocket` or `new EventSource` in a component with no cleanup (it outlives the component)',
269
+ fix: '`on(target, type, fn)` / `interval(ms, fn)` from @weft-js/core/browser, or clean it up in `onCleanup`',
270
+ example: "export function A() { window.addEventListener('keydown', () => {}); return <p /> }",
271
+ },
272
+ {
273
+ code: 'W051',
274
+ severity: 'error',
275
+ by: 'compiler',
276
+ catches:
277
+ 'A @weft-js/core/browser value, `on` or `interval` created outside an owner: module scope, a timer or promise callback, an event handler, after `await`',
278
+ fix: "Create it in a component's (or `defineState`'s) setup, or inside `createRoot(dispose => …)`",
279
+ example: "import { online } from '@weft-js/core/browser'\nexport const up = online()",
280
+ },
281
+ {
282
+ code: 'W052',
283
+ severity: 'error',
284
+ by: 'compiler',
285
+ catches: '`watch` folding a stream by hand',
286
+ fix: '`fold` + `initial` where it is read: kept in the cache',
287
+ example:
288
+ "import { state, watch } from '@weft-js/core'\nimport { prices } from './server/prices'\nexport function A() { const tick = prices({}); const list = state([]); watch(() => tick.value, m => { list.value = [...list.value, m] }); return <p>{list.value.length}</p> }",
289
+ },
290
+ {
291
+ code: 'W053',
292
+ severity: 'error',
293
+ by: 'start',
294
+ catches: '`.live()` on a query with no `tags` (dev): no mutation can ever poke it',
295
+ fix: 'Add `tags` to the query, and the same tag to the mutations that change it',
296
+ },
297
+ {
298
+ code: 'W054',
299
+ severity: 'error',
300
+ by: 'compiler',
301
+ catches:
302
+ '`media(query)` without `ssr` in a server-rendered component (hydration mismatch); a warning when `weft check` cannot tell',
303
+ fix: 'Pass the server value: `media(query, { ssr: false })`',
304
+ example:
305
+ "import { media } from '@weft-js/core/browser'\nexport function A() { const dark = media('(prefers-color-scheme: dark)'); return <p>{dark.value ? 'dark' : 'light'}</p> }",
306
+ },
307
+ {
308
+ code: 'W056',
309
+ severity: 'warning',
310
+ by: 'compiler',
311
+ catches:
312
+ '`watch` that copies a live value (a @weft-js/core/browser value, `.live()`) into `state`',
313
+ fix: 'Read `live.value` directly, or `derived(() => live.value)`',
314
+ example:
315
+ "import { state, watch } from '@weft-js/core'\nimport { online } from '@weft-js/core/browser'\nexport function A() { const isOnline = online(); const up = state(true); watch(() => isOnline.value, v => { up.value = v }); return <p>{up.value}</p> }",
316
+ },
317
+ {
318
+ code: 'W060',
319
+ severity: 'error',
320
+ by: 'compiler',
321
+ catches:
322
+ "A `group()` from @weft-js/core/style that can't be evaluated at build: a value that isn't a literal (named with its path), a `group()` outside a top-level `const`, or an accent that isn't literal tokens",
323
+ fix: "Inline the token name or literal; define groups as `export const x = group('x', { … })`; raw CSS goes in `css: { … }`",
324
+ example:
325
+ "import { group } from '@weft-js/core/style'\nconst px = 13\nexport const g = group('g', { base: { pad: px } })",
326
+ },
327
+ {
328
+ code: 'W061',
329
+ severity: 'error',
330
+ by: 'compiler',
331
+ catches:
332
+ "`bind:x` on a native element, or `bind:x`/`bind(box)` with something that isn't a box or that isn't a component prop",
333
+ fix: 'Write `<Card bind:person={name} />`. A form element takes `value` and `onChange`',
334
+ example:
335
+ "import { bind, state } from '@weft-js/core'\nexport function A() { const n = state(1); return <input value={bind(n)} /> }",
336
+ },
337
+ ]
@@ -0,0 +1,18 @@
1
+ /** contracts diagnostic entries in catalog order. */
2
+ export const contracts = [
3
+ {
4
+ code: 'C001',
5
+ severity: 'warning',
6
+ by: 'cli',
7
+ catches:
8
+ "A function's `input` or `output` schema can't become JSON Schema: the contract says `{}`",
9
+ fix: "Use a schema with JSON Schema support (Zod 4 has it), or drop what can't be represented",
10
+ },
11
+ {
12
+ code: 'C002',
13
+ severity: 'error',
14
+ by: 'start',
15
+ catches: "A function's value failed its `output` schema (dev and tests)",
16
+ fix: 'Fix `run`, or the schema',
17
+ },
18
+ ]
@@ -0,0 +1,52 @@
1
+ /** databaseQueries diagnostic entries in catalog order. */
2
+ export const databaseQueries = [
3
+ {
4
+ code: 'Q011',
5
+ severity: 'warning',
6
+ by: 'start',
7
+ catches:
8
+ "A function read or wrote a table its `tags`/`invalidates` don't name (dev): a mutation of that table won't refresh it",
9
+ fix: "Add the table's name (or `table:…`) to `tags`/`invalidates`",
10
+ },
11
+ {
12
+ code: 'Q012',
13
+ severity: 'error',
14
+ by: 'start',
15
+ catches: '`@weft-js/start/db*` or `@weft-js/start/auth` reached the client graph',
16
+ fix: "Import it from a server module (`src/server/**`, `*.server.ts`) or a route's `load`",
17
+ },
18
+ {
19
+ code: 'Q013',
20
+ severity: 'error',
21
+ by: 'start',
22
+ catches:
23
+ "A database URL the driver can't open (a `postgres://` URL given to the SQLite entry, or the reverse)",
24
+ fix: 'Use the matching `createDb`: `@weft-js/start/db/sqlite` or `@weft-js/start/db/postgres`',
25
+ },
26
+ {
27
+ code: 'Q014',
28
+ severity: 'error',
29
+ by: 'start',
30
+ catches: "The database doesn't match the schema: a table or column is missing",
31
+ fix: 'Run `weft db generate`, then reload (new migrations apply on the next request)',
32
+ },
33
+ ]
34
+
35
+ /** databaseSchema diagnostic entries in catalog order. */
36
+ export const databaseSchema = [
37
+ {
38
+ code: 'Q015',
39
+ severity: 'warning',
40
+ by: 'cli',
41
+ catches: '`src/db/schema.ts` changed since the last `weft db generate`',
42
+ fix: 'Run `weft db generate`',
43
+ },
44
+ {
45
+ code: 'Q016',
46
+ severity: 'error',
47
+ by: 'start',
48
+ catches:
49
+ "A request used the database while it was down, or a migration it needs failed (503; the server log has the driver's error). Pages that run no statements still serve",
50
+ fix: 'Start the database and check `DATABASE_URL`, or fix the migration: the next statement tries again',
51
+ },
52
+ ]