@hozu/cli 0.23.0 → 0.25.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 (68) hide show
  1. package/README.md +3 -1
  2. package/dist/commands/add.d.ts +1 -1
  3. package/dist/commands/add.d.ts.map +1 -1
  4. package/dist/commands/add.js +3 -3
  5. package/dist/commands/add.js.map +1 -1
  6. package/dist/commands/browse-page.d.ts.map +1 -1
  7. package/dist/commands/browse-page.js +6 -4
  8. package/dist/commands/browse-page.js.map +1 -1
  9. package/dist/commands/browse-tab.d.ts +5 -0
  10. package/dist/commands/browse-tab.d.ts.map +1 -1
  11. package/dist/commands/browse-tab.js +18 -3
  12. package/dist/commands/browse-tab.js.map +1 -1
  13. package/dist/commands/browse.d.ts.map +1 -1
  14. package/dist/commands/browse.js +24 -7
  15. package/dist/commands/browse.js.map +1 -1
  16. package/dist/commands/call.d.ts.map +1 -1
  17. package/dist/commands/call.js +26 -4
  18. package/dist/commands/call.js.map +1 -1
  19. package/dist/commands/migrate.d.ts.map +1 -1
  20. package/dist/commands/migrate.js +12 -4
  21. package/dist/commands/migrate.js.map +1 -1
  22. package/dist/commands/request.d.ts +1 -0
  23. package/dist/commands/request.d.ts.map +1 -1
  24. package/dist/commands/request.js +125 -32
  25. package/dist/commands/request.js.map +1 -1
  26. package/dist/commands/scaffold.d.ts.map +1 -1
  27. package/dist/commands/scaffold.js +4 -5
  28. package/dist/commands/scaffold.js.map +1 -1
  29. package/dist/contract.d.ts +15 -2
  30. package/dist/contract.d.ts.map +1 -1
  31. package/dist/gen/go.d.ts +3 -1
  32. package/dist/gen/go.d.ts.map +1 -1
  33. package/dist/gen/go.js +34 -4
  34. package/dist/gen/go.js.map +1 -1
  35. package/dist/main.d.ts.map +1 -1
  36. package/dist/main.js +4 -2
  37. package/dist/main.js.map +1 -1
  38. package/dist/migrate/steps.d.ts +2 -2
  39. package/dist/migrate/steps.d.ts.map +1 -1
  40. package/dist/migrate/steps.js +105 -13
  41. package/dist/migrate/steps.js.map +1 -1
  42. package/dist/remote.d.ts.map +1 -1
  43. package/dist/remote.js +7 -2
  44. package/dist/remote.js.map +1 -1
  45. package/package.json +9 -9
  46. package/schema/browse.schema.json +17 -0
  47. package/schema/call.schema.json +1 -1
  48. package/schema/gen.schema.json +1 -1
  49. package/schema/inspect.schema.json +10 -1
  50. package/schema/migrate.schema.json +10 -2
  51. package/skill/SKILL.md +4 -5
  52. package/skill/example/features/bookmarks/views.ts +8 -12
  53. package/skill/example/ui/button.ts +6 -2
  54. package/skill/topics/auth.md +2 -2
  55. package/skill/topics/contracts.md +3 -1
  56. package/skill/topics/data.md +10 -6
  57. package/skill/topics/deploy.md +4 -2
  58. package/skill/topics/diagnostics.md +8 -8
  59. package/skill/topics/feature.md +5 -5
  60. package/skill/topics/forms.md +2 -0
  61. package/skill/topics/http.md +2 -1
  62. package/skill/topics/i18n.md +4 -2
  63. package/skill/topics/machine.md +9 -5
  64. package/skill/topics/pages.md +3 -1
  65. package/skill/topics/patterns.md +7 -6
  66. package/skill/topics/recipes.md +15 -5
  67. package/skill/topics/testing.md +7 -0
  68. package/skill/topics/views.md +30 -16
@@ -2,10 +2,10 @@
2
2
 
3
3
  ```ts
4
4
  export const Board = ui.view({
5
- machine: m, // optional: without it, no ctx / when / events, and 0 JS
5
+ machine: m, // optional: without it, no ctx / is / events, and 0 JS
6
6
  route: home, // optional: render gets { params, search } typed by the route
7
7
  seed: ({ search }) => ({ q: search.q }), // optional, with machine + route: context fields from the URL
8
- render: ({ ctx, when, is, params, search, locale }) => ui.main({ class: 'mx-auto max-w-xl' }, [ /* children */ ]),
8
+ render: ({ ctx, is, current, params, search, locale }) => ui.main({ class: 'mx-auto max-w-xl' }, [ /* children */ ]),
9
9
  })
10
10
  ```
