create-hozu 0.4.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -29,6 +29,7 @@ export declare const packageJson: (name: string, version: string) => {
29
29
  '@hozu/data': string;
30
30
  '@hozu/runtime-server': string;
31
31
  '@hozu/schema-zod': string;
32
+ '@hozu/transform': string;
32
33
  zod: string;
33
34
  };
34
35
  devDependencies: {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAA;AAChD,MAAM,MAAM,MAAM,GAAG,WAAW,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAA;AAE1D,eAAO,MAAM,MAAM,EAAE,SAAS,KAAK,EAAiC,CAAA;AAGpE,eAAO,MAAM,YAAY,QAAsB,CAAA;AAiB/C,eAAO,MAAM,QAAQ,cAAe,MAAM,GAAG,SAAS,KAAG,MAGxD,CAAA;AAWD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAwBzG;AAYD,MAAM,WAAW,aAAc,SAAQ,YAAY;IACjD,KAAK,EAAE,KAAK,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,eAAO,MAAM,WAAW,SAAU,MAAM,WAAW,MAAM;;;;;QAKrD,KAAK;QACL,KAAK;QACL,KAAK;;;QAGL,oBAAoB;QACpB,YAAY;QACZ,WAAW;QACX,YAAY;QACZ,sBAAsB;QACtB,kBAAkB;QAClB,GAAG;;;QAGH,WAAW;QACX,eAAe;QACf,aAAa;QACb,UAAU;;;QAED,IAAI;;CACf,CAAA;AAEF,wBAAsB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAmBtF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAA;AAChD,MAAM,MAAM,MAAM,GAAG,WAAW,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAA;AAE1D,eAAO,MAAM,MAAM,EAAE,SAAS,KAAK,EAAiC,CAAA;AAGpE,eAAO,MAAM,YAAY,QAAsB,CAAA;AAiB/C,eAAO,MAAM,QAAQ,cAAe,MAAM,GAAG,SAAS,KAAG,MAGxD,CAAA;AAWD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAwBzG;AAYD,MAAM,WAAW,aAAc,SAAQ,YAAY;IACjD,KAAK,EAAE,KAAK,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,eAAO,MAAM,WAAW,SAAU,MAAM,WAAW,MAAM;;;;;QAKrD,KAAK;QACL,KAAK;QACL,KAAK;;;QAGL,oBAAoB;QACpB,YAAY;QACZ,WAAW;QACX,YAAY;QACZ,sBAAsB;QACtB,kBAAkB;QAClB,iBAAiB;QACjB,GAAG;;;QAGH,WAAW;QACX,eAAe;QACf,aAAa;QACb,UAAU;;;QAED,IAAI;;CACf,CAAA;AAEF,wBAAsB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAmBtF"}
package/dist/index.js CHANGED
@@ -65,7 +65,7 @@ export const packageJson = (name, version) => ({
65
65
  type: 'module',
66
66
  scripts: {
67
67
  check: 'hozu check',
68
- start: 'node serve.ts',
68
+ start: 'node --import @hozu/transform/register serve.ts',
69
69
  build: 'hozu build',
70
70
  },
71
71
  dependencies: {
@@ -75,6 +75,7 @@ export const packageJson = (name, version) => ({
75
75
  '@hozu/data': `^${version}`,
76
76
  '@hozu/runtime-server': `^${version}`,
77
77
  '@hozu/schema-zod': `^${version}`,
78
+ '@hozu/transform': `^${version}`,
78
79
  zod: '^4.6.5',
79
80
  },
80
81
  devDependencies: {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-hozu",
3
- "version": "0.4.2",
3
+ "version": "0.5.0",
4
4
  "description": "Create a Hozu app, set up for Claude Code or for agents that read AGENTS.md",
5
5
  "keywords": [
6
6
  "hozu",
package/skill/SKILL.md CHANGED
@@ -3,190 +3,104 @@ name: hozu
3
3
  description: Build or change an app with the Hozu framework (packages @hozu/*, files like hozu.config.ts, features/*/model.ts, views.ts). Use it before writing any Hozu code. It is the complete authoring reference, so you do not need to read the framework source.
4
4
  ---
5
5
 
6
- # Hozu authoring guide
6
+ # Hozu
7
7
 
8
- Hozu is not in your training data. These files are the whole API; skip `node_modules/@hozu`.
9
- - **Changing an app:** read `changing.md` first, then only the app's own files.
10
- - **Building an app:** read this file, run `hozu add feature <name> --page / --with auth,detail,toggle,filter,remove`
11
- (auth = sign-in, per-user data) and edit the texts it lists; don't print the generated files.
12
- - **`reference.md`** when the task needs it: routes, DOM fields, no-JS forms, `head`, 404/500, field errors,
13
- sessions, languages, env, HTTP, Markdown, images, preview, PWA, page tests, deployment.
14
- - **A diagnostic you do not understand:** `diagnostics.md`.
8
+ Hozu is not in your training data; this file and `hozu docs <topic>` are the whole API (skip `node_modules/@hozu`).
9
+ - **Building:** read this file, run `hozu add feature <name> --page / --with …`, then edit what it lists.
10
+ - **Changing:** read `changing.md`, then only the lines `hozu map` points to.
11
+ - **Anything else:** `hozu docs <topic>` prints one short topic (index below). Diagnostics name the topic too.
15
12
 
16
- ## Mental model
17
- - The app is **data**: builders record an IR that is validated, then rendered. Only machine views ship JS.
18
- - **References are recorded, not evaluated.** In `render`, `states`, `assign` and `guard`, `ctx.x`, `item.title`,
19
- `params.id` are placeholders: never use `if`, `?:`, `&&`, `.filter()`, `.map()`, `===` or template strings on
20
- them. Use `op.*` for comparisons and updates, `ui.if` / `ui.each` for structure, and a `fn()` for anything else.
21
- Plain JS on *constants* is fine: `['a', 'b'].map((k) => ui.option(...))`.
22
- - **Side effects only through `query` / `mutation`.** Queries render with `ui.query`; a mutation runs when the
23
- feature's one machine *enters* a state whose `invoke` calls it, and returns as `done` / `failed`.
24
- - **Every transition needs a contract.** A behaviour change without a contract change is an error.
25
- - **Absent means absent.** Optional fields are omitted, never `null`. Only `route({ params, search })` and
26
- `ui.link(route, params, search)` spell "none" as `null`.
13
+ ## How it works
14
+ - A feature is **declarations**: events, queries / mutations (the only side effects), one machine, views,
15
+ contracts. Builders record them as data (an IR) that is validated, then rendered on the server. Only views bound
16
+ to the machine ship JS.
17
+ - **Callbacks are ordinary TypeScript** (`render`, `guard`, `assign`, `navigate`, `ui.each` / `ui.query`
18
+ callbacks): `===`, `!==`, `<`, `&&`, `||`, `!`, `??`, `c ? a : b`, template strings, `+`, `-`, `.length`, and in
19
+ `assign`, `ctx.x = v`, `ctx.n += 1`, `ctx.list.push(v)`, `ctx.list = ctx.list.filter((i) => i.id !== e.id)`.
20
+ Methods on data (`.map`, `.toUpperCase()`…) are not: use `ui.each` for lists and a `fn()` for computation.
21
+ - A mutation runs when the machine **enters** a state whose `invoke` calls it; that state drops other events, and
22
+ `done` / `failed` leave it. Contracts are needed only where a transition decides (a guard, `navigate`, a `fn`).
27
23
 
28
- ## Files
24
+ ## Files and commands
29
25
  ```
30
- hozu.config.ts project(): schema adapter, site, routes, pages, features
31
- routes.ts route() declarations
32
- server.ts resolvers(project, implement => [...]): query/mutation implementations
33
- serve.ts createServer({ build, styles, resolvers }).listen(PORT)
34
- app.css @import "tailwindcss";
35
- features/<name>/
36
- model.ts schemas, events, query / mutation / tag / fn, the machine
37
- views.ts views, contracts, feature()
26
+ hozu.config.ts project({ schema, site, routes, pages, features }) routes.ts route() declarations
27
+ features/<name>/model.ts schemas, events, effects, machine views.ts views, contracts, feature()
28
+ features/<name>/server.ts implement(...) resolvers serve.ts createServer(...)
38
29
  ```
39
-
40
- ## Commands (from the app directory)
41
30
  ```
42
- pnpm exec hozu check # after every edit: types, rules, contracts
43
- pnpm exec hozu check --update-lock # accept an intended behaviour change
44
- pnpm exec hozu add feature items --page / --with detail,toggle # a working feature, wired in
45
- pnpm exec hozu map # outline of the app with file:line
46
- pnpm exec hozu get / --select button --forms # pages without a server: text, attributes, forms
47
- pnpm exec hozu post / --field title=A --next 'POST / title=a' --next / # submit a form like a browser
31
+ npx hozu check # after every edit: types, rules, contracts
32
+ npx hozu check --update-lock # accept an intended behaviour change
33
+ npx hozu add feature items --page / --with auth,detail,toggle,filter,remove
34
+ npx hozu map # outline with file:line
35
+ npx hozu get / --select button --forms # a page, no server needed
36
+ npx hozu post / --field title=A --next / # a no-JS form post, cookies shown
37
+ npx hozu docs views # one topic
48
38
  ```
49
- Diagnostics give `file:line`, cause and fix: apply the fix. `get` / `post` start from fresh in-memory data;
50
- chain steps with `--next`. Run `node serve.ts` only to use the app. Relative imports end in `.ts`.
39
+ `get` / `post` replace a running server for checks; `npm start` runs the app. Relative imports end in `.ts`.
51
40
 
52
- ## model.ts
41
+ ## A feature in one screen
53
42
  ```ts
54
- export const Item = z.object({ id: z.string(), title: z.string(), read: z.boolean() })
43
+ // model.ts
44
+ export const Item = z.object({ id: z.string(), title: z.string(), done: z.boolean() })
55
45
  export const Add = event({ payload: z.object({ title: z.string() }) })
56
-
57
- export const itemsTag = tag({ param: null }) // or tag({ param: z.string() }) → itemsTag(x)
58
- export const listItems = query({
59
- input: z.object({}), output: z.array(Item),
60
- scope: 'public', // 'user' = per-session data (needs project session)
61
- freshness: 'static', // | { revalidate: s } | { swr: s } | 'live'
62
- tags: () => [itemsTag()], // optional; (input) => [...]
63
- })
64
- export const getItem = query({ input: Key, output: Item, errors: { NotFound: Key }, … })
65
- export const addItem = mutation({
66
- input: z.object({ title: z.string().min(2, 'Use at least 2 characters') }), output: Item,
67
- errors: { Duplicate: z.object({ title: z.string() }) }, // optional: declared failures
68
- invalidates: () => [itemsTag()], // refreshes queries with these tags
69
- })
70
- export const unread = fn({ // pure JS, self-contained: no imports or closures
71
- input: z.object({ items: z.array(Item) }), output: z.array(Item),
72
- impl: ({ items }) => items.filter((i) => !i.read),
73
- })
74
-
75
- export const m = machine({
46
+ export const itemsTag = tag({ param: null })
47
+ export const listItems = query({ input: z.object({}), output: z.array(Item), scope: 'public',
48
+ freshness: 'static', tags: () => [itemsTag()] })
49
+ export const addItem = mutation({ input: z.object({ title: z.string().min(2, 'Too short') }), output: Item,
50
+ errors: { Duplicate: z.object({ title: z.string() }) }, invalidates: () => [itemsTag()] })
51
+ export const items = machine({
76
52
  context: z.object({ draft: z.string(), error: z.string().nullable() }),
77
53
  initialContext: { draft: '', error: null },
78
54
  initial: 'idle',
79
55
  states: ({ ctx }) => ({
80
- idle: {
81
- on: [
82
- on(Draft, { target: 'idle', assign: (e) => [op.set(ctx.draft, e.text)] }),
83
- on(Add, { target: 'adding', guard: (e) => op.neq(e.title, ''), // first matching guard wins
84
- assign: (e) => [op.set(ctx.draft, e.title), op.set(ctx.error, null)] }),
85
- ],
86
- },
56
+ idle: { on: [on(Add, { target: 'adding', assign: (e) => { ctx.draft = e.title; ctx.error = null } })] },
87
57
  adding: {
88
- ignore: [Draft, Add], // events dropped while busy (HZ005)
89
- invoke: invoke(addItem, { // runs on entering the state
58
+ invoke: invoke(addItem, {
90
59
  input: { title: ctx.draft },
91
- done: [{ target: 'idle', assign: () => [op.set(ctx.draft, '')],
92
- navigate: (r) => ui.link(itemPage, { id: r.id }) }], // optional
93
- failed: { // every declared error + Unexpected
94
- Duplicate: [{ target: 'idle', assign: () => [op.set(ctx.error, 'Already exists')] }],
95
- Unexpected: [{ target: 'idle', assign: (e) => [op.set(ctx.error, e.message)] }],
60
+ done: { target: 'idle', assign: () => { ctx.draft = '' } },
61
+ failed: {
62
+ Duplicate: { target: 'idle', assign: () => { ctx.error = 'Already listed' } },
63
+ Unexpected: { target: 'idle', assign: (e) => { ctx.error = e.message } },
96
64
  },
97
65
  }),
98
66
  },
99
- flash: { after: [{ ms: 3000, target: 'idle' }] }, // timers; `final: true` for terminal states
100
67
  }),
101
68
  })
102
- ```
103
- - `op.set`, `op.append(list, item)`, `op.inc(n, by)`, `op.removeWhere(list, 'key', value)`.
104
- - Guards: `op.eq / neq / lt / lte / gt / gte`, `op.and(...)`, `op.or(...)`, `op.not(g)`, or a boolean `fn`. The
105
- reference goes on the left: `op.eq(ctx.tab, 'design')`.
106
- - A transition to the same state re-enters it and re-runs its `invoke`; busy states `ignore` instead.
107
-
108
- ## views.ts
109
- ```ts
69
+ // views.ts
110
70
  export const Board = ui.view({
111
- machine: m, // optional: without it, no ctx / when / events, 0 JS
112
- route: home, // optional: render gets { params, search } typed by the route
113
- render: ({ ctx, when, search }) =>
71
+ machine: items,
72
+ render: ({ ctx, when }) =>
114
73
  ui.main({ class: 'mx-auto max-w-xl' }, [
115
74
  ui.form({ on: { submit: ui.send(Add, { title: ui.dom.form('title') }) } }, [
116
- ui.input({ name: 'title', required: true, value: ctx.draft,
117
- on: { input: ui.send(Draft, { text: ui.dom.value }) } }),
75
+ ui.input({ name: 'title', required: true, value: ctx.draft }),
118
76
  ui.button({ type: 'submit' }, ['Add']),
119
77
  ]),
120
- ui.if(op.neq(ctx.error, null), [ui.p({ role: 'alert' }, [ctx.error])], []),
121
- when(['adding'], [ui.p({ 'aria-busy': 'true' }, ['Adding…'])]),
78
+ ctx.error !== null && ui.p({ role: 'alert' }, [ctx.error]),
79
+ when(['adding'], [ui.p({ 'aria-busy': 'true' }, [`Adding ${ctx.draft}…`])]),
122
80
  ui.query(listItems, {}, {
123
- ready: (items) => ui.ul({}, [ui.each(items, 'id', (i) =>
124
- ui.li({}, [ui.a({ href: ui.link(itemPage, { id: i.id }) }, [i.title])]))]),
125
- pending: ui.p({}, ['Loading…']), // or null
81
+ ready: (list) => ui.ul({}, [ui.each(list, 'id', (i) => ui.li({}, [i.title, i.done ? ' ✓' : '']))]),
126
82
  failed: { Unexpected: () => ui.p({ role: 'alert' }, ['Unavailable']) },
127
83
  }),
128
84
  ]),
129
85
  })
86
+ export const todos = feature({ id: 'todos', intent: { summary: 'A to-do list' },
87
+ declarations: { Add, itemsTag, listItems, addItem, items, Board } })
130
88
  ```
131
- - `ui.<tag>(attrs, children)`; values are literals, references or guards. `ui.each(list, 'id', (item) => node)`.
132
- - `class` is a static string of Tailwind classes that must exist (HZ026). Conditional classes:
133
- `toggle: { 'bg-indigo-600 text-white': op.eq(ctx.tab, t) }`. CSS variables: `vars: { '--hue': item.hue }`.
134
- - Events: `on: { click: ui.send(Event, payload) }`. Payload fields: literals, references, `ui.dom.value`,
135
- `ui.dom.form('name')`, `ui.dom.checked`, `ui.dom.valueAsNumber`, `ui.dom.key`.
136
- - Links: `ui.link(route, params, search)`, never a string path (HZ032). The third argument exists only when the
137
- route declares `search` (`null` = all defaults). Filters that belong in the URL are `search` links, not context.
138
- - A form whose submit reads only `ui.dom.form(...)`, literals, context, params and search also works without JS.
139
-
140
- ### Contracts
141
- ```ts
142
- export const adds = contract(m, {
143
- given: { state: 'idle' }, // context: optional, defaults to initialContext
144
- when: [
145
- { send: Add, payload: { title: 'A' } },
146
- { done: addItem, result: { id: 'i9', title: 'A', read: false } },
147
- ], // or { failed: addItem, error: 'Duplicate', data } / { elapse: ms }
148
- expect: {
149
- state: 'idle',
150
- changes: { error: null }, // only what changes; every other field must stay equal
151
- effects: [{ effect: addItem, input: { title: 'A' } }, { navigate: '/items/i9' }], // optional: none
152
- },
153
- })
154
- ```
155
- Cover each `on`, `done`, `failed` and `after` once. `given.context` sets up a full context; nested objects in
156
- `changes` are patches too, arrays are replaced. HZ016 prints each missing contract ready to paste.
157
-
158
- ### Feature
159
- ```ts
160
- export const items = feature({
161
- id: 'items',
162
- intent: { summary: 'A reading list', invariants: ['Titles are unique'] }, // invariants optional
163
- declarations: { Add, Draft, itemsTag, listItems, getItem, addItem, unread, m, Board, Detail, adds },
164
- })
165
- ```
166
- Every declaration goes in `declarations` once, under its name. Optional: `imports: [otherFeature]`,
167
- `exports: [Event, query, …]` (all other features may use), `styles: [new URL('./x.css', import.meta.url)]`.
168
-
169
- ## hozu.config.ts
170
- `project({ schema: zodAdapter, styles, site: { url, name, lang }, routes: { home, itemPage }, pages, features })`;
171
- `hozu add feature --page` adds static pages. A page with params:
172
- ```ts
173
- ui.page(itemPage, {
174
- views: [Detail],
175
- head: {
176
- query: getItem, // its failure sets the status (NotFound → 404)
177
- input: (params) => ({ id: params.id }),
178
- render: (item) => ({ title: item.title, description: item.title, type: 'article' }),
179
- },
180
- entries: { query: listItems, input: {}, params: (item) => ({ id: item.id }) }, // sitemap
181
- }),
182
- ```
183
- ```ts
184
- // routes.ts
185
- export const home = route({ path: '/', params: null, search: z.object({ show: Show.default('all') }) })
186
- export const itemPage = route({ path: '/items/:id', params: z.object({ id: z.string() }), search: null })
187
- ```
89
+ Every declaration goes in `declarations` once, under its name. Resolvers:
90
+ `implement(addItem, ({ title }, { fail }) => exists ? fail('Duplicate', { title }) : save(title))`.
188
91
 
189
- ## server.ts
190
- `export const createResolvers = () => resolvers(project, (implement) => [...])`, one
191
- `implement(getItem, ({ id }, { fail }) => item ?? fail('NotFound', { id }))` per query / mutation. `hozu add feature` puts a feature's resolvers in `features/<name>/server.ts`.
192
- Input failing its schema returns `Invalid` (`{ message, fields }`; `reference.md`).
92
+ ## Topics (`hozu docs <topic>`)
93
+ | Task | Topic |
94
+ |---|---|
95
+ | elements, attributes, classes, events, DOM fields, lists, links | `views` |
96
+ | states, events, invoke, timers, guards, updates | `machine` |
97
+ | queries, mutations, tags, errors, `fn()`, resolvers | `data` |
98
+ | writing and checking contracts | `contracts` |
99
+ | routes, params, search, pages, `head`, 404, sitemap | `pages` |
100
+ | forms without JS, field errors, selects | `forms` |
101
+ | sign-in, sessions, per-user data | `auth` |
102
+ | common UI: filters, empty states, per-item actions, load more, optimistic | `patterns` |
103
+ | webhooks and JSON APIs | `endpoints` |
104
+ | browser APIs and DOM libraries (maps, charts) | `widgets` |
105
+ | languages, env, HTTP, Markdown, images, preview, PWA, tests, deployment | `i18n`, `env`, `http`, `content`, `testing`, `deploy` |
106
+ | a diagnostic code | `diagnostics` |
package/skill/changing.md CHANGED
@@ -7,7 +7,7 @@ Keep the loop short: map once, edit everything, check once, verify once.
7
7
  - `pnpm exec hozu map`: every route, query, mutation, event, state, view and contract, each with its `file:line`.
8
8
  Open only the lines the change touches. Below, *model* is where the app keeps schemas, events, effects and
9
9
  the machine (`model.ts`), and *views* where it keeps views, contracts and `feature()`.
10
- - The API is in `SKILL.md`. Use a recipe below when one fits; open `patterns.md` / `reference.md` only for
10
+ - The API is in `SKILL.md`. Use a recipe below when one fits; run `hozu docs <topic>` only for
11
11
  something else.
12
12
 
13
13
  ## 2. Recipes
@@ -20,33 +20,28 @@ Names follow `hozu add feature items`: `Item`, `NewItem`, `Add`, `addItem`, `ite
20
20
  - context: `priority: Priority`, with `priority: 'normal'` in `initialContext`;
21
21
  - `fields` gets `priority: z.string().nullable()`, with `priority: null` in `initialContext` and in the `Add`
22
22
  assign that resets it;
23
- - the `Add` assign also gets `op.set(ctx.priority, e.priority)`, and the add `invoke` input becomes
23
+ - the `Add` assign also gets `ctx.priority = e.priority`, and the add `invoke` input becomes
24
24
  `{ title: ctx.draft, priority: ctx.priority }`.
25
25
  - **views:**
26
26
  - the form's submit sends `{ title: ui.dom.form('title'), priority: ui.dom.form('priority') }`;
27
27
  - inside the form add
28
28
  `ui.select({ name: 'priority', 'aria-label': 'Priority', class: 'rounded border px-2' }, ['low', 'normal', 'high'].map((p) => ui.option({ value: p, selected: p === 'normal' }, [p])))`;
29
29
  - in the item: `ui.span({ class: 'text-xs' }, [item.priority])`.
30
- - **Contracts:**
31
- - add `priority: 'normal'` to every `Add` payload, to the add effect's input, and to the `done` result;
32
- - `rejectsInvalid`'s `data.fields` and `changes.fields` get `priority: null`.
30
+ - **Contracts:** if the app has contracts that send `Add` or return an item, add `priority` to their payloads,
31
+ inputs and results. These transitions only copy values, so they need no new contract.
33
32
  - **server:** store `priority` (seed items included) and return it.
34
33
 
35
34
  ### An action button that works on many items (e.g. "Clear done")
36
35
  - **model:**
37
36
  - `export const ClearDone = event({ payload: z.object({}) })`;
38
37
  - `export const clearDone = mutation({ input: z.object({}), output: z.object({ removed: z.number() }), invalidates: () => [itemsTag()] })`;
39
- - in `idle`: `on(ClearDone, { target: 'clearing', assign: () => [op.set(ctx.error, null)] })`;
38
+ - in `idle`: `on(ClearDone, { target: 'clearing', assign: () => { ctx.error = null } })`;
40
39
  - a state
41
- `clearing: { ignore: [...], invoke: invoke(clearDone, { input: {}, done: [{ target: 'idle' }], failed: { Unexpected: [{ target: 'idle', assign: (e) => [op.set(ctx.error, e.message)] }] } }) }`;
42
- - add `ClearDone` to every busy state's `ignore`, and give `clearing` the same list.
40
+ `clearing: { invoke: invoke(clearDone, { input: {}, done: 'idle', failed: { Unexpected: { target: 'idle', assign: (e) => { ctx.error = e.message } } } }) }`
41
+ (busy states drop events they do not handle, so no `ignore`).
43
42
  - **views:** the control
44
43
  `ui.form({ on: { submit: ui.send(ClearDone, {}) } }, [ui.button({ type: 'submit', class: 'text-sm underline' }, ['Clear done'])])`.
45
- - **Contracts:**
46
- - `given: { state: 'idle' }`, when `[{ send: ClearDone, payload: {} }, { done: clearDone, result: { removed: 1 } }]`,
47
- expect `{ state: 'idle', effects: [{ effect: clearDone, input: {} }] }`;
48
- - a second one for `failed … 'Unexpected'` from `clearing`;
49
- - add `ClearDone`, `clearDone` and both contracts to `declarations`.
44
+ - Add `ClearDone` and `clearDone` to `declarations`. The new transitions only copy values, so they need no contract.
50
45
  - **server:**
51
46
  `implement(clearDone, () => { const before = items.length; items.splice(0, items.length, ...items.filter((i) => !i.done)); return { removed: before - items.length } })`.
52
47
  - **Try it:** `hozu post / --button 'Clear done' --next /`.
@@ -66,20 +61,22 @@ Run a fresh scaffold into a scratch app with `--with detail`, and copy the parts
66
61
  ### Other changes
67
62
  | Change | Touch |
68
63
  |---|---|
69
- | New UI-only state (a tab) | model: the context field and its initial value, an event, an `on` that `op.set`s it (add the event to every busy state's `ignore`) → views: the control, a contract, the event in `declarations`. |
64
+ | New UI-only state (a tab) | model: the context field and its initial value, an event, an `on` whose `assign` sets it → views: the control, the event in `declarations` (no contract: it only copies a value). |
70
65
  | Filter / sort / page in the URL | the route's `search` schema (with a default) → links with `ui.link(route, params, { key: value })` → read `search.key` in the view. No machine change. |
71
66
  | New page | `routes.ts` → a view with `route`, in `declarations` → `ui.page(...)` in `hozu.config.ts` (`head`, and `entries` when the route has params). |
72
67
 
73
68
  Whenever the machine changes:
74
- - Add one contract per new transition; HZ016 prints each missing one ready to paste.
75
- - Every busy state `ignore`s every event its visible controls can send; HZ005 prints the missing entries.
69
+ - A transition that decides something (a guard, `navigate`, or a `fn` in its values) needs a contract; HZ016
70
+ prints each missing one ready to paste. Transitions that only copy values are reviewed through the lock.
71
+ - States with `invoke` drop unhandled events by themselves. Other states must handle or `ignore` every event their
72
+ visible controls send; HZ005 prints the missing entries.
76
73
 
77
74
  ## 3. Check (once, after all edits)
78
75
  ```
79
76
  pnpm exec hozu check
80
77
  ```
81
- Fix what it reports. When the behaviour change is intended and everything is clean, run
82
- `pnpm exec hozu check --update-lock`. HZ018 asks for this.
78
+ Fix what it reports. When the behaviour change is intended, run `pnpm exec hozu check --update-lock`: HZ018 shows
79
+ each changed transition as `was: … now: …`, and accepting it updates the lock.
83
80
 
84
81
  ## 4. Verify (once, no server needed)
85
82
  - **Pages:** `pnpm exec hozu get / /items/i1` prints the status, title, alerts and visible text.
@@ -1,4 +1,4 @@
1
- import { event, fn, invoke, machine, mutation, on, op, query, tag, ui } from '@hozu/core'
1
+ import { event, fn, invoke, machine, mutation, on, query, tag, ui } from '@hozu/core'
2
2
  import { z } from 'zod'
3
3
  import { bookmarkPage, Show } from '../../routes.ts'
4
4
 
@@ -83,45 +83,73 @@ export const bookmarksMachine = machine({
83
83
  states: ({ ctx }) => ({
84
84
  idle: {
85
85
  on: [
86
- on(Draft, { target: 'idle', assign: (e) => [op.set(ctx.draft, e.text)] }),
86
+ on(Draft, {
87
+ target: 'idle',
88
+ assign: (e) => {
89
+ ctx.draft = e.text
90
+ },
91
+ }),
87
92
  on(Add, {
88
93
  target: 'adding',
89
- assign: (e) => [
90
- op.set(ctx.draft, e.title),
91
- op.set(ctx.kind, e.kind),
92
- op.set(ctx.error, null),
93
- op.set(ctx.fields, { title: null, kind: null }),
94
- ],
94
+ assign: (e) => {
95
+ ctx.draft = e.title
96
+ ctx.kind = e.kind
97
+ ctx.error = null
98
+ ctx.fields = { title: null, kind: null }
99
+ },
100
+ }),
101
+ on(ToggleRead, {
102
+ target: 'toggling',
103
+ assign: (e) => {
104
+ ctx.target = e.id
105
+ },
95
106
  }),
96
- on(ToggleRead, { target: 'toggling', assign: (e) => [op.set(ctx.target, e.id)] }),
97
107
  ],
98
108
  },
99
109
  adding: {
100
- ignore: [Draft, Add, ToggleRead],
101
110
  invoke: invoke(addBookmark, {
102
111
  input: { title: ctx.draft, kind: ctx.kind },
103
- done: [
104
- {
105
- target: 'idle',
106
- assign: () => [op.set(ctx.draft, '')],
107
- navigate: (b) => ui.link(bookmarkPage, { id: b.id }),
112
+ done: {
113
+ target: 'idle',
114
+ assign: () => {
115
+ ctx.draft = ''
108
116
  },
109
- ],
117
+ navigate: (b) => ui.link(bookmarkPage, { id: b.id }),
118
+ },
110
119
  failed: {
111
- Duplicate: [{ target: 'idle', assign: () => [op.set(ctx.error, DUPLICATE)] }],
112
- Invalid: [{ target: 'idle', assign: (e) => [op.set(ctx.fields, e.fields)] }],
113
- Unexpected: [{ target: 'idle', assign: (e) => [op.set(ctx.error, e.message)] }],
120
+ Duplicate: {
121
+ target: 'idle',
122
+ assign: () => {
123
+ ctx.error = DUPLICATE
124
+ },
125
+ },
126
+ Invalid: {
127
+ target: 'idle',
128
+ assign: (e) => {
129
+ ctx.fields = e.fields
130
+ },
131
+ },
132
+ Unexpected: {
133
+ target: 'idle',
134
+ assign: (e) => {
135
+ ctx.error = e.message
136
+ },
137
+ },
114
138
  },
115
139
  }),
116
140
  },
117
141
  toggling: {
118
- ignore: [Draft, Add, ToggleRead],
119
142
  invoke: invoke(toggleRead, {
120
143
  input: { id: ctx.target },
121
- done: [{ target: 'idle' }],
144
+ done: 'idle',
122
145
  failed: {
123
- NotFound: [{ target: 'idle' }],
124
- Unexpected: [{ target: 'idle', assign: (e) => [op.set(ctx.error, e.message)] }],
146
+ NotFound: 'idle',
147
+ Unexpected: {
148
+ target: 'idle',
149
+ assign: (e) => {
150
+ ctx.error = e.message
151
+ },
152
+ },
125
153
  },
126
154
  }),
127
155
  },
@@ -1,4 +1,4 @@
1
- import { contract, feature, op, ui } from '@hozu/core'
1
+ import { contract, feature, ui } from '@hozu/core'
2
2
  import { bookmarkPage, home } from '../../routes.ts'
3
3
  import {
4
4
  Add,
@@ -41,20 +41,20 @@ export const Board = ui.view({
41
41
  minlength: 2,
42
42
  maxlength: 80,
43
43
  value: ctx.draft,
44
- 'aria-invalid': op.neq(ctx.fields.title, null),
44
+ 'aria-invalid': ctx.fields.title !== null,
45
45
  'aria-describedby': 'title-error',
46
46
  class: 'flex-1 rounded border px-3 py-2',
47
47
  on: { input: ui.send(Draft, { text: ui.dom.value }) },
48
48
  }),
49
49
  ui.select(
50
50
  { name: 'kind', 'aria-label': 'Kind', class: 'rounded border px-2' },
51
- kinds.map((k) => ui.option({ value: k, selected: op.eq(ctx.kind, k) }, [k])),
51
+ kinds.map((k) => ui.option({ value: k, selected: ctx.kind === k }, [k])),
52
52
  ),
53
53
  ui.button({ type: 'submit', class: 'rounded bg-indigo-600 px-4 py-2 text-white' }, ['Add']),
54
54
  ],
55
55
  ),
56
56
  ui.p({ id: 'title-error', class: 'text-sm text-rose-600' }, [ctx.fields.title]),
57
- ui.if(op.neq(ctx.error, null), [ui.p({ role: 'alert', class: 'text-rose-600' }, [ctx.error])], []),
57
+ ctx.error !== null && ui.p({ role: 'alert', class: 'text-rose-600' }, [ctx.error]),
58
58
  when(
59
59
  ['adding'],
60
60
  [
@@ -71,7 +71,7 @@ export const Board = ui.view({
71
71
  ui.a(
72
72
  {
73
73
  href: ui.link(home, null, { show: s.value }),
74
- 'aria-current': op.eq(search.show, s.value),
74
+ 'aria-current': search.show === s.value,
75
75
  class:
76
76
  'rounded-full border px-3 py-1 aria-[current=true]:bg-indigo-600 aria-[current=true]:text-white',
77
77
  },
@@ -84,11 +84,9 @@ export const Board = ui.view({
84
84
  {},
85
85
  {
86
86
  ready: (items) =>
87
- ui.if(
88
- isEmpty({ items, show: search.show }),
89
- [ui.p({ class: 'text-slate-500' }, ['No bookmarks'])],
90
- [
91
- ui.ul({ class: 'divide-y rounded border' }, [
87
+ isEmpty({ items, show: search.show })
88
+ ? ui.p({ class: 'text-slate-500' }, ['No bookmarks'])
89
+ : ui.ul({ class: 'divide-y rounded border' }, [
92
90
  ui.each(visible({ items, show: search.show }), 'id', (b) =>
93
91
  ui.li({ class: 'flex items-center gap-3 px-4 py-3' }, [
94
92
  ui.a({ href: ui.link(bookmarkPage, { id: b.id }), class: 'flex-1 underline' }, [
@@ -101,13 +99,11 @@ export const Board = ui.view({
101
99
  class: 'text-sm',
102
100
  on: { click: ui.send(ToggleRead, { id: b.id }) },
103
101
  },
104
- [ui.if(op.eq(b.read, true), ['Mark unread'], ['Mark read'])],
102
+ [b.read ? 'Mark unread' : 'Mark read'],
105
103
  ),
106
104
  ]),
107
105
  ),
108
106
  ]),
109
- ],
110
- ),
111
107
  pending: ui.p({}, ['Loading…']),
112
108
  failed: { Unexpected: () => ui.p({ role: 'alert' }, ['Bookmarks are unavailable']) },
113
109
  },
@@ -127,7 +123,7 @@ export const Detail = ui.view({
127
123
  ui.article({}, [
128
124
  ui.h1({ class: 'text-3xl font-bold' }, [b.title]),
129
125
  ui.p({}, ['Kind: ', b.kind]),
130
- ui.p({}, [ui.if(op.eq(b.read, true), ['Read'], ['Unread'])]),
126
+ ui.p({}, [b.read ? 'Read' : 'Unread']),
131
127
  ]),
132
128
  pending: null,
133
129
  failed: {
@@ -0,0 +1,12 @@
1
+ # Sign-in, sessions, per-user data
2
+
3
+ - **Start from the scaffold:** `hozu add feature notes --page / --with auth` writes `features/account` (sign-in page,
4
+ sign-out, `me`), the session cookie in `serve.ts`, per-user resolvers, and a redirect to `/login` when signed out.
5
+ Replace the name-only sign-in with real credentials before production; set `SESSION_SECRET` (and
6
+ `SESSION_SECURE=true` behind HTTPS).
7
+ - `project({ session: z.object({ user: z.string() }) })` declares the identity. Queries with `scope: 'user'` and all
8
+ mutations receive `session`; public queries never do. A mutation calls `setSession(value)` (or `null` to sign out).
9
+ - `createServer({ session: sessionCookie({ name: 'sid', secret }) })` (from `@hozu/runtime-server`): a signed,
10
+ HttpOnly cookie. The session never reaches browser JavaScript.
11
+ - Guard pages: `head: { query: me, redirects: { Unauthorized: login }, … }`.
12
+ - Calling another API with a token: keep the token in the session (server side) and read it in the resolver.
@@ -0,0 +1,14 @@
1
+ # Markdown, images, share images, fonts, preview, offline
2
+
3
+ - **Markdown:** `@hozu/content`: `const posts = await loadCollection({ dir: new URL('./content/posts/', import.meta.url), schema })`
4
+ in `server.ts` gives `{ slug, data, html, headings }`; return it from query resolvers and render `ui.html(post.html)`.
5
+ - **Images:** `ui.img({ src: ui.asset(new URL('./hero.jpg', import.meta.url)), alt, width, height })` (HZ028 without
6
+ dimensions). With `@hozu/image`, `hozu build` adds WebP `srcset` widths.
7
+ - **Share images:** `head.render → image: ui.og({ title, subtitle })` (needs `og: ogImage` from `@hozu/image` in
8
+ `createServer`); on a static host use `image: ui.asset(new URL('./share.png', import.meta.url))`.
9
+ - **Fonts:** a local `@font-face` gets a size-matched fallback automatically.
10
+ - **Page transitions:** links cross-fade (CSS view transitions, no JS); turn off with
11
+ `@view-transition { navigation: none; }` in `app.css`.
12
+ - **Preview:** `createServer({ preview: { secret } })`; `/_hozu/preview?secret=…&path=/posts/a` turns it on; resolvers
13
+ read `ctx.preview`; preview responses are never cached.
14
+ - **Offline:** `site.offline: route` (a static page) makes a service worker (HZ043).