@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
package/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # @weft-js/cli
2
2
 
3
- > **Early release (0.1.0).** APIs will change between minor versions.
3
+ > **Early release (0.1.x).** APIs can change between minor versions.
4
4
 
5
- The `weft` command. It runs an app (`dev`, `build`, `start`), checks it (`check`, `tsc`, `fix`), serves its docs to people
6
- and agents (`docs`, `agents-md`, `mcp`) and manages plugins (`add`, `plugins`). An app made with `npm create @weft-js`
5
+ The `weft` command. It runs an app (`dev`, `build`, `start`), checks it (`check`, `tsc`, `fix`, `audit`), ships it
6
+ (`deploy`), serves its docs to people and agents (`docs`, `agents-md`, `mcp`) and manages plugins (`add`, `plugins`). An app made with `npm create @weft-js`
7
7
  already has it.
8
8
 
9
9
  ```bash
@@ -25,22 +25,28 @@ files, errors and warnings.
25
25
  | `fix [files…]` | Applies the safe autofixes |
26
26
  | `doctor` | Which compiler this install uses, whether Rust is around (it's optional), and what live data does on the target |
27
27
  | `dev [--port <n>]` | The dev server, for an app with `src/routes` |
28
- | `build [--runtime node\|bun\|deno] [--preset <p>]` | The production build, into `.output/` |
28
+ | `build [--runtime node\|bun\|deno] [--preset <p>]` | The production build, into `.output/`. The built pages are then audited (L-codes), and an error fails the build |
29
29
  | `start [--runtime <r>]` | Runs the build |
30
+ | `audit [routes…] [--url <u>]` | The built pages, or a running site, in a clean Chrome at phone and desktop widths: axe-core's WCAG rules, console errors, the head, sideways scroll and layout shift |
31
+ | `deploy hosts` | Which hosts can run this app, why not, what each costs, and the one Weft suggests |
32
+ | `deploy init [target]` | The host's files (`netlify.toml`, `fly.toml` and a Dockerfile…) and a GitHub workflow that checks and deploys on push |
33
+ | `deploy [--prod] [--yes]` | Checks the fit, builds for the target and runs the host's own CLI: Netlify, Vercel, Cloudflare, Fly or Render. Without `--yes` it's a dry run |
30
34
  | `db generate [name]`, `db live`, `db listen` | A migration for schema changes; one with change triggers; the change feed as its own process |
31
35
  | `routes [--meta]` | The route table; `--meta` adds each route's metadata and H-diagnostics |
36
+ | `surfaces` | The canvas and WebGL surfaces the project already defines |
32
37
  | `test --gate` | The test gate on the diff: fail-before, mutants on changed lines and no weakened tests |
33
38
  | `browser <cmd>` | Drives the running app: open, snapshot, act, assert and more (`weft browser help`) |
34
39
  | `docs [topic\|code]` | Prints a docs page or a diagnostic (`weft docs props`, `weft docs W014`) |
35
- | `agents-md [--check]` | Writes AGENTS.md from the installed weft; `--check` exits 1 when it's stale |
40
+ | `agents-md [--check]` | Writes AGENTS.md from the installed Weft; `--check` exits 1 when it's stale |
36
41
  | `map [--stdout]` | Writes `.weft/map.md`: what to call in this app |
42
+ | `icons publish <dir>` | Every icon and variant as static `.svg` files with a `manifest.json`, for a CDN |
37
43
  | `tasks`, `run <task>` | Lists commands and project tasks; runs a task from `weft.config.ts` |
38
44
  | `add <feature>` | Installs a plugin and wires it into the config; also `tailwind`, `auth`, `db` and `rust` |
39
45
  | `plugins [list\|explain\|sync\|audit\|approve]` | Plugins, their slots and permissions |
40
46
  | `style find\|explain\|matrix` | Style groups |
41
- | `tokens` | warp's resolved tokens as DTCG, in `.weft/tokens/` |
47
+ | `tokens` | Warp's resolved tokens as DTCG, in `.weft/tokens/` |
42
48
  | `lsp` | A language server for editors, with inferred props |
43
- | `mcp` | These commands as MCP tools, on stdio |
49
+ | `mcp` | An MCP server on stdio: `docs`, `check`, `audit`, `routes_meta`, `map`, `tasks`, `surfaces`, `style_find`, `style_explain`, `test_gate` and the `browser_*` tools |
44
50
 
45
51
  Every command takes `--root <dir>` (the working directory by default) and `--json`. JSON goes to stdout and text for
46
52
  people to stderr. Exit codes: 0 ok, 1 errors found (or a stale AGENTS.md under `--check`), 2 a usage or internal error.
@@ -48,8 +54,9 @@ people to stderr. Exit codes: 0 ok, 1 errors found (or a stale AGENTS.md under `
48
54
  ## Docs
49
55
 
50
56
  - `weft docs config`: `weft.config.ts` and every key in it.
