@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/docs/plugins.md CHANGED
@@ -8,8 +8,8 @@ own diagnostics and `weft add` steps. It uses only what any plugin can use, and
8
8
 
9
9
  ```
10
10
  packages/pay/ packages/pay/ (`src/stripe.ts`)
11
- plugin.mjs the manifest plugin.mjs a `pay.rail` provider
12
- src/index.ts client-safe src/index.ts stripeRail(options)
11
+ plugin.mjs the manifest plugin.mjs `pay.rail` provider
12
+ src/index.ts client-safe src/index.ts stripeRail(options)
13
13
  src/server.ts the ledger
14
14
  src/test.ts fakePay, payRailSuite
15
15
  src/checks.mjs PAY-codes
@@ -24,8 +24,12 @@ A rail is how a payment happens. `fakePay` is a second rail, which is how you kn
24
24
 
25
25
  ```js
26
26
  export const pay = definePlugin({
27
- name: '@weft-js/pay', version: '0.1.0', weft: '*', namespace: 'PAY', permissions: {},
28
- contribute: () => ({ provides: [...], diagnostics, check, docs, setup, agents, catalog, map }),
27
+ name: '@weft-js/pay',
28
+ version: '0.1.0', weft: '*', namespace: 'PAY', permissions: {},
29
+ contribute: () => ({
30
+ provides: [...], diagnostics, check, docs, setup,
31
+ agents, catalog, map,
32
+ }),
29
33
  })
30
34
  ```
31
35
 
@@ -42,8 +46,18 @@ imports lazily, on that side only. That's why an app that doesn't use a plugin c
42
46
  Pay provides `server.extension`, the slot for anything that needs a server presence:
43
47
 
44
48
  ```js
45
- provide(defineSlot({ id: 'server.extension', version: 1, cardinality: 'keyed', sides: ['server'] }),
46
- { key: 'pay', sides: { server: { module: '@weft-js/pay/server', export: 'extension' } } })
49
+ provide(
50
+ defineSlot({
51
+ id: 'server.extension', version: 1,
52
+ cardinality: 'keyed', sides: ['server'],
53
+ }),
54
+ {
55
+ key: 'pay',
56
+ sides: { server: {
57
+ module: '@weft-js/pay/server', export: 'extension',
58
+ } },
59
+ },
60
+ )
47
61
  ```
48
62
 
49
63
  The key is the mount: `/_weft/x/pay/...`. A keyed provider also needs `src/<key>.server.ts`, the app's own configuration, and
@@ -82,7 +96,11 @@ and a good template for a provider.
82
96
  `diagnostics` declares each code: severity, what it catches, the fix. `check` finds them:
83
97
 
84
98
  ```js
85
- check: { include: ['src/**/*.{ts,tsx}'], run: ctx => import('./src/checks.mjs').then(c => c.run(ctx)) }
99
+ check: {
100
+ include: ['src/**/*.{ts,tsx}'],
101
+ run: ctx => import('./src/checks.mjs')
102
+ .then(c => c.run(ctx)),
103
+ }
86
104
  ```
87
105
 
88
106
  `run(ctx)` gets `files`, `read`, `exists`, `jsx`, `slots`, and returns findings: `{ code, file, start, end, why, help?, fix? }`.
@@ -108,9 +126,14 @@ sees what a user sees. It's imported from `@weft-js/pay/test`, since core can't
108
126
  ```js
109
127
  export const setup = {
110
128
  with: ['@weft-js/pay/stripe'], // added too
111
- needs: ['db', 'auth'], // refused, with nothing changed, until they're there
129
+ // Refused without changes until these are present.
130
+ needs: ['db', 'auth'],
112
131
  run({ engine, layer, read }) {
113
- return { files: {...}, edits: {...}, env: { STRIPE_SECRET_KEY: 'sk_test_placeholder' }, next: ['...'] }
132
+ return {
133
+ files: {...}, edits: {...},
134
+ env: { STRIPE_SECRET_KEY: 'sk_test_placeholder' },
135
+ next: ['...'],
136
+ }
114
137
  },
115
138
  }
116
139
  ```
package/docs/props.md CHANGED
@@ -7,10 +7,21 @@ Leave a component's props unannotated. weft infers them from its callers and its
7
7
 
8
8
  ```tsx
9
9
  function Row(props) {
10
- return <li><button type="button" onClick={props.onPick}>{props.label.toUpperCase()}</button></li>
10
+ const { label, onPick } = props
11
+ return (
12
+ <li>
13
+ <button type="button" onClick={onPick}>
14
+ {label.toUpperCase()}
15
+ </button>
16
+ </li>
17
+ )
11
18
  }
