@hozu/cli 0.18.2 → 0.19.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.
Files changed (73) hide show
  1. package/dist/agent.d.ts +19 -0
  2. package/dist/agent.d.ts.map +1 -0
  3. package/dist/agent.js +33 -0
  4. package/dist/agent.js.map +1 -0
  5. package/dist/commands/add.d.ts.map +1 -1
  6. package/dist/commands/add.js +1 -0
  7. package/dist/commands/add.js.map +1 -1
  8. package/dist/commands/browse-page.d.ts.map +1 -1
  9. package/dist/commands/browse-page.js +8 -2
  10. package/dist/commands/browse-page.js.map +1 -1
  11. package/dist/commands/browse-tab.d.ts +10 -0
  12. package/dist/commands/browse-tab.d.ts.map +1 -1
  13. package/dist/commands/browse-tab.js +44 -1
  14. package/dist/commands/browse-tab.js.map +1 -1
  15. package/dist/commands/browse.d.ts.map +1 -1
  16. package/dist/commands/browse.js +42 -3
  17. package/dist/commands/browse.js.map +1 -1
  18. package/dist/commands/docs.js +1 -1
  19. package/dist/commands/scaffold.js +8 -8
  20. package/dist/commands/scaffold.js.map +1 -1
  21. package/dist/commands/skill.js +1 -1
  22. package/dist/contract.d.ts +4 -0
  23. package/dist/contract.d.ts.map +1 -1
  24. package/dist/guide.d.ts +27 -0
  25. package/dist/guide.d.ts.map +1 -0
  26. package/dist/guide.js +48 -0
  27. package/dist/guide.js.map +1 -0
  28. package/dist/main.d.ts.map +1 -1
  29. package/dist/main.js +2 -1
  30. package/dist/main.js.map +1 -1
  31. package/dist/migrate/steps.d.ts.map +1 -1
  32. package/dist/migrate/steps.js +7 -0
  33. package/dist/migrate/steps.js.map +1 -1
  34. package/package.json +13 -9
  35. package/schema/browse.schema.json +13 -0
  36. package/schema/inspect.schema.json +20 -0
  37. package/skill/SKILL.md +63 -0
  38. package/skill/example/app.css +1 -0
  39. package/skill/example/app.ts +35 -0
  40. package/skill/example/features/bookmarks/feature.ts +13 -0
  41. package/skill/example/features/bookmarks/model.ts +163 -0
  42. package/skill/example/features/bookmarks/views.ts +156 -0
  43. package/skill/example/hozu.config.ts +34 -0
  44. package/skill/example/previews.ts +13 -0
  45. package/skill/example/routes.ts +11 -0
  46. package/skill/example/ui/badge.ts +11 -0
  47. package/skill/example/ui/button.ts +23 -0
  48. package/skill/example/ui/field.ts +20 -0
  49. package/skill/example/ui/input.ts +33 -0
  50. package/skill/example/ui/kit.ts +7 -0
  51. package/skill/example/ui/tv.ts +12 -0
  52. package/skill/topics/auth.md +56 -0
  53. package/skill/topics/components.md +92 -0
  54. package/skill/topics/content.md +37 -0
  55. package/skill/topics/contracts.md +36 -0
  56. package/skill/topics/data.md +74 -0
  57. package/skill/topics/deploy.md +73 -0
  58. package/skill/topics/diagnostics.md +99 -0
  59. package/skill/topics/endpoints.md +39 -0
  60. package/skill/topics/env.md +44 -0
  61. package/skill/topics/feature.md +90 -0
  62. package/skill/topics/fetch.md +67 -0
  63. package/skill/topics/forms.md +47 -0
  64. package/skill/topics/http.md +21 -0
  65. package/skill/topics/i18n.md +34 -0
  66. package/skill/topics/machine.md +78 -0
  67. package/skill/topics/pages.md +69 -0
  68. package/skill/topics/patterns.md +85 -0
  69. package/skill/topics/recipes.md +73 -0
  70. package/skill/topics/requests.md +41 -0
  71. package/skill/topics/testing.md +84 -0
  72. package/skill/topics/views.md +51 -0
  73. package/templates/guide.md +16 -0
