create-hozu 0.7.0 → 0.9.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.
@@ -15,18 +15,21 @@ export const Board = ui.view({
15
15
  - **Classes:** `class` is a static string of Tailwind classes that must exist (HZ026). Conditional classes:
16
16
  `toggle: { 'bg-indigo-600 text-white': ctx.tab === t }`. CSS variables: `vars: { '--hue': item.hue }`. No `style`.
17
17
  - **Conditions:** `ctx.error !== null && ui.p({ role: 'alert' }, [ctx.error])`, `item.done ? 'done' : 'open'`,
18
- `list.length === 0 ? ui.p({}, ['Empty']) : ui.ul({}, [...])`. With an enter/leave animation:
19
- `ui.if(cond, [then], [else], 'fade')`.
18
+ `list.length === 0 ? ui.p({}, ['Empty']) : ui.ul({}, [...])`; a branch may be a list: `open ? [a, b] : null`, also as what a query branch or an each item returns.
19
+ With an enter/leave animation: `ui.if(cond, [then], [else], 'fade')` (the motion name is required).
20
20
  - **By machine state:** `when(['adding', 'saving'], [ui.p({}, ['Saving…'])])`.
21
21
  - **Lists:** `ui.each(items, 'id', (item) => ui.li({}, [item.title]))`; `ui.each(tags, null, (t) => …)` for primitives.
22
22
  Never `.map` over data (only over constants: `['a', 'b'].map((k) => ui.option({ value: k }, [k]))`).
23
23
  - **Text:** template strings work: `` `${n} items` ``.
24
+ - **Reuse:** `export const row = part((item: Item) => ui.li({}, [item.done ? 'Done' : item.title]))`, called as
25
+ `row(item)`; it is inlined, so the IR equals the inline form. A plain function that receives data is HZ059.
24
26
  - **Events:** `on: { click: ui.send(Event, payload) }`, any DOM event name plus `visible` (entered the viewport).
25
- Payload fields: literals, data, `ui.dom.value`, `ui.dom.form('name')`, `ui.dom.checked`, `ui.dom.valueAsNumber`,
26
- `ui.dom.key`. `ui.dom.value` / `ui.dom.form` fill an enum field only from a `<select>` or radios whose literal
27
- option values are all members (HZ033).
27
+ Payload fields: literals, data, `ui.dom.value`, `ui.dom.form('name')` / `ui.dom.formAll('name')` (submit; `hozu docs
28
+ forms`), `ui.dom.checked`, `ui.dom.valueAsNumber`, `ui.dom.key`. `ui.dom.value` / `ui.dom.form` fill an enum field
29
+ only from a `<select>`, radios or submit buttons whose literal values are all members (HZ033).
28
30
  - **Links:** `ui.a({ href: ui.link(itemPage, { id: item.id }) }, [...])`; never a string path (HZ032). The third
29
- argument exists only when the route declares `search`: `ui.link(home, null, { show: 'done' })`.
31
+ argument is optional and exists only when the route declares `search`: omitted means every default, and a search
32
+ lists only the fields that differ: `ui.link(home, null, { show: 'done' })`.
30
33
  - **Data:** `ui.query(listItems, input, { ready: (items) => …, pending: ui.p({}, ['Loading…']), failed: { NotFound:
31
34
  () => …, Unexpected: () => … } })`; `pending` is optional, `failed` lists every declared error plus `Unexpected`; a branch may return `null` to render
32
35
  nothing.
@@ -0,0 +1,7 @@
1
+ import { resolvers } from '@hozu/data'
2
+ import { app } from '@hozu/runtime-server'
3
+ import project from './hozu.config.ts'
4
+
5
+ export default app({
6
+ resolvers: resolvers(project, () => []),
7
+ })
@@ -7,6 +7,7 @@ import { home } from './routes.ts'
7
7
  export default project({
8
8
  schema: zodAdapter,
9
9
  styles: new URL('./app.css', import.meta.url),
10
+ app: new URL('./app.ts', import.meta.url),
10
11
  site: { url: 'http://localhost:3000', name: '__NAME__', lang: 'en' },
11
12
  routes: { home },
12
13
  pages: [ui.page(home, { views: [Home], head: { render: () => ({ title: '__NAME__' }) } })],
@@ -3,24 +3,12 @@
3
3
  A web app built with Hozu (`@hozu/*`). Hozu is not in your training data.
4
4
 
5
5
  ## Before writing code
6
- - __READ__
7
- - Changing existing code: read `__SKILL__/changing.md` first.
6
+ - __READ__ It has the change loop, the commands and the rules no diagnostic checks.
8
7
  - The files in `__SKILL__/` are the whole API. Do not read the framework source in `node_modules/@hozu`.
9
-
10
- ## The loop
11
- ```
12
- __RUN__ hozu add feature tasks --page / --with detail,toggle,filter,remove # then edit the texts it lists
13
- # add auth to the list for sign-in and per-user data
14
- __RUN__ hozu map # outline of the app with file:line, before a change
15
- __RUN__ hozu check # after every change: types, rules, contracts
16
- __RUN__ hozu check --update-lock # only to accept a clean, intended behaviour change
17
- __RUN__ hozu get / --select button --forms # try pages without a server: text, attributes, forms
18
- __RUN__ hozu post / --field title=Ship --next / # submit a form like a browser
19
- __RUN__ hozu browse / --do 'click Save' # real browser, no server: errors, widgets, text after steps
20
- ```
21
8
  __NOTE__
22
9
  ## Rules
23
10
  - After `hozu add feature`, do not print the generated files: edit the texts it lists; `hozu map` shows the rest.
24
11
  - Apply the fix each diagnostic gives; do not work around a rule.
25
- - Every behaviour change comes with a contract change.
26
- - Do not edit `__SKILL__/`: `__RUN__ hozu skill` rewrites it for the installed Hozu version.
12
+ - `__RUN__ hozu get` and `__RUN__ hozu browse` run the same app as `npm start`; do not start a server to check.
13
+ - Do not edit `__SKILL__/` or the text between the `hozu` markers: `__RUN__ hozu skill` rewrites both for the
14
+ installed Hozu version.
package/skill/changing.md DELETED
@@ -1,45 +0,0 @@
1
- # Changing a Hozu app
2
-
3
- Keep the loop short: map once, edit everything, check once, verify once.
4
-
5
- ## 1. Read
6
- - The change request.
7
- - `pnpm exec hozu map`: every route, query, mutation, event, state, view and contract, each with its `file:line`.
8
- Open only the lines the change touches. *model* is `model.ts` (schemas, events, effects, `fn`s, the machine),
9
- *views* is `views.ts` (views, contracts); `feature.ts` lists both modules, so new exports need no registration.
10
- - The API is in `SKILL.md`. `hozu docs recipes` has worked steps for an enum field in the add form, a bulk action
11
- button, a detail field and a detail page; `hozu docs <topic>` for anything else.
12
-
13
- ## 2. What to touch
14
- | Change | Touch |
15
- |---|---|
16
- | 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 (no contract: it only copies a value). |
17
- | 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. If the page also filters as you type, `seed: ({ search }) => ({ key: search.key })` on the view and read `ctx.key` only. |
18
- | A per-item action stored on the server (pin, archive, star) | model: a field on the item, an event, a mutation that `invalidates` the list tag, `on(Event, { target: 'pinning', assign: (e) => { ctx.target = e.id } })` and a state with `invoke` → views: the per-item form from `hozu docs patterns` → server: store it and sort in the list resolver. These transitions only copy values: no contract. |
19
- | A control that works in every mode | `machine({ on: [...] })` instead of repeating it per state. |
20
- | New page | `routes.ts` → a view with `route` in `views.ts` → `ui.page(...)` in `hozu.config.ts` (`head`, and `entries` when the route has params). |
21
-
22
- Whenever the machine changes:
23
- - A transition that decides something (a guard, `navigate`, or a `fn` in its values) needs a contract; HZ016
24
- prints each missing one ready to paste. Transitions that only copy values are reviewed through the lock.
25
- - States with `invoke` drop unhandled events by themselves. Other states must handle or `ignore` every event their
26
- visible controls send; HZ005 prints the missing entries.
27
-
28
- ## 3. Check (once, after all edits)
29
- ```
30
- pnpm exec hozu check
31
- ```
32
- Fix what it reports. When the behaviour change is intended, run `pnpm exec hozu check --update-lock`: HZ018 shows
33
- each changed transition as `was: … now: …`, and accepting it updates the lock.
34
-
35
- ## 4. Verify (once, no server needed)
36
- - **Pages:** `pnpm exec hozu get / /items/i1` prints the status, title, alerts and visible text.
37
- - **Attributes and forms:** `--select button` (or `'[role=alert]'`, `a[href]`, `#id`) prints elements with their
38
- attributes, e.g. `aria-pressed`; `--forms` lists each form's fields and buttons. Never start a server for this.
39
- - **Forms:** `pnpm exec hozu post / --field title=A --field priority=high --next /items` fills the form like a
40
- browser (other fields keep their defaults). It follows the redirect, then runs the next steps in the same
41
- process.
42
- - **Chaining:** chain what must share data, e.g. `--next 'POST / title=a'` (fields as `a=1&b=2`) or
43
- `--next /items/i3`.
44
- - **A form with only a button:** `--button 'Clear done'`, or `--next 'POST / @Clear done'`. With one form per item,
45
- `--field id=t2` picks the item's form.
@@ -1,14 +0,0 @@
1
- import { createServer } from '@hozu/adapter-node'
2
- import { buildProject } from '@hozu/core/ir'
3
- import { compileStyles } from '@hozu/css'
4
- import project from './hozu.config.ts'
5
- import { createResolvers } from './server.ts'
6
-
7
- const port = Number(process.env.PORT ?? 3000)
8
- const build = buildProject(project, { sources: false })
9
-
10
- createServer({
11
- build,
12
- styles: await compileStyles(build),
13
- resolvers: createResolvers(),
14
- }).listen(port, () => console.log(`Bookmarks on http://localhost:${port}`))
@@ -1,30 +0,0 @@
1
- # Widgets (browser APIs, DOM libraries)
2
-
3
- Start with `hozu add widget <feature> <Name>`: it writes the declaration, the client module, the `serve.ts` bundle
4
- and the `@hozu/bundle` dependency. There is no `widget` export; the pieces are:
5
- ```ts
6
- export const Map = ui.widget({ tag: 'div', props: z.object({ lat: z.number(), lng: z.number() }),
7
- events: { picked: z.object({ id: z.string() }) }, client: new URL('./map.client.ts', import.meta.url),
8
- load: 'visible', wraps: false }) // widgets.ts; the feature lists the module
9
- ui.use(Map, { props: { lat: ctx.lat, lng: ctx.lng }, on: { picked: (d) => ui.send(Pick, { id: d.id }) },
10
- class: 'h-96 w-full' }, []) // in a view
11
- ```
12
- ```ts
13
- // map.client.ts: a type-only import of the declaration
14
- import { implement } from '@hozu/core/widget'
15
- import type { Map } from './widgets.ts'
16
- export default implement<typeof Map>(({ el, props, emit, signal }) => {
17
- const map = createMap(el, props) // any DOM library
18
- map.on('pick', (id) => emit('picked', { id }))
19
- return { update(next) { map.move(next) }, destroy() { map.remove() } }
20
- })
21
- ```
22
- - `on` is optional. `ui.use` takes no other attributes: put a role or label on a wrapping element,
23
- `ui.section({ role: 'region', 'aria-label': 'Map' }, [ui.use(Map, { props }, [])])`.
24
- - `load`: `'eager' | 'visible' | 'idle'`; `wraps: true` keeps the children as server HTML.
25
- - `serve.ts` passes `widgets: await bundleWidgets(build)` (the server refuses to start without it; `hozu build` bundles
26
- them itself). A library's CSS goes in `app.css` (`@import "leaflet/dist/leaflet.css";`); a map or chart host needs a
27
- height class.
28
- - Check it with `hozu browse /` (no server): each widget is listed as mounted / failed / not mounted with its size
29
- and canvases, next to any error it threw. A mounted host carries `data-hozu-widget="<feature>.<Name>"` and
30
- `data-hozu-widget-state="mounted"` for your own browser tests.
@@ -1,14 +0,0 @@
1
- import { createServer } from '@hozu/adapter-node'
2
- import { buildProject } from '@hozu/core/ir'
3
- import { compileStyles } from '@hozu/css'
4
- import project from './hozu.config.ts'
5
- import { createResolvers } from './server.ts'
6
-
7
- const port = Number(process.env.PORT ?? 3000)
8
- const build = buildProject(project, { sources: false })
9
-
10
- createServer({
11
- build,
12
- styles: await compileStyles(build),
13
- resolvers: createResolvers(),
14
- }).listen(port, () => console.log(`__NAME__ on http://localhost:${port}`))
@@ -1,6 +0,0 @@
1
- import { resolvers } from '@hozu/data'
2
- import project from './hozu.config.ts'
3
-
4
- export function createResolvers() {
5
- return resolvers(project, () => [])
6
- }