12
19
  export default function List() {
13
- return <ul><Row label="One" onPick={() => console.log('picked one')} /></ul>
20
+ return (
21
+ <ul>
22
+ <Row label="One" onPick={() => console.log('picked one')} />
23
+ </ul>
24
+ )
14
25
  }
15
26
  ```
16
27
 
@@ -0,0 +1,26 @@
1
+ # Reading reactive values
2
+
3
+ `state(initial)` returns a writable `State<T>`. Read or assign its `.value`.
4
+ `derived(fn)` returns a read-only `Derived<T>`. Read `.value` to evaluate it and
5
+ track its dependencies.
6
+
7
+ Use `memo(fn)` when a caller needs an accessor instead of a box:
8
+
9
+ ```ts
10
+ import { derived, memo, state } from '@weft-js/core'
11
+
12
+ const count = state(1)
13
+ const doubled = derived(() => count.value * 2)
14
+ const readDoubled = memo(() => count.value * 2)
15
+
16
+ console.log(doubled.value, readDoubled()) // 2, 2
17
+ ```
18
+
19
+ Both forms cache their result and refresh it when a dependency changes. `memo`
20
+ returns a function, so call it; it has no `.value` property. The compiler also uses
21
+ this accessor form to cache component children.
22
+
23
+ For side effects, `watch(source, callback)` tracks reads in `source`. Keep the
24
+ source pure and give reactive work an owner with a component or `createRoot`.
25
+ Graph tracking, mount queues and renderer helpers belong to
26
+ `@weft-js/core/internal`; application code uses the public lifecycle APIs.
package/docs/rules.md CHANGED
@@ -1,32 +1,39 @@
1
1
  ```tsx
2
2
  import { derived } from '@weft-js/core'
3
3
 