@@ -0,0 +1,84 @@
1
+ # Testing
2
+
3
+ - **Read a page without a server:** `hozu get /path --select 'button[aria-pressed=true]' --forms` (status, title,
4
+ visible text; `--forms` lists each form's fields and submit buttons). Endpoints: `hozu get '/api/items?x=1'`.
5
+ - **Try one query or mutation without a page:** `hozu call notes.listNotes --input '{}' --session '{"user":"ada"}'`;
6
+ a mutation writes real data, so it needs `--write`. An endpoint too:
7
+ `hozu call api.who --input '{"room":"a"}' --header 'Authorization: Bearer t'` (a POST needs `--write`).
8
+ - **Drive the app in a real browser, still without a server:**
9
+ `hozu browse / --session '{"user":"ada"}' --do 'fill New note=Milk' --do 'press Enter' --do 'click Pin in "Milk"'`.
10
+ - `--js both` (the default) runs every step with JS and with JS switched off; submit with `press Enter` or
11
+ `submit "<form>"` so one step list drives both.
12
+ - Steps: `fill <label>=<value>`, `select <label>=<option>`, `check` / `uncheck <label>`, `click <name>`,
13
+ `submit "<form>"`, `press <key>`, `wait <ms>`, `goto <path>`, `post <path> a=1&b=2`,
14
+ `remember <name> from url|<selector> [@attr]` (later steps read `$name`); a target may end with `in "<text>"`
15
+ (for fill and select, before or after `=value`).
16
+ - Labels are what `hozu get <page> --forms` lists; a missing one prints `Did you mean "…"?`. One `--do` may hold
17
+ several steps: `--do 'fill Title=Milk; press Enter'`.
18
+ - **Other users, other pages, after a reload, after sign-out:** verify any such statement once, in one `browse`
19
+ chain with `--js both`. `--as <name>` starts an actor with its own browser; all actors share one app.
20
+ - A stale form (sent after the data changed elsewhere): `--do 'remember save from form:has([name=title]) @action'`,
21
+ change the data as another `--as`, then `--do 'post $save title=x'`. No server and no curl needed.
22
+ - **The output** is per step only the lines added (`+`) or removed (`−`). A passing six-step run stays under 1.5 KB.
23
+ Exit code 1 when a step failed, the modes differ or an error was printed.
24
+ - **A step that reloads the page** with JS on says `the page reloaded` (a form that should update in place);
25
+ `--full` adds `N elements replaced` (a region drawn again), `--json` has both as `document` / `replaced`.
26
+ - **A pending state:** `--do 'hold notes.addNote'` keeps that effect's answer back; the next steps (and
27
+ `--screenshot`) see the busy UI; `--do 'release'` answers it.
28
+ - `browse` runs the `npm start` app: what only `hozu dev` does (reload on edits, DevTools) is not in it.
29
+ - In code: `const page = await testApp(app).get('/')` from `@hozu/testing` → `{ status, headers, html, text, payload }`.
30
+ - A build with errors renders nothing: run `hozu check`.
31
+
32
+ <!-- more -->
33
+
34
+ - **`hozu get`** prints the status, redirect, `set-cookie` attributes (`HttpOnly`, `SameSite`), title, alerts and
35
+ visible text.
36
+ - `--forms` lists each form: fields with their defaults, checkbox / radio groups with every value (checked ones
37
+ marked ✓), controls that join through `form=` (marked `(form=)`), and submit buttons with their name and value.
38
+ - It needs no browser.
39
+ - **`hozu call`** runs the effect through the app's own handler and prints the value or the declared error. With
40
+ `--write`, a mutation also prints the tags it invalidated and the queries they refresh. `runs: 'browser'` effects
41
+ need `hozu browse`.
42
+ - **`hozu browse`:**
43
+ - It uses the installed Chrome / Chromium / Edge (`HOZU_CHROME=/path` to choose); without one it is a config
44
+ error. The app runs in-process, exactly as `npm start` serves it.
45
+ - `--js both` runs both modes in the same Chrome, side by side. Use `--js on` or `--js off` for one mode.
46
+ - Steps in detail: a second fill of a repeated name fills the next field; `check` / `uncheck` set the state;
47
+ `click <name>` on a submit button posts with its name and value; `submit "<form>"` takes a form's `aria-label` or
48
+ its submit button text. Labels and names are what a user reads (aria-label, `<label>`, placeholder, button text,
49
+ `title`), or a field's `name`.
50
+ - `in "<text>"` picks the smallest list item, table row or form containing that text
51
+ (`click Delete in "Buy milk"`).
52
+ - A step with no native effect prints `js-only (<reason>)` in the off column, e.g. a `type=button` button.
53
+ - **Actors:** the steps after an `--as <name>` are that actor's, and a later `--as <name>` switches back. `--session`
54
+ right after an `--as` signs that actor in. All actors share one data store and one session store, so what ada
55
+ writes is what bob reads.
56
+ - Signing out and in again inside one actor's chain also works (`click Sign out`, `fill Name=bob`, `press Enter`).
57
+ - `hozu browse /notes --as ada --session '{"user":"ada"}' --as bob --session '{"user":"bob"}' --as ada --do 'click Share in "Milk"' --as bob --do 'goto /inbox'`
58
+ - `--header 'Name: value'` adds a header to every request: before the first `--as` for every actor, after an
59
+ `--as` for that actor.
60
+ - **Another visitor's data:** `--as ada --do 'remember note from li a @href' --as bob --do 'goto $note'` (bob's page
61
+ should answer 403), or `--as bob --do 'post /notes/n1 text=x'`: a forged native post, as bob, without the page.
62
+ Each mode keeps its own remembered values.
63
+ - **The output** is small on purpose: lines print once when both modes agree and per mode where they differ; a
64
+ navigation prints `→ <path>` and the new page's lines; a live update on another actor's page prints under the step
65
+ (`bob: + Milk`).
66
+ - `≠ DIFFERS` marks a step where both modes made a request and the resulting text differs: a no-JS/JS parity bug.
67
+ - Errors: uncaught exceptions, `console.error`s, CSP violations and failed requests, each with the page, the
68
+ resource type and the mode. A 400 re-render of an invalid native post is not an error, and a page answering
69
+ 401, 403, 404 or 410 is the step's status (`→ /notes/n1 (403)`), so an access check exits 0.
70
+ - To forge a post, take the form's `action` from `hozu get <page> --forms` or `remember … @action` on a page the
71
+ server rendered (`goto` it first): forms the client renders after a change carry no `action`.
72
+ - Exit code 1 also when a client component failed. `--json` has every line; `--full` prints them all;
73
+ `--select <css>`, `--screenshot shot.png` (after the steps), `--viewport 390x844` (a phone; default 1280x800) and
74
+ `--reduced-motion`.
75
+ - It also prints the client components on the page (mounted, failed, size, canvases).
76
+ - **`testApp`:** `app` is the default export of `app.ts`; `.post(path, fields)` submits a native form, with fields as
77
+ a record or as `[name, value]` pairs for repeated names. `testApp(app, { session: store })` may swap only the
78
+ session store (a test issuer). `testApp` reads no env files: pass `testApp(app, { env: process.env })` (or a record)
79
+ for the variables your resolvers need; `hozu get`, `call` and `browse` read `env.files` themselves.
80
+ - `hozu get`, `hozu browse` and `testApp` build the app module; a build with errors exits 1 (throws).
81
+ - Vitest: add `hozuTransform()` from `@hozu/transform/vite` to `plugins`.
82
+ - Browser tests: wait for `html[data-hozu-ready]` (set after hydration) before clicking.
83
+ - A person checks the result with `npm run dev` (`hozu dev`: reloads and Hozu DevTools); what they ask for from
84
+ there arrives as requests (`hozu docs requests`). The tools above stay the way you verify.
@@ -0,0 +1,51 @@
1
+ # Views
2
+
3
+ ```ts
4
+ export const Board = ui.view({
5
+ machine: m, // optional: without it, no ctx / when / events, and 0 JS
6
+ route: home, // optional: render gets { params, search } typed by the route
7
+ seed: ({ search }) => ({ q: search.q }), // optional, with machine + route: context fields from the URL
8
+ render: ({ ctx, when, params, search, locale }) => ui.main({ class: 'mx-auto max-w-xl' }, [ /* children */ ]),
9
+ })
10
+ ```
11
+ - **Elements:** `ui.<tag>(attrs, children)` for every HTML and SVG element; void tags (`input`, `img`) take only attrs.
12
+ Children are nodes, strings, numbers, data values, and `null` / `false` (render nothing).
13
+ - **Classes:** `class` is a static string of Tailwind classes that must exist (HZ026); conditional classes:
14
+ `toggle: { 'bg-indigo-600 text-white': ctx.tab === t }`. No `style`.
15
+ - **Conditions:** `ctx.error !== null && ui.p({ role: 'alert' }, [ctx.error])`, `item.done ? 'done' : 'open'`.
16
+ By machine state: `when(['adding', 'saving'], [ui.p({}, ['Saving…'])])`.
17
+ - **Lists:** `ui.each(items, 'id', (item) => ui.li({}, [item.title]))`. Never `.map` over data.
18
+ - **Numbers and dates:** `ui.format.number(q.price, { style: 'currency', currency: 'USD' })`, `ui.format.date(x,
19
+ { dateStyle: 'medium' })`, `ui.format.relative(n, 'day')`, `ui.format.list(xs)` (Intl, the page's locale).
20
+ - **Events:** `on: { click: ui.send(Event, payload) }`; payload fields are literals, data, `ui.dom.value`,
21
+ `ui.dom.form('name')` (submit; `hozu docs forms`).
22
+ - **Links:** `ui.a({ href: ui.link(itemPage, { id: item.id }) }, [...])`; never a string path (HZ032).
23
+ - **Data:** `ui.query(listItems, input, { ready: (items) => …, failed: { NotFound: () => …, Unexpected: () => … } })`;
24
+ `failed` lists every declared error plus `Unexpected`.
25
+ - **Shared UI** (buttons, inputs, fields): `ui.use(Button, { variant, props, on }, ['Save'])` of a kit component
26
+ (`hozu docs components`).
27
+
28
+ <!-- more -->
29
+
30
+ - **Attributes:** HTML names in lower case (`for`, `minlength`, `aria-pressed`, `data-x`), typed per tag. Values are
31
+ literals or data: `'aria-pressed': ctx.show === 'all'`, `title: ctx.error ?? 'OK'`.
32
+ - **CSS variables:** `vars: { '--hue': item.hue }`.
33
+ - **More conditions:** `list.length === 0 ? ui.p({}, ['Empty']) : ui.ul({}, [...])`; a `?:` / `&&` branch may be a list:
34
+ `open ? [a, b] : null`. A query branch or an each item returns one node: wrap several in an element (HZ014).
35
+ With an enter/leave animation: `ui.if(cond, [then], [else], 'fade')` (the motion name is required).
36
+ - **More lists:** `ui.each(tags, null, (t) => …)` for primitives. `.map` only over constants:
37
+ `['a', 'b'].map((k) => ui.option({ value: k }, [k]))`.
38
+ - **Text:** template strings work: `` `${n} items` ``.
39
+ - **Reuse:** `export const row = part((item: Item) => ui.li({}, [item.done ? 'Done' : item.title]))`, called as
40
+ `row(item)`; it is inlined, so the IR equals the inline form. A plain function that receives data is HZ059.
41
+ - **Shared UI:** use the kit component, not a styled `ui.button` per page (`example/` uses a kit).
42
+ - **More events:** any DOM event name plus `visible` (entered the viewport). Payload fields also:
43
+ `ui.dom.formAll('name')`, `ui.dom.checked`, `ui.dom.valueAsNumber`, `ui.dom.key`. `ui.dom.value` / `ui.dom.form`
44
+ fill an enum field only from a `<select>`, radios or submit buttons whose literal values are all members (HZ033).
45
+ - **Search in links:** the third argument of `ui.link` is optional and exists only when the route declares `search`:
46
+ omitted means every default, and a search lists only the fields that differ: `ui.link(home, null, { show: 'done' })`.
47
+ - **More data:** `pending: ui.p({}, ['Loading…'])` is optional; a branch may return `null` to render nothing.
48
+ Server-fetched data is sent with the page and never fetched again; after a mutation, queries whose tags it
49
+ invalidates refresh in place.
50
+ - **Also:** `ui.html(post.html)` (trusted HTML from query data only, HZ030), `ui.asset(new URL('./x.png',
51
+ import.meta.url))`, `ui.window({ on })` / `ui.document({ on })`, `ui.embed(OtherView)`.
@@ -0,0 +1,16 @@
1
+ # __NAME__
2
+
3
+ A web app built with Hozu (`@hozu/*`). Hozu is not in your training data.
4
+
5
+ ## Before writing code
6
+ - __READ__ It has the change loop, the commands and the rules no diagnostic checks.
7
+ - The files in `__SKILL__/` are the whole API. Do not read the framework source in `node_modules/@hozu`.
8
+ __NOTE__
9
+ ## Rules
10
+ - After `hozu add feature`, do not print the generated files: edit the texts it lists; `hozu map` shows the rest.
11
+ - Apply the fix each diagnostic gives; do not work around a rule.
12
+ - `__RUN__ hozu get` and `__RUN__ hozu browse` run the same app as `npm start`; do not start a server to check.
13
+ - Asked to do the Hozu requests (from DevTools under `npm run dev`): `__RUN__ hozu requests --full`, then follow
14
+ `__RUN__ hozu docs requests`.
15
+ - Do not edit `__SKILL__/` or the text between the `hozu` markers: `__RUN__ hozu skill` rewrites both for the
16
+ installed Hozu version.