11
11
  - **Elements:** `ui.<tag>(attrs, children)` for every HTML and SVG element; void tags (`input`, `img`) take only attrs.
@@ -13,10 +13,10 @@ export const Board = ui.view({
13
13
  - **Classes:** `class` is a static string of Tailwind classes that must exist (HZ026); conditional classes:
14
14
  `toggle: { 'bg-indigo-600 text-white': ctx.tab === t }`. No `style`.
15
15
  - **Conditions:** `ctx.error !== null && ui.p({ role: 'alert' }, [ctx.error])`, `item.done ? 'done' : 'open'`.
16
- By machine state: `is(['paused']) ? resume : pause`, `!is(['idle']) && ui.p({}, ['Saving…'])`,
17
- `disabled: is(['saving'])` (keep a control and disable it rather than hide it while busy: no flash).
18
- - **Dialogs, popovers, menus:** native, no machine state: `ui.button({ commandfor: 'd', command: 'show-modal' })` +
19
- `ui.dialog({ id: 'd', closedby: 'any' }, [...])`, `popover` / `popovertarget`, `ui.details`.
16
+ By machine state: `!is(['idle']) && ui.p({}, ['Saving…'])`,
17
+ `disabled: is(['saving'])` (disable a control while busy, not hide it: it stays put).
18
+ - **Dialogs, popovers, menus:** native (bound to the machine: --more): `ui.button({ commandfor: 'd', command:
19
+ 'show-modal' })` + `ui.dialog({ id: 'd', closedby: 'any' }, [...])`, `popover` / `popovertarget`, `ui.details`.
20
20
  - **Lists:** `ui.each(items, 'id', (item) => ui.li({}, [item.title]))`. Never `.map` over data.
21
21
  - **Numbers and dates:** `ui.format.number(q.price, { style: 'currency', currency: 'USD' })`, `ui.format.date(x,
22
22
  { dateStyle: 'medium' })`, `ui.format.relative(n, 'day')`, `ui.format.list(xs)` (Intl, the page's locale).
@@ -33,7 +33,10 @@ export const Board = ui.view({
33
33
  <!-- more -->
34
34
 
35
35
  - **Motion:** what an update adds fades in by itself (not with reduced motion); a view two pages show stays still
36
- across a page change. `hozu browse` reports a flash or a layout shift: fix those.
36
+ across a page change. `hozu browse` reports a flash or a layout shift: fix those. `c ? a : b` whose branches are
37
+ one element of the same tag and shape (`ctx.paused ? resumeButton : pauseButton`) keeps the element: its text,
38
+ classes, attributes and listener follow `c`, and focus stays (not when a differing value computes, such as a `fn`
39
+ or a template string, or links elsewhere).
37
40
  - **Attributes:** HTML names in lower case (`for`, `minlength`, `aria-pressed`, `data-x`), typed per tag. Values are
38
41
  literals or data: `'aria-pressed': ctx.show === 'all'`, `title: ctx.error ?? 'OK'`.
39
42
  - **Sizes and colours from data:** `vars` with an arbitrary-value class: `class: 'w-[calc(var(--pct)*1%)]'`,
@@ -42,7 +45,9 @@ export const Board = ui.view({
42
45
  (runs on the server, and in the browser inside an island).
43
46
  - **More conditions:** `list.length === 0 ? ui.p({}, ['Empty']) : ui.ul({}, [...])`; a `?:` / `&&` branch may be a list:
44
47
  `open ? [a, b] : null`. A query branch or an each item returns one node: wrap several in an element (HZ014).
45
- With an enter/leave animation: `ui.if(cond, [then], [else], 'fade')` (the motion name is required).
48
+ With an enter/leave animation (only then): `ui.if(cond, [then], [else], 'fade')`, by machine state
49
+ `when(['saving'], [children], 'fade')`, and list rows `ui.each(items, 'id', row, 'fade')` (the motion name is
50
+ required; without a motion, `is([...]) && …`).
46
51
  - **More lists:** `ui.each(tags, null, (t) => …)` for primitives. `.map` only over constants:
47
52
  `['a', 'b'].map((k) => ui.option({ value: k }, [k]))`.
48
53
  - **Text:** template strings work: `` `${n} items` ``.
@@ -50,25 +55,34 @@ export const Board = ui.view({
50
55
  `row(item)`; it is inlined, so the IR equals the inline form. A plain function that receives data is HZ059.
51
56
  - **Shared UI:** use the kit component, not a styled `ui.button` per page (`example/` uses a kit).
52
57
  - **More events:** any DOM event name plus `visible` (entered the viewport). Payload fields also:
53
- `ui.dom.formAll('name')`, `ui.dom.checked`, `ui.dom.valueAsNumber`, `ui.dom.key`. `ui.dom.value` / `ui.dom.form`
58
+ `ui.dom.formAll('name')`, `ui.dom.checked`, `ui.dom.valueAsNumber`, `ui.dom.key`. Keyboard shortcuts:
59
+ `ui.window({ on: { keydown: ui.send(Open, {}, { keys: ['Mod+k', '/'] }) } })` sends only on those presses and
60
+ stops the browser's own (`Mod` is ⌘ on Apple, Ctrl elsewhere; also `Ctrl`, `Meta`, `Alt`, `Shift`); a printable
61
+ key without a modifier waits while the person types in a field (`Escape` does not). `ui.dom.value` / `ui.dom.form`
54
62
  fill an enum field only from a `<select>`, radios or submit buttons whose literal values are all members (HZ033).
55
63
  - **Search in links:** the third argument of `ui.link` is optional and exists only when the route declares `search`:
56
64
  omitted means every default, and a search lists only the fields that differ: `ui.link(home, null, { show: 'done' })`.
57
65
  - **More data:** `pending: ui.p({}, ['Loading…'])` is optional; a branch may return `null` to render nothing.
58
66
  Server-fetched data is sent with the page and never fetched again; after a mutation, queries whose tags it
59
- invalidates refresh in place.
67
+ invalidates refresh in place. When a query's input changes, the rows stay (`aria-busy` on the parent) and update
68
+ by key; `pending` shows only before the first answer.
60
69
  - **From Vue or React:** `computed` → a `fn`; `ref` + `@click` → a context field + `ui.set`; `v-if` → `?:` / `&&`
61
- (with `is([...])` for a machine state); `v-for` + `:key` → `ui.each(list, 'id', …)`; `setInterval` → `after` with
62
- `refresh` (`hozu docs machine`); `watch` → a transition's `assign`; DOM libraries (charts, maps) → a client component
70
+ (with `is([...])` for a machine state); `v-for` + `:key` → `ui.each(list, 'id', …)`; `setInterval` → `freshness: { poll: s }`
71
+ for data that changes on its own (`hozu docs data`), `after` with `refresh` for a refresh the visitor pauses
72
+ (`hozu docs machine`); `watch` → a transition's `assign`; DOM libraries (charts, maps) → a client component
63
73
  (`hozu docs components`; `examples/showcase` has a Chart.js one).
64
74
  - **Also:** `ui.html(post.html)` (trusted HTML from query data only, HZ030), `ui.asset(new URL('./x.png',
65
75
  import.meta.url))`, `ui.window({ on })` / `ui.document({ on })`, `ui.embed(OtherView)`.
66
76
 
67
77
  ## Menus, dialogs, counting
68
- - When a query's input changes, the rows stay (`aria-busy` on the parent) and update by key; `pending` shows only
69
- before the first answer.
70
- - A link to the page shown gets `aria-current="page"`, a link to a section above it (`/orders` on `/orders/7`)
71
- `"true"`; the same path with another search (a next page) gets nothing. An `aria-current` you set wins.
78
+ - A link to the address shown gets `aria-current="page"`. Which links mark a section is yours to say: the render's
79
+ `current(route)` is true on that route's pages, so a menu writes
80
+ `ui.a({ href: ui.link(orders, null), 'aria-current': current(orders) || current(orderDetail) }, ['Orders'])` and
81
+ styles `aria-[current]:font-bold`: `true` is written `"page"` on the address itself, `false` writes nothing.
82
+ `current(shop, { category: 'apparel' })` also compares those params (search is ignored). A menu is a constant
83
+ list mapped to links, one line per section:
84
+ `...[[orders, 'Orders'], [customers, 'Customers']].map(([r, label]) => ui.a({ href: ui.link(r, null),
85
+ 'aria-current': current(r) }, [label]))`; `current(a) || current(b)` works there too.
72
86
  - `ui.dialog({ open: is(['editing']), on: { close: ui.send(Cancel, {}) } }, [...])` opens as a modal and closes with
73
87
  the machine; Escape sends `close`. It needs JavaScript: a dialog that must open without it uses the native
74
88
  `commandfor` button (the short form) and closes when the data that shows it changes.