4
- export function Cart({ items, taxRate }: { items: { id: string; name: string; price: number }[]; taxRate: number }) {
5
- const subtotal = derived(() => items.reduce((s, i) => s + i.price, 0))
4
+ export function Cart({ items, taxRate }: {
5
+ items: { id: string; name: string; price: number }[]
6
+ taxRate: number
7
+ }) {
8
+ const subtotal = derived(() =>
9
+ items.reduce((s, i) => s + i.price, 0),
10
+ )
6
11
  const total = derived(() => subtotal.value * (1 + taxRate))
7
12
  return (
8
13
  <section>
9
- <ul>{items.map(item => <li key={item.id}>{item.name}</li>)}</ul>
14
+ <ul>
15
+ {items.map(item => <li key={item.id}>{item.name}</li>)}
16
+ </ul>
10
17
  <p>{total.value.toFixed(2)}</p>
11
18
  </section>
12
19
  )
13
20
  }
14
21
  ```
15
22
 
16
- 1. **State is a box. Read and write `.value`.** Everywhere, JSX included (W015).
17
- 2. **Anything computed from other values is `derived`.** Not in a `watch` or the component body (W001).
18
- 3. **JSX is always live.** Every `{expr}` and attribute re-evaluates when what it reads changes.
19
- 4. **Props are always current.** Destructure them (one level, W006). Read them in `derived`, `watch`, JSX or handlers.
20
- `const x = props.y` is a live read-only alias. `let x = props.y` and `props.y =` write locally; only `bind:y={box}`
23
+ 1. **State is a box. Read and write `.value`**, including in JSX (W015).
24
+ 2. **Compute from other values with `derived`**, not `watch` or the component body (W001).
25
+ 3. **JSX is live.** Each `{expr}` and attribute re-evaluates when its inputs change.
26
+ 4. **Props are current.** Destructure one level (W006). Read in `derived`, `watch`, JSX or handlers.
27
+ `const x = props.y` is a live read-only alias; `let x = props.y` and `props.y =` write locally; only `bind:y={box}`
21
28
  writes out.
22
- 5. **The component body runs once.** No early returns on state (W040), no `let` counters (W041). Branch in JSX.
29
+ 5. **The body runs once.** No state-based early returns (W040) or `let` counters (W041). Branch in JSX.
23
30
  6. **Pass a box only to give write access** (`value={coupon}`, child prop typed `State<string | null>`), or so list
24
31
  rows can read one entry: `cart.get(p.id)` re-runs one row, `cart[p.id]` or `cart.value[p.id]` re-runs them all
25
32
  (W017), in the list and in the row component alike.
26
- 7. **Lists and conditionals the way you know them**: `.map` with `key` (W010), `&&`, ternaries. Keep list items
27
- stable: replace only the item that changed (`p.id === id ? { ...p, price } : p`), never `({ ...p })` every item
33
+ 7. **Lists and conditionals**: `.map` with `key` (W010), `&&`, ternaries. Keep items
34
+ stable: replace only the changed item (`p.id === id ? { ...p, price } : p`), never `({ ...p })` every item
28
35
  (W014).
29
36
  8. **Copy a prop into state with `state.linked(() => prop)`**, not `state(prop)` (W005). `untrack(() => …)` is a
30
- snapshot that never updates: not for anything you render (W018).
37
+ snapshot that never updates: never render it (W018).
31
38
  9. **Side effects go in `watch(() => source, value => cleanup?)`**; DOM work in `onMount`. `flush()` applies pending
32
39
  DOM updates now.
package/docs/rust.md CHANGED
@@ -37,10 +37,16 @@ import { z } from 'zod'
37
37
  import { search } from './rust'
38
38
 
39
39
  export const searchDocs = query({
40
- input: z.object({ q: z.string().trim().min(1).max(200), limit: z.int().min(1).max(50).default(10) }),
41
- output: z.array(z.object({ id: z.string(), title: z.string(), score: z.number() })),
40
+ input: z.object({
41
+ q: z.string().trim().min(1).max(200),
42
+ limit: z.int().min(1).max(50).default(10),
43
+ }),
44
+ output: z.array(z.object({
45
+ id: z.string(), title: z.string(), score: z.number(),
46
+ })),
42
47
  tags: ['docs'],
43
- impl: search, // was: run: ({ input }) => rank(input.q, input.limit)
48
+ // Was: run: ({ input }) => rank(input.q, input.limit)
49
+ impl: search,
44
50
  })
45
51
  ```
46
52
 
@@ -54,8 +60,10 @@ serde = { version = "1", features = ["derive"] }
54
60
  tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
55
61
  weft = { path = "../../node_modules/@weft-js/start/crate" }
56
62
 
57
- [build-dependencies]
58
- weft = { path = "../../node_modules/@weft-js/start/crate", default-features = false, features = ["build"] }
63
+ [build-dependencies.weft]
64
+ path = "../../node_modules/@weft-js/start/crate"
65
+ default-features = false
66
+ features = ["build"]
59
67
  ```
60
68
 
61
69
  ```rust
@@ -65,13 +73,18 @@ fn main() { weft::build::generate("search"); }
65
73
 
66
74
  ```rust
67
75
  // crates/search/src/main.rs
68
- weft::contract!(); // types and a `Service` trait, generated from your Zod: never hand-written
76
+ // Types and the `Service` trait are generated from Zod.
77
+ weft::contract!();
69
78
  use contract::*;
70
79
 
71
80
  struct Search;
72
81
 
73
82
  impl Service for Search {
74
- async fn search_docs(&self, ctx: weft::Ctx, input: SearchDocsInput) -> Result<SearchDocsOutput, weft::Error> {
83
+ async fn search_docs(
84
+ &self,
85
+ ctx: weft::Ctx,
86
+ input: SearchDocsInput,
87
+ ) -> Result<SearchDocsOutput, weft::Error> {
75
88
  ctx.touch("docs");
76
89
  Ok(vec![])
77
90
  }
@@ -97,7 +110,11 @@ use contract::*;
97
110
 
98
111
  struct Media;
99
112
  impl Service for Media {
100
- async fn thumbnail(&self, ctx: weft::Ctx, input: ThumbnailInput) -> Result<ThumbnailOutput, weft::Error> { /* … */ }
113
+ async fn thumbnail(
114
+ &self,
115
+ ctx: weft::Ctx,
116
+ input: ThumbnailInput,
117
+ ) -> Result<ThumbnailOutput, weft::Error> { /* … */ }
101
118
  }
102
119
 
103
120
  weft::native!(contract::functions(Media));
@@ -106,7 +123,8 @@ weft::native!(contract::functions(Media));
106
123
  ```ts
107
124
  // src/server/rust.ts
108
125
  import { native } from '@weft-js/start/rust'
109
- export const media = native('media') // options: timeout, claims, validate, as for remote()
126
+ // Options: timeout, claims, validate, as for remote().
127
+ export const media = native('media')
110
128
  ```
111
129
 
112
130
  - An addon runs each call on the worker pool, so a long one doesn't hold the event loop. `native()` sizes the pool to the
@@ -147,13 +165,24 @@ All three have `ctx.touch(table)`, `ctx.tag(tag)`, `ctx.remaining()`, `ctx.cance
147
165
 
148
166
  <!-- example: skip a fragment of src/server/docs.ts -->
149
167
  ```ts
150
- export const getDoc = query({ input, output, errors: { NotFound: z.object({ id: z.string() }) }, impl: search })
151
- // a TS body raises the same thing: run: () => raise('NotFound', { id })
168
+ export const getDoc = query({
169
+ input, output,
170
+ errors: { NotFound: z.object({ id: z.string() }) },
171
+ impl: search,
172
+ })
173
+ // A TS body raises the same thing:
174
+ // run: () => raise('NotFound', { id })
152
175
  ```
153
176
 
154
177
  ```rust
155
- async fn get_doc(&self, ctx: weft::Ctx, input: GetDocInput) -> Result<GetDocOutput, GetDocError> {
156
- Err(GetDocError::NotFound(GetDocNotFound { id: input.id.to_string() }))
178
+ async fn get_doc(
179
+ &self,
180
+ ctx: weft::Ctx,
181
+ input: GetDocInput,
182
+ ) -> Result<GetDocOutput, GetDocError> {
183
+ Err(GetDocError::NotFound(GetDocNotFound {
184
+ id: input.id.to_string(),
185
+ }))
157
186
  }
158
187
  ```
159
188
 
@@ -189,11 +218,12 @@ request id, and the detail stays in the sidecar's log.
189
218
  ```ts
190
219
  // weft.config.ts
191
220
  rust: {
192
- unions: 'allow', // let plain object unions through (R102)
221
+ unions: 'allow', // let plain object unions through (R102)
193
222
  services: {
194
- search: { crate: 'native/search' }, // default: crates/<name>
195
- core: { external: true }, // built and run elsewhere: no cargo here
196
- imaging: { wasm: false }, // links C, and this host has an addon: no fallback (R104)
223
+ search: { crate: 'native/search' }, // default: crates/<name>
224
+ core: { external: true }, // built and run elsewhere: no cargo
225
+ // Links C; this host has an addon, so no fallback (R104).
226
+ imaging: { wasm: false },
197
227
  },
198
228
  }
199
229
  ```
@@ -1,8 +1,9 @@
1
- `@weft-js/start` app: file routes in `src/routes` (`_layout.tsx`, `[id].tsx`, `load`, `meta`), server functions in `src/server/**` or `*.server.ts`. Read `weft docs start` first. Import your own code as `#/server/todos` (`src/server/todos`); generated files are `weft:slot/<id>` and `weft:tokens/<name>`.
1
+ `@weft-js/start` app: file routes in `src/routes` (`_layout.tsx`, `[id].tsx`, `load`, `meta`), server functions in `src/server/**` or `*.server.ts`. Import your own code as `#/server/todos` (`src/server/todos`); generated files are `weft:slot/<id>` and `weft:tokens/<name>`.
2
2
 
3
3
  - **Reads:** `query({ tags: ['todos'], auth: 'user', run: ({ ctx }) => ctx.db.select().from(todos) })`. In a component `todos().value ?? []`; in `load` and handlers `await todos.fetch()`.
4
4
  - **Writes:** `mutation({ input, auth: 'user', invalidates: ['todos'], optimistic, run })`.
5
5
  - **Database:** Drizzle; `await` every statement. Schema change: `weft db generate` (Q015). Inputs: `schemas.todos.insert.pick({ title: true })`.
6
- - **Auth:** `ctx.user` is the user or `null`; `auth: 'user'` refuses anyone else (A002). A guard is `redirect('/login')` in a layout's `load`. `run` must still check who owns a row.
6
+ - **Auth:** `ctx.user` is the user or `null`; `auth: 'user'` refuses anyone else (A002). A guard is `redirect('/login')` in a layout's `load`. `run` must still check who owns a row. Login is a database session: a signed cookie caches it for pages for 5 minutes, and mutations read the database (`session.cookieCache` in `createAuth`). JWTs are API-only (`api: { functions }`).
7
7
  - **Never** copy query results into `state`, import `db` from a component (Q012), `fetch` `/_weft/*`, or poll (`todos.live()`; rooms: `channel`).
8
+ - **Navigation:** chrome shared across sections (sidebar, tabs) lives in the one layout above them, following `currentPath()`; a `_layout.tsx` per section is replaced on every move between sections. Route changes show a skeleton from the page's markup; `export const skeleton = Named` (a local function of plain markup: div/section/p/h1/ul/li…, class and data-* only) overrides it.
8
9
  - **Every page sets `meta` as you build it**, from `data` and `params`: `weft docs meta`.