51
- - `weft docs diagnostics`: every code, and which ones the installed weft reports.
57
+ - `weft docs diagnostics`: every code, and which ones the installed Weft reports.
52
58
  - `weft docs verify`: `weft browser` and the test gate.
59
+ - `weft docs deploy`: hosts, `weft deploy` and the E-codes.
53
60
  - `weft docs plugins`: writing a plugin.
54
61
 
55
62
  The source of each page is in [docs/](docs). Adding a diagnostic is in [CONTRIBUTING.md](../../CONTRIBUTING.md).
@@ -59,13 +66,23 @@ The source of each page is in [docs/](docs). Adding a diagnostic is in [CONTRIBU
59
66
  `check`, `tasks` and the MCP tools report the same shape:
60
67
 
61
68
  ```json
62
- { "code": "W014", "severity": "error", "owner": "core", "file": "src/App.tsx",
63
- "span": [21, 44, 21, 117], "offset": [867, 940],
64
- "why": "`.map(p => ({ ...p, … }))` gives every item a new identity on each update…",
65
- "fix": { "text": "Keep items stable … or replace only the item that changed." },
69
+ {
70
+ "code": "W014",
71
+ "severity": "error",
72
+ "owner": "core",
73
+ "file": "src/App.tsx",
74
+ "span": [21, 44, 21, 117],
75
+ "offset": [867, 940],
76
+ "why": "Copying every item gives each a new identity.",
77
+ "fix": { "text": "Keep unchanged items stable." },
66
78
  "project": true,
67
- "related": [{ "file": "src/components/ProductList.tsx", "span": [29, 10, 29, 10], "why": "rendered here as a keyed list" }],
68
- "docs": "weft docs W014" }
79
+ "related": [{
80
+ "file": "src/components/ProductList.tsx",
81
+ "span": [29, 10, 29, 10],
82
+ "why": "rendered here as a keyed list"
83
+ }],
84
+ "docs": "weft docs W014"
85
+ }
69
86
  ```
70
87
 
71
88
  - `span` is 1-based `[line, col, endLine, endCol]`, with columns in UTF-16 units. `offset` is UTF-16 indices into the
@@ -73,3 +90,10 @@ The source of each page is in [docs/](docs). Adding a diagnostic is in [CONTRIBU
73
90
  - `fix.edits` (`{ file, span, offset, text }`) is there when there's a mechanical change, and `fix.safe` says whether
74
91
  `weft fix` applies it without asking.
75
92
  - `project: true` marks a result that needed more than one file to find.
93
+
94
+ ## Links
95
+
96
+ - [Source](https://github.com/Ravonus/weft/tree/main/packages/cli)
97
+ - [Weft on GitHub](https://github.com/Ravonus/weft): the framework, the docs and every package
98
+ - [Changelog](https://github.com/Ravonus/weft/blob/main/CHANGELOG.md)
99
+ - [Issues](https://github.com/Ravonus/weft/issues)
package/bin/weft.mjs CHANGED
@@ -4,5 +4,12 @@ import { main } from '../src/cli.mjs'
4
4
  // Not `exitCode`: an app's module can leave a pool open, and a finished command must still end.
5
5
  const code = await main(process.argv.slice(2))
6
6
  // A pipe holds about 64 KB: exiting first would cut off long --json output.
7
- await Promise.all([process.stdout, process.stderr].map(out => new Promise(done => { out.write('', done) })))
7
+ await Promise.all(
8
+ [process.stdout, process.stderr].map(
9
+ (out) =>
10
+ new Promise((done) => {
11
+ out.write('', done)
12
+ }),
13
+ ),
14
+ )
8
15
  process.exit(code)
package/docs/async.md CHANGED
@@ -6,16 +6,26 @@ import { resource, Boundary } from '@weft-js/core'
6
6
  type User = { name: string }
7
7
 
8
8
  function Name({ id }: { id: string }) {
9
- // `id` is read before the first `await`, so a new id fetches again and aborts the old request.
10
- const user = resource(signal => fetch(`/api/users/${id}`, { signal }).then(r => r.json() as Promise<User>))
11
- // `pending` is also true on a refetch, while the old value is still here: dim it rather than replace it (X033).
12
- return <h1 aria-busy={user.pending ? 'true' : 'false'}>{user.value?.name}</h1>
9
+ // A new id refetches and aborts the old request.
10
+ const user = resource(signal =>
11
+ fetch(`/api/users/${id}`, { signal })
12
+ .then(r => r.json() as Promise<User>),
13
+ )
14
+ // On a refetch, dim the old value rather than replace it (X033).
15
+ return (
16
+ <h1 aria-busy={user.pending ? 'true' : 'false'}>
17
+ {user.value?.name}
18
+ </h1>
19
+ )
13
20
  }
14
21
 
15
- // The resource is made inside the Boundary (in Name), so the Boundary waits for it.
22
+ // Name makes the resource inside the Boundary, which waits for it.
16
23
  export function Profile({ id }: { id: string }) {
17
24
  return (
18
- <Boundary pending={<p>Loading…</p>} error={(e, retry) => <button onClick={retry}>Retry</button>}>
25
+ <Boundary
26
+ pending={<p>Loading…</p>}
27
+ error={(e, retry) => <button onClick={retry}>Retry</button>}
28
+ >
19
29
  <Name id={id} />
20
30
  </Boundary>
21
31
  )
package/docs/browser.md CHANGED
@@ -2,20 +2,34 @@
2
2
 
3
3
  ```tsx
4
4
  import { state } from '@weft-js/core'
5
- import { online, visible, lifecycle, media, on, interval } from '@weft-js/core/browser'
5
+ import {
6
+ online,
7
+ visible,
8
+ lifecycle,
9
+ media,
10
+ on,
11
+ interval,
12
+ } from '@weft-js/core/browser'
6
13
 
7
14
  export function Toolbar() {
8
- const isOnline = online() // Derived<boolean>; server: true
9
- const shown = visible() // Derived<boolean>; server: true
10
- const page = lifecycle() // 'active' | 'passive' | 'hidden' | 'frozen' | 'terminated'
11
- const dark = media('(prefers-color-scheme: dark)', { ssr: false }) // server renders `ssr`
15
+ const isOnline = online() // Derived<boolean>; server: true
16
+ const shown = visible() // Derived<boolean>; server: true
17
+ // 'active' | 'passive' | 'hidden' | 'frozen' | 'terminated'
18
+ const page = lifecycle()
19
+ // Server renders `ssr`.
20
+ const dark = media('(prefers-color-scheme: dark)', { ssr: false })
12
21
  const open = state(true)
13
22
  const now = state(Date.now())
14
- on(window, 'keydown', e => { if (e.key === 'Escape') open.value = false }) // e: KeyboardEvent
15
- interval('1s', () => { now.value = Date.now() }) // also '500ms', '5m', '1h', or a number of ms
23
+ on(window, 'keydown', e => { // e: KeyboardEvent
24
+ if (e.key === 'Escape') open.value = false
25
+ })
26
+ // Also '500ms', '5m', '1h', or a number of ms.
27
+ interval('1s', () => { now.value = Date.now() })
16
28
  return (
17
29
  <p hidden={!open.value}>
18
- {isOnline.value ? 'online' : 'offline'}, {dark.value ? 'dark' : 'light'}, {shown.value ? page.value : 'hidden'},
30
+ {isOnline.value ? 'online' : 'offline'}{', '}
31
+ {dark.value ? 'dark' : 'light'}{', '}
32
+ {shown.value ? page.value : 'hidden'},
19
33
  {new Date(now.value).toLocaleTimeString()}
20
34
  </p>
21
35
  )
package/docs/config.md CHANGED
@@ -12,24 +12,41 @@ import { dialScript, warp } from '@weft-js/warp/plugin'
12
12
  export default defineConfig({
13
13
  scope: { write: ['src/**', 'test/**'], forbid: ['.env*'] },
14
14
  tasks: {
15
- seed: { run: 'node scripts/seed.mjs', description: 'Load demo products into the dev db', sideEffects: ['db'], env: ['dev'] },
15
+ seed: {
16
+ run: 'node scripts/seed.mjs',
17
+ description: 'Load demo products into the dev db',
18
+ sideEffects: ['db'], env: ['dev'],
19
+ },
16
20
  },
17
21
  environments: { dev: { url: 'http://localhost:5173' } },
18
- site: { // page metadata defaults: static tags in the server's first response (weft docs meta)
22
+ // Metadata defaults: static tags in the first response.
23
+ // See weft docs meta.
24
+ site: {
19
25
  name: 'Acme',
20
26
  url: 'https://acme.dev',
21
27
  title: '%s · Acme',
22
28
  description: 'Tools for builders.',
23
29
  locale: 'en_US',
24
- image: { url: '/og.png', width: 1200, height: 630 }, // default 'auto': a generated card (weft docs meta); false: none
30
+ // Default 'auto': generated card; false: none (weft docs meta).
31
+ image: { url: '/og.png', width: 1200, height: 630 },
25
32
  twitter: { card: 'summary_large_image', site: '@acme' },
26
- inlineCss: false, // default true: each page carries what its first paint needs; false keeps plain links
27
- criticalCss: false, // default true: one hashed sheet plus a first-paint part; false splits by chunk and inlines up to 48 KB
28
- criticalKeep: ['theme-x'], // words of selectors a first paint keeps, for classes a head script sets
29
- head: dialScript, // raw HTML in every page's head, before the stylesheets: dialScript from @weft-js/warp/plugin restores `setDial(…, { persist: true })` before the first paint
33
+ // Default true: first-paint CSS. False keeps plain links,
34
+ // as a strict CSP needs (D012).
35
+ inlineCss: false,
36
+ // Default true: one hashed sheet and first-paint CSS.
37
+ // False splits by chunk and inlines up to 48 KB.
38
+ criticalCss: false,
39
+ // Selector words to keep for classes set by a head script.
40
+ criticalKeep: ['theme-x'],
41
+ // Raw HTML before stylesheets on every page. dialScript from
42
+ // @weft-js/warp/plugin restores setDial(…, { persist: true })
43
+ // before the first paint.
44
+ head: dialScript,
30
45
  },
31
- plugins: [warp()], // plugin factories (@weft-js/core/plugin); a plain `{ name, provides }` entry is data only
32
- slots: {}, // names the winner when two plugins provide one slot
46
+ // Plugin factories; plain { name, provides } entries are data.
47
+ plugins: [warp()],
48
+ // Names the winner when two plugins provide one slot.
49
+ slots: {},
33
50
  })
34
51
  ```
35
52
 
@@ -91,3 +108,16 @@ that hides itself or code in dependencies (your package manager's lockfile pins
91
108
  fixed code to hash, so it's audited on every check instead. A plugin defined in the config file is your own code:
92
109
  only its permissions are compared. A plugin is matched to its package by name, so one that claims another's name is
93
110
  hashed as that package.
111
+
112
+
113
+ ## Indexing intent
114
+
115
+ For a site intended for public search, declare
116
+ `build: { audit: { indexing: 'public' } }`. L024 then fails the build or audit if
117
+ robots metadata or an HTTP X-Robots-Tag header says noindex. `private` or the
118
+ backward-compatible `auto` leaves this a warning; neither removes a directive.
119
+ After deployment, run `weft audit --url https://your-site.example --indexing public`:
120
+ a host or proxy may add a header that a local build cannot see. Preserve intentional
121
+ preview/private exclusions. A noindex finding is an indexing policy issue, not
122
+ chat-agent traffic; use a clean profile before attributing other audit failures
123
+ to application or framework code.
package/docs/core.md ADDED
@@ -0,0 +1,30 @@
1
+ # Core entrypoints
2
+
3
+ Import application APIs such as `state`, `derived`, `watch`, `context` and `createRoot`
4
+ from `@weft-js/core`. Use the `State<T>` and `Derived<T>` types for boxes.
5
+
6
+ `memo(fn)` returns a cached accessor, `() => T`. Call it to read the current value.
7
+ It tracks the same dependencies as `derived(fn)` and recomputes when they change.
8
+
9
+ ```ts
10
+ import { memo, state } from '@weft-js/core'
11
+
12
+ const count = state(1)
13
+ const twice = memo(() => count.value * 2)
14
+ console.log(twice()) // 2
15
+ ```
16
+
17
+ Compiler output and framework integrations use `@weft-js/core/internal`. This
18
+ entrypoint re-exports the runtime's existing implementations and shares its graph,
19
+ box constructors and server state. It does not create another runtime instance.
20
+
21
+ The root no longer exports `DIRTY`, `rt`, `listener`, `read`, `reach`, `clean`,
22
+ `setFlat`, `flat`, `collecting`, `runMounts`, `setKeyed`, `NONE`, `stats`,
23
+ `devComponent` or `Box`. Integrations that require these use the internal entrypoint.
24
+ Applications create boxes with `state` rather than constructing `Box`.
25
+
26
+ `hole`, `localProps` and `restOf` also live under the internal entrypoint. `hole`
27
+ tracks a renderer's child binding. `localProps` implements writable prop locals.
28
+ `restOf` takes the prop snapshot used by a compiled native-element spread. These
29
+ are compiler and renderer contracts, rather than application APIs. The `hole`
30
+ export of `@weft-js/core/ssr` is a separate server-rendering helper.
package/docs/deploy.md ADDED
@@ -0,0 +1,108 @@
1
+ # Deploy: from `weft build` to a live URL
2
+
3
+ weft reads what the app uses and says which hosts can run it, before a build and before an account exists. Then it
4
+ writes the host's files and a workflow, and `weft deploy` runs the host's own CLI and reports the URL.
5
+
6
+ ```bash
7
+ # Host fit, cost and suggestion
8
+ weft deploy hosts
9
+ # Host files, GitHub workflow and deploy config
10
+ weft deploy init
11
+ # Dry run: build and commands that --yes would run
12
+ weft deploy
13
+ # Preview; add --prod for production
14
+ weft deploy --yes
15
+ ```
16
+
17
+ `weft deploy` without `--yes` runs nothing. An agent runs the dry run on its own, and asks before `--yes`: a deploy
18
+ publishes, and a host may bill for it. Nothing in weft creates a site, an app, a volume or a DNS record; `init` prints the
19
+ one command per host that does, for you to run.
20
+
21
+ ## Which host
22
+
23
+ | The app uses | Netlify / Vercel | Cloudflare | Fly / Render |
24
+ |---|---|---|---|
25
+ | pages, queries, mutations, forms, `stream()` | ✓ | ✓ | ✓ |
26
+ | `channel`, `sharedState`, `realtime({ share })` | ✗ E001 | ✓ (Durable Objects) | ✓ |
27
+ | `.live({ required })`, `remote()` sidecars | ✗ E002 | ✗ E002 | ✓ |
28
+ | a SQLite file, media uploads | ✗ E003 | ✗ E003 | with a volume or disk (billed) |
29
+ | `changes` without an external listener | degraded (R014) | degraded | ✓ |
30
+
31
+ The suggestion: Netlify when nothing needs a server that stays up (a CDN with functions is the cheapest and fastest
32
+ host), Cloudflare when channels are all that need one, Fly when data must last on disk, else Fly or Render.
33
+
34
+ A function host blocked by a channel has a third way, the split: pages on the function host, realtime on a small server.
35
+ It needs a second origin and a cookie both can read, and is designed (research 46 §10), not built.
36
+
37
+ ## `deploy` in weft.config.ts
38
+
39
+ ```ts
40
+ deploy: {
41
+ // netlify | vercel | cloudflare | fly | render
42
+ target: 'fly',
43
+ fly: {
44
+ app: 'shop',
45
+ region: 'iad',
46
+ volume: { name: 'data', sizeGb: 1 },
47
+ },
48
+ ci: 'github', // the workflow; false for none
49
+ branch: 'main',
50
+ cdn: {
51
+ proxy: 'cloudflare',
52
+ images: { provider: 'cloudinary', cloud: 'shop' },
53
+ },
54
+ }
55
+ ```
56
+
57
+ Names, regions and sizes only. Every token comes from the environment, and one written into the config, a host file or a
58
+ workflow is **E008**. With `deploy.target` set, `weft build` builds for it, and `weft check` reports E001–E003 for it.
59
+
60
+ A volume or disk mounts at `/app/.data`: SQLite's default `file:.data/app.db` is already there, and uploads go to
61
+ `.data/media` (`WEFT_MEDIA_DIR`).
62
+
63
+ ## What each target writes and runs
64
+
65
+ | Target | Files | `weft deploy --yes` runs | Reads |
66
+ |---|---|---|---|
67
+ | netlify | `netlify.toml` | `weft build --preset netlify`, `netlify deploy --dir dist --functions .netlify/functions-internal --json` | `NETLIFY_AUTH_TOKEN`, `NETLIFY_SITE_ID` |
68
+ | vercel | `vercel.json` | `weft build --preset vercel`, `vercel deploy --prebuilt` | `VERCEL_TOKEN`, `VERCEL_ORG_ID`, `VERCEL_PROJECT_ID` |
69
+ | cloudflare | `wrangler.toml` | `weft build --preset cloudflare_durable`, `wrangler deploy` in `.output/` | `CLOUDFLARE_API_TOKEN`, `CLOUDFLARE_ACCOUNT_ID` |
70
+ | fly | `fly.toml`, `Dockerfile`, `.dockerignore` | `flyctl deploy --remote-only` (Fly builds the image) | `FLY_API_TOKEN` |
71
+ | render | `render.yaml`, `Dockerfile`, `.dockerignore` | the deploy hook (Render builds the pushed commit) | `RENDER_DEPLOY_HOOK_URL` |
72
+
73
+ Every target also gets `.github/workflows/weft-deploy.yml`: on push to `branch`, install, `weft check`, then `weft deploy
74
+ --yes --prod`; on a pull request, a preview where the host has them. Add the variables as repository secrets (`gh secret
75
+ set NAME`). Netlify, Vercel and Cloudflare deploy what was built, so build where the host runs: the workflow builds on
76
+ Linux, and a native addon built on a Mac is **E004**.
77
+
78
+ Each written file starts with `# weft deploy init <target>, sha <hash>`. Change the config and `weft check` reports the
79
+ file as stale (**E006**); `init` rewrites it. A file you edited keeps its old hash, and `init` leaves it alone unless
80
+ `--force`.
81
+
82
+ ## CDN and media
83
+
84
+ - **Cloudflare in front of a server host** (`cdn.proxy: 'cloudflare'`): proxy the domain to the Fly or Render origin.
85
+ Hashed assets are already `immutable`, and pages are `private` unless a route caches, so the proxy can't serve one
86
+ user's page to another. E010: `site.url` is still the host's own domain. E011: a proxy in front of a CDN host.
87
+ - **Cloudinary for uploads** (`cdn.images`): checked (E012 without `@weft-js/plugin-media`, E013 without a cloud or with
88
+ two), not yet wired: uploads still go to the media server's disk (E014). `?media` imports never need it: the build
89
+ already makes sized AVIF and WebP files that any CDN serves.
90
+
91
+ ## Codes
92
+
93
+ E001 sockets on a function host, E002 a held process or a sidecar, E003 data on a disk that doesn't last, E004 a native
94
+ addon deployed from another OS, E005 no target (with the suggestion), E006 a stale host file, E007 an unset variable,
95
+ E008 a written credential, E009 the memory bus on many instances, E010–E014 CDN and media. `weft docs E003` explains one.
96
+
97
+
98
+ ## Indexing intent
99
+
100
+ For a site intended for public search, declare
101
+ `build: { audit: { indexing: 'public' } }`. L024 then fails the build or audit if
102
+ robots metadata or an HTTP X-Robots-Tag header says noindex. `private` or the
103
+ backward-compatible `auto` leaves this a warning; neither removes a directive.
104
+ After deployment, run `weft audit --url https://your-site.example --indexing public`:
105
+ a host or proxy may add a header that a local build cannot see. Preserve intentional
106
+ preview/private exclusions. A noindex finding is an indexing policy issue, not
107
+ chat-agent traffic; use a clean profile before attributing other audit failures
108
+ to application or framework code.
package/docs/design.md CHANGED
@@ -4,7 +4,15 @@ An app with warp has one design language: `@weft-js/warp`'s primitives and compo
4
4
  is, and the look follows the theme, the dials and the density.
5
5
 
6
6
  ```tsx
7
- import { Button, Card, Heading, Input, Row, Stack, Text } from '@weft-js/warp'
7
+ import {
8
+ Button,
9
+ Card,
10
+ Heading,
11
+ Input,
12
+ Row,
13
+ Stack,
14
+ Text,
15
+ } from '@weft-js/warp'
8
16
 
9
17
  export function Billing() {
10
18
  return (
@@ -85,13 +93,14 @@ In every app:
85
93
  | D038 | Two solid Buttons side by side | One primary action per region |
86
94
  | D039 | `<html>` with no `lang` | Set it |
87
95
 
88
- D010 is warp's contrast gate on the seed and tokens. D020–D025 are style groups.
96
+ D010 is warp's contrast gate on the seed and tokens: `weft build` stops on it. D012 is a CSP that blocks the inlined CSS. D020–D025 are style groups.
89
97
 
90
98
  `// weft-allow D001: <reason>` on the line above keeps one site; the reason is required. `check: { design: false }` in
91
99
  the config turns these off.
92
100
 
93
101
  ## Tokens
94
102
 
95
- The theme is `warp({ seed, dials, radius, fonts, tokens })`: every value goes through the contrast check. `weft tokens`
103
+ The theme is `warp({ seed, dials, fields, radius, fonts, tokens })`: every value goes through the contrast check. `weft docs theme`
104
+ has worked looks and the button channels. `weft tokens`
96
105
  writes the resolved tokens to `.weft/tokens/` as DTCG 2025.10, one file per theme × expression plus a resolver, for
97
106
  Figma, Style Dictionary or Terrazzo.
@@ -0,0 +1,79 @@
1
+ # Core and Start entrypoints
2
+
3
+ These paths are retained package exports. Node tooling must not enter a browser bundle.
4
+ The snapshots cover export paths, targets and conditions, not names or signatures.
5
+
6
+ ## @weft-js/core
7
+
8
+ - `@weft-js/core`: Application reactive runtime and components.
9
+ - `@weft-js/core/internal`: Compiler and renderer contract; shares the runtime graph, not an app API.
10
+ - `@weft-js/core/keyed`: Keyed state helpers.
11
+ - `@weft-js/core/jsx-runtime`: JSX types for compiler output; types only.
12
+ - `@weft-js/core/dom`: Browser DOM rendering and hydration; other helpers serve compiler output.
13
+ - `@weft-js/core/ssr`: Server rendering and compiler helpers.
14
+ - `@weft-js/core/head`: Browser route metadata updates.
15
+ - `@weft-js/core/test`: Test utilities; requires the test peers.
16
+ - `@weft-js/core/test/plugins`: Plugin conformance test suites; browser layout tests need a browser.
17
+ - `@weft-js/core/persist`: State persistence.
18
+ - `@weft-js/core/history`: State history.
19
+ - `@weft-js/core/sync`: State synchronization.
20
+ - `@weft-js/core/interaction`: Interaction state.
21
+ - `@weft-js/core/browser`: Browser APIs and browser state.
22
+ - `@weft-js/core/style`: Style groups and styling helpers.
23
+ - `@weft-js/core/ds`: Design system primitives.
24
+ - `@weft-js/core/media`: Media components and helpers.
25
+ - `@weft-js/core/shape`: Shape components and helpers.
26
+ - `@weft-js/core/text`: Text components and helpers.
27
+ - `@weft-js/core/surface`: Surface components and helpers.
28
+ - `@weft-js/core/media-probe`: Media probing for tooling; Node entry.
29
+ - `@weft-js/core/client`: Client type declarations only; no runtime condition.
30
+ - `@weft-js/core/style/matrix`: Style matrix tooling; Node entry.
31
+ - `@weft-js/core/hydration`: Hydration tooling; Node entry.
32
+ - `@weft-js/core/markup`: Shared markup helpers and types.
33
+ - `@weft-js/core/node-purge`: Node cache purge tooling.
34
+ - `@weft-js/core/hmr`: Compiler-generated development hot updates.
35
+ - `@weft-js/core/plugin`: Plugin declarations and runtime helpers.
36
+ - `@weft-js/core/devtools`: Development tools.
37
+ - `@weft-js/core/agent`: In-page development agent; requires testing peers.
38
+ - `@weft-js/core/config`: Configuration declarations and runtime helpers.
39
+ - `@weft-js/core/vite`: Vite plugin; Node tooling.
40
+ - `@weft-js/core/compiler`: Compiler binding; Node tooling and native or WASM compiler.
41
+
42
+ ## @weft-js/start
43
+
44
+ - `@weft-js/start`: Application server functions, forms and routing APIs.
45
+ - `@weft-js/start/data`: Server function and data primitives.
46
+ - `@weft-js/start/derive`: Server-side page metadata derivation.
47
+ - `@weft-js/start/media`: Media schema and asset types.
48
+ - `@weft-js/start/hub`: Realtime hub and topics; server.
49
+ - `@weft-js/start/live`: Streams and live queries.
50
+ - `@weft-js/start/bus`: Realtime bus interfaces.
51
+ - `@weft-js/start/changes`: Database change feeds; server.
52
+ - `@weft-js/start/changes-sql`: SQL for database change triggers; server tooling.
53
+ - `@weft-js/start/live-target`: Live target capability checks; Node tooling.
54
+ - `@weft-js/start/listen`: Database change listener; server.
55
+ - `@weft-js/start/verify`: Live verification; server tooling.
56
+ - `@weft-js/start/bus/postgres`: Postgres realtime bus; server, requires Postgres.
57
+ - `@weft-js/start/list`: List queries and helpers.
58
+ - `@weft-js/start/list/drizzle`: Drizzle list integration; requires Drizzle.
59
+ - `@weft-js/start/channel`: Realtime channels.
60
+ - `@weft-js/start/share`: Shared state.
61
+ - `@weft-js/start/db`: Drizzle schema helpers; requires Drizzle.
62
+ - `@weft-js/start/db/core`: Database interfaces and core helpers.
63
+ - `@weft-js/start/db/sqlite`: SQLite database adapter.
64
+ - `@weft-js/start/db/postgres`: Postgres database adapter; requires Postgres driver.
65
+ - `@weft-js/start/db/mysql`: MySQL database adapter; requires MySQL driver.
66
+ - `@weft-js/start/auth`: Authentication provider interface.
67
+ - `@weft-js/start/auth/better-auth`: Better Auth integration; requires Better Auth.
68
+ - `@weft-js/start/auth/drizzle`: Drizzle authentication integration; requires Drizzle.
69
+ - `@weft-js/start/extension`: Server extension contract.
70
+ - `@weft-js/start/client`: Client application bootstrap; browser.
71
+ - `@weft-js/start/server`: Server application rendering and request handling.
72
+ - `@weft-js/start/vite`: Start Vite plugin; Node tooling.
73
+ - `@weft-js/start/routes`: Route manifest tooling.
74
+ - `@weft-js/start/og`: Open Graph image tooling; server.
75
+ - `@weft-js/start/app`: Development and build application tooling; Node.
76
+ - `@weft-js/start/inspect`: Route metadata inspection; Node tooling.
77
+ - `@weft-js/start/contract`: Server function contract generation; Node tooling.
78
+ - `@weft-js/start/rust`: Rust remote function support.
79
+ - `@weft-js/start/sidecar`: Rust sidecar supervisor; Node tooling.
package/docs/fonts.md CHANGED
@@ -10,8 +10,18 @@ export default defineConfig({
10
10
  plugins: [
11
11
  text({
12
12
  fonts: {
13
- body: { family: 'Inter', src: 'assets/fonts/inter.woff2', fallback: 'Arial', axes: { wght: [100, 900] } },
14
- display: { family: 'Fraunces', src: 'assets/fonts/fraunces.woff2', fallback: 'Georgia', weight: 700 },
13
+ body: {
14
+ family: 'Inter',
15
+ src: 'assets/fonts/inter.woff2',
16
+ fallback: 'Arial',
17
+ axes: { wght: [100, 900] },
18
+ },
19
+ display: {
20
+ family: 'Fraunces',
21
+ src: 'assets/fonts/fraunces.woff2',
22
+ fallback: 'Georgia',
23
+ weight: 700,
24
+ },
15
25
  },
16
26
  }),
17
27
  ],
package/docs/icons.md CHANGED
@@ -12,20 +12,24 @@ Three forms, and all three build to the same bytes:
12
12
  import { CheckIcon } from '@weft-js/warp-icons'
13
13
  <CheckIcon />
14
14
 
15
- import Icons from '@weft-js/warp-icons' // or: import * as Icons
15
+ // Or: import * as Icons
16
+ import Icons from '@weft-js/warp-icons'
16
17
  <Icons.Check />
17
18
 
18
- import { Icon } from '@weft-js/warp' // or: '@weft-js/warp-icons/icon'
19
+ // Or import from '@weft-js/warp-icons/icon'.
20
+ import { Icon } from '@weft-js/warp'
19
21
  <Icon name="check" />
20
22
  ```
21
23
 
22
24
  Props on any of them:
23
25
 
24
26
  ```tsx
25
- <CheckIcon /> // decorative: aria-hidden, focusable="false"
27
+ // Decorative: aria-hidden, focusable="false"
28
+ <CheckIcon />
26
29
  <CheckIcon label="Saved" /> // role="img" with that name
27
30
  <CheckIcon size={24} /> // otherwise warp's icon sizes apply
28
- <CheckIcon tier="vivid" /> // this tier whatever the page's expression is
31
+ // This tier regardless of the page's expression.
32
+ <CheckIcon tier="vivid" />
29
33
  ```
30
34
 
31
35
  An icon you don't use costs nothing. One adds about 164 B of brotli with all tiers in. `sideEffects` lists only CSS, so a named import ships that icon.
@@ -41,7 +45,7 @@ One scheme, generated from the art with its types:
41
45
  | `BellIconSolid` | `Icons.BellSolid` | `solid` | the solid form |
42
46
  | `BellIconAction` | `Icons.BellAction` | `action` | has a built-in animation |
43
47
 
44
- The tier is a prop, never a name. An open mark, like an arrow, has a notched badge as its solid, with the mark cut out. Pick it on the sheet: `npm run sheet` in the package writes `contact-sheet.html`, with tier, variant, size, theme and search. `dither-sheet.html` compares the treatments.
48
+ The tier is a prop, never a name. Hollow and solid describe the object: an open mark like an arrow is an outlined arrow in hollow and a filled one in solid, never a badge. Pick it on the sheet: `npm run sheet` in the package writes `contact-sheet.html`, with tier, variant, size, theme and search. `dither-sheet.html` compares the treatments.
45
49
 
46
50
  ## A wrong name
47
51
 
@@ -69,9 +73,11 @@ A remote icon renders an empty `<svg data-icon data-loading>` of the right size
69
73
  Five icons have a built-in animation: `trash` (the lid lifts), `bell` (it rings), `check` (it draws on), `refresh` (it spins), `copy` (the sheet stacks).
70
74
 
71
75
  ```tsx
72
- <Icon name="trash" action /> // hover, focus and press, on the icon or its parent
76
+ // Hover, focus and press on the icon or its parent.
77
+ <Icon name="trash" action />
73
78
  <Icon name="refresh" action="on" /> // now; refresh keeps spinning
74
- <TrashIconAction action="press" /> // 'hover' | 'focus' | 'press' | 'on'
79
+ // 'hover' | 'focus' | 'press' | 'on'
80
+ <TrashIconAction action="press" />
75
81
  ```
76
82
 
77
83
  It's CSS alone, and each one's stylesheet ships only if that icon is used (about 230 B of CSS and 50 B of JS, brotli). Outlines ease; pixels move in whole cells, in frames. warp's motion dial and `prefers-reduced-motion` turn it off. An action on another icon is I001.
package/docs/lists.md CHANGED
@@ -11,7 +11,9 @@
11
11
  every row re-runs on every change (1 DOM write, hundreds of re-runs). Replace only what changed:
12
12
 
13
13
  ```ts
14
- products.value = products.value.map(p => (p.id === id ? { ...p, price } : p))
14
+ products.value = products.value.map(p =>
15
+ p.id === id ? { ...p, price } : p,
16
+ )
15
17
  ```
16
18
 
17
19
  The compiler sees this inside one file; `weft check` follows the box through props into other files.
package/docs/meta.md CHANGED
@@ -11,7 +11,11 @@ on what the reader gets, from what is on the page:
11
11
  ```tsx
12
12
  import type { Meta } from '@weft-js/start'
13
13
 
14
- export const meta: Meta = { title: 'Pricing', description: 'Three plans, billed monthly, and what each one includes.' }
14
+ export const meta: Meta = {
15
+ title: 'Pricing',
16
+ description: 'Three plans, billed monthly, ' +
17
+ 'and what each one includes.',
18
+ }
15
19
  ```
16
20
 
17
21
  On a param route, build it from `data` and `params`, so each page says what it is:
@@ -22,8 +26,14 @@ On a param route, build it from `data` and `params`, so each page says what it i
22
26
  import { notFound, type LoadArgs, type Meta } from '@weft-js/start'
23
27
  import { getUser } from '#/server/users'
24
28
 
25
- export const load = async ({ params }: LoadArgs<'/users/[id]'>) => (await getUser.fetch({ id: params.id })) ?? notFound()
26
- export const meta: Meta<typeof load> = ({ data }) => ({ title: data.name, description: data.bio })
29
+ export const load = async ({ params }: LoadArgs<'/users/[id]'>) => {
30
+ const user = await getUser.fetch({ id: params.id })
31
+ return user ?? notFound()
32
+ }
33
+ export const meta: Meta<typeof load> = ({ data }) => ({
34
+ title: data.name,
35
+ description: data.bio,
36
+ })
27
37
  ```
28
38
 
29
39
  `meta` is plain data: it runs once per request after the loaders and before the first byte. It can't read a box (H006)