@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.
- package/dist/agent.d.ts +19 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +33 -0
- package/dist/agent.js.map +1 -0
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +1 -0
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/browse-page.d.ts.map +1 -1
- package/dist/commands/browse-page.js +8 -2
- package/dist/commands/browse-page.js.map +1 -1
- package/dist/commands/browse-tab.d.ts +10 -0
- package/dist/commands/browse-tab.d.ts.map +1 -1
- package/dist/commands/browse-tab.js +44 -1
- package/dist/commands/browse-tab.js.map +1 -1
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +42 -3
- package/dist/commands/browse.js.map +1 -1
- package/dist/commands/docs.js +1 -1
- package/dist/commands/scaffold.js +8 -8
- package/dist/commands/scaffold.js.map +1 -1
- package/dist/commands/skill.js +1 -1
- package/dist/contract.d.ts +4 -0
- package/dist/contract.d.ts.map +1 -1
- package/dist/guide.d.ts +27 -0
- package/dist/guide.d.ts.map +1 -0
- package/dist/guide.js +48 -0
- package/dist/guide.js.map +1 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +2 -1
- package/dist/main.js.map +1 -1
- package/dist/migrate/steps.d.ts.map +1 -1
- package/dist/migrate/steps.js +7 -0
- package/dist/migrate/steps.js.map +1 -1
- package/package.json +13 -9
- package/schema/browse.schema.json +13 -0
- package/schema/inspect.schema.json +20 -0
- package/skill/SKILL.md +63 -0
- package/skill/example/app.css +1 -0
- package/skill/example/app.ts +35 -0
- package/skill/example/features/bookmarks/feature.ts +13 -0
- package/skill/example/features/bookmarks/model.ts +163 -0
- package/skill/example/features/bookmarks/views.ts +156 -0
- package/skill/example/hozu.config.ts +34 -0
- package/skill/example/previews.ts +13 -0
- package/skill/example/routes.ts +11 -0
- package/skill/example/ui/badge.ts +11 -0
- package/skill/example/ui/button.ts +23 -0
- package/skill/example/ui/field.ts +20 -0
- package/skill/example/ui/input.ts +33 -0
- package/skill/example/ui/kit.ts +7 -0
- package/skill/example/ui/tv.ts +12 -0
- package/skill/topics/auth.md +56 -0
- package/skill/topics/components.md +92 -0
- package/skill/topics/content.md +37 -0
- package/skill/topics/contracts.md +36 -0
- package/skill/topics/data.md +74 -0
- package/skill/topics/deploy.md +73 -0
- package/skill/topics/diagnostics.md +99 -0
- package/skill/topics/endpoints.md +39 -0
- package/skill/topics/env.md +44 -0
- package/skill/topics/feature.md +90 -0
- package/skill/topics/fetch.md +67 -0
- package/skill/topics/forms.md +47 -0
- package/skill/topics/http.md +21 -0
- package/skill/topics/i18n.md +34 -0
- package/skill/topics/machine.md +78 -0
- package/skill/topics/pages.md +69 -0
- package/skill/topics/patterns.md +85 -0
- package/skill/topics/recipes.md +73 -0
- package/skill/topics/requests.md +41 -0
- package/skill/topics/testing.md +84 -0
- package/skill/topics/views.md +51 -0
- package/templates/guide.md +16 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"steps.js","sourceRoot":"","sources":["../../src/migrate/steps.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"steps.js","sourceRoot":"","sources":["../../src/migrate/steps.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AACrE,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAA;AAchE,sGAAsG;AACtG,MAAM,CAAC,MAAM,KAAK,GAAW;IAC3B;QACE,IAAI,EAAE,MAAM;QACZ,EAAE,EAAE,MAAM;QACV,OAAO,EAAE,qFAAqF;QAC9F,OAAO,EAAE,aAAa;QACtB,SAAS,EAAE,YAAY;KACxB;IACD;QACE,IAAI,EAAE,MAAM;QACZ,EAAE,EAAE,MAAM;QACV,OAAO,EAAE,2EAA2E;QACpF,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;QACnE,SAAS,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE;QACrB,KAAK,EAAE,UAAU;KAClB;IACD;QACE,IAAI,EAAE,MAAM;QACZ,EAAE,EAAE,MAAM;QACV,OAAO,EACL,gHAAgH;QAClH,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;QACnE,SAAS,EAAE,CAAC,EAAE,EAAE,EAAE;YAChB,MAAM,QAAQ,GAAI,EAA6D,CAAC,QAAQ,IAAI,EAAE,CAAA;YAC9F,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC;gBAAE,CAAC,CAAC,OAAO,KAAK,EAAE,CAAA;YACzD,MAAM,GAAG,GAAI,EAA+C,CAAC,GAAG,CAAA;YAChE,IAAI,GAAG,EAAE,CAAC;gBACR,GAAG,CAAC,KAAK,KAAK,EAAE,CAAA;gBAChB,GAAG,CAAC,QAAQ,KAAK,EAAE,CAAA;YACrB,CAAC;YACD,OAAO,EAAE,CAAA;QACX,CAAC;KACF;IACD;QACE,IAAI,EAAE,MAAM;QACZ,EAAE,EAAE,MAAM;QACV,OAAO,EACL,8IAA8I;QAChJ,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,CAAC;QAC1D,SAAS,EAAE,CAAC,EAAE,EAAE,EAAE;YAChB,CAAC;YAAC,EAA6B,CAAC,MAAM,KAAK,EAAE,CAAA;YAC7C,OAAO,EAAE,CAAA;QACX,CAAC;QACD,KAAK,EAAE,cAAc;KACtB;IACD;QACE,IAAI,EAAE,MAAM;QACZ,EAAE,EAAE,MAAM;QACV,OAAO,EACL,4OAA4O;QAC9O,OAAO,EAAE,UAAU;QACnB,SAAS,EAAE,mBAAmB;KAC/B;IACD;QACE,IAAI,EAAE,MAAM;QACZ,EAAE,EAAE,MAAM;QACV,OAAO,EACL,oUAAoU;QACtU,OAAO,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;QAC/D,SAAS,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE;KACtB;IACD;QACE,IAAI,EAAE,MAAM;QACZ,EAAE,EAAE,MAAM;QACV,OAAO,EACL,oJAAoJ;QACtJ,OAAO,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;QAC/D,SAAS,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE;KACtB;IACD;QACE,IAAI,EAAE,MAAM;QACZ,EAAE,EAAE,MAAM;QACV,OAAO,EACL,iOAAiO;QACnO,OAAO,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;QAC/D,SAAS,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE;KACtB;IACD;QACE,IAAI,EAAE,MAAM;QACZ,EAAE,EAAE,MAAM;QACV,OAAO,EACL,kQAAkQ;QACpQ,OAAO,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;QAC/D,SAAS,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE;KACtB;CACF,CAAA;AAED,MAAM,CAAC,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC,IAAI,CAAA;AAEpC,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,OAAe,EAAU,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAE5F,MAAM,IAAI,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAqB,CAAA;AAEhF,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAS,EAAE,CAAS,EAAU,EAAE;IAC3D,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;IACxB,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;IACxB,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,CAAA;AAC3B,CAAC,CAAA;AAED,gFAAgF;AAChF,MAAM,UAAU,KAAK,CAAC,IAAY,EAAE,EAAU;IAC5C,MAAM,GAAG,GAAW,EAAE,CAAA;IACtB,IAAI,EAAE,GAAG,IAAI,CAAA;IACb,OAAO,YAAY,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;QAChC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC,CAAA;QAC7C,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAA;QACtB,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACd,EAAE,GAAG,IAAI,CAAC,EAAE,CAAA;IACd,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hozu/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "The hozu command: check, map, why, show, get, browse, call, plan, add, build, dev, serve, docs, migrate and skill (all with --json)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"hozu",
|
|
@@ -37,7 +37,9 @@
|
|
|
37
37
|
"files": [
|
|
38
38
|
"bin",
|
|
39
39
|
"dist",
|
|
40
|
-
"schema"
|
|
40
|
+
"schema",
|
|
41
|
+
"skill",
|
|
42
|
+
"templates"
|
|
41
43
|
],
|
|
42
44
|
"engines": {
|
|
43
45
|
"node": ">=22.18"
|
|
@@ -46,12 +48,14 @@
|
|
|
46
48
|
"access": "public"
|
|
47
49
|
},
|
|
48
50
|
"dependencies": {
|
|
49
|
-
"@hozu/devtools": "0.
|
|
50
|
-
"@hozu/core": "0.
|
|
51
|
-
"@hozu/validator": "0.
|
|
52
|
-
"@hozu/machine": "0.
|
|
53
|
-
"@hozu/compiler": "0.
|
|
54
|
-
"@hozu/transform": "0.
|
|
55
|
-
|
|
51
|
+
"@hozu/devtools": "0.19.0",
|
|
52
|
+
"@hozu/core": "0.19.0",
|
|
53
|
+
"@hozu/validator": "0.19.0",
|
|
54
|
+
"@hozu/machine": "0.19.0",
|
|
55
|
+
"@hozu/compiler": "0.19.0",
|
|
56
|
+
"@hozu/transform": "0.19.0"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"create-hozu": "0.19.0"
|
|
56
60
|
}
|
|
57
61
|
}
|
|
@@ -153,6 +153,19 @@
|
|
|
153
153
|
"navigated": {
|
|
154
154
|
"type": "boolean"
|
|
155
155
|
},
|
|
156
|
+
"document": {
|
|
157
|
+
"type": "string",
|
|
158
|
+
"enum": [
|
|
159
|
+
"in place",
|
|
160
|
+
"reloaded",
|
|
161
|
+
"navigated"
|
|
162
|
+
],
|
|
163
|
+
"description": "What the step did to the document: kept it, loaded the same URL again, or loaded another one (ADR 0063 C3)."
|
|
164
|
+
},
|
|
165
|
+
"replaced": {
|
|
166
|
+
"type": "number",
|
|
167
|
+
"description": "With the document kept: how many elements are new after the step (a region drawn again)."
|
|
168
|
+
},
|
|
156
169
|
"url": {
|
|
157
170
|
"type": "string"
|
|
158
171
|
},
|
|
@@ -529,6 +529,23 @@
|
|
|
529
529
|
],
|
|
530
530
|
"additionalProperties": false
|
|
531
531
|
},
|
|
532
|
+
{
|
|
533
|
+
"type": "object",
|
|
534
|
+
"properties": {
|
|
535
|
+
"kind": {
|
|
536
|
+
"type": "string",
|
|
537
|
+
"const": "poll"
|
|
538
|
+
},
|
|
539
|
+
"seconds": {
|
|
540
|
+
"type": "number"
|
|
541
|
+
}
|
|
542
|
+
},
|
|
543
|
+
"required": [
|
|
544
|
+
"kind",
|
|
545
|
+
"seconds"
|
|
546
|
+
],
|
|
547
|
+
"additionalProperties": false
|
|
548
|
+
},
|
|
532
549
|
{
|
|
533
550
|
"type": "object",
|
|
534
551
|
"properties": {
|
|
@@ -1925,6 +1942,9 @@
|
|
|
1925
1942
|
},
|
|
1926
1943
|
"context": {
|
|
1927
1944
|
"$ref": "#/definitions/Json"
|
|
1945
|
+
},
|
|
1946
|
+
"previous": {
|
|
1947
|
+
"type": "string"
|
|
1928
1948
|
}
|
|
1929
1949
|
},
|
|
1930
1950
|
"required": [
|
package/skill/SKILL.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hozu
|
|
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
|
+
---
|
|
5
|
+
|
|
6
|
+
# Hozu
|
|
7
|
+
|
|
8
|
+
Hozu is not in your training data: this file and `npx hozu docs <topic>` are the whole API (skip
|
|
9
|
+
`node_modules/@hozu`). Diagnostics teach the other rules: apply their fix.
|
|
10
|
+
|
|
11
|
+
## The change loop
|
|
12
|
+
1. `npx hozu map`: the session, the verify line, each file's role and declaration's `file:line`; open only the
|
|
13
|
+
lines you change. A new app: `npx hozu add feature <name> --page / --with …`, then
|
|
14
|
+
`npx hozu docs feature`.
|
|
15
|
+
2. Edit everything the change needs (table below).
|
|
16
|
+
3. `npx hozu check` once. For an intended behaviour change, `npx hozu check --update-lock`, then list the accepted
|
|
17
|
+
`now:` lines in your summary.
|
|
18
|
+
4. Verify once with the line `hozu map` prints: `npx hozu browse <path> --session '…' --js both --do '…'` (the
|
|
19
|
+
app with and without JS; no server, no `curl`).
|
|
20
|
+
5. Show the person the change: `npx hozu show <file:line> --note "<in their words>"`.
|
|
21
|
+
|
|
22
|
+
## What to touch
|
|
23
|
+
| Change | Touch |
|
|
24
|
+
|---|---|
|
|
25
|
+
| UI-only state (a tab) | model: a context field, an event, an `on` whose `assign` sets it → views: the control |
|
|
26
|
+
| Filter / sort in the URL | the route's `search` (with a default) → `ui.link(route, params, { key })` → `search.key`; while typing: `seed` the machine from `search` |
|
|
27
|
+
| A per-item action (pin, archive) | model: the item field, an event, a mutation that `invalidates` the list tag, an `on` into a state with `invoke` → views: the per-item form (`hozu docs patterns`) → store it where the data lives |
|
|
28
|
+
| A control every state handles | `machine({ on: [...] })` |
|
|
29
|
+
| New page | `routes.ts` → a view with `route` → `ui.page(...)` in `hozu.config.ts` |
|
|
30
|
+
| UI (a button, a field) | `ui.use` of a kit component (`npx hozu docs components`) |
|
|
31
|
+
|
|
32
|
+
## Rules no diagnostic checks
|
|
33
|
+
- **Where data lives is the person's call; ask when the request does not say.** The visitor's own, no sign-in →
|
|
34
|
+
`'browser'` (`localStorage` in `fetch.ts`); a user's across devices → session + database; everyone's → a
|
|
35
|
+
database. Arrays in examples are stand-ins, never storage (`npx hozu docs data`).
|
|
36
|
+
- **Every query and mutation states `runs`:** a database, a secret or the session → `'server'` (resolver in
|
|
37
|
+
`app.ts`); browser storage or credentials → `'browser'`; a public API → `'either'` (`fetch.ts`).
|
|
38
|
+
- **Query resolvers only read.** Writes belong in mutations and endpoints.
|
|
39
|
+
- **Other users, reloads, sign-out:** verify in one `browse` chain with `--js both`, `--as <name>` per user.
|
|
40
|
+
- **Contracts only where a transition decides:** a guard, `navigate` or a computed value; the lock has the rest.
|
|
41
|
+
- **`'live'` only for push**.
|
|
42
|
+
- **`invalidates` drives the client refresh** of the queries with those tags.
|
|
43
|
+
- **`previews.ts` is for people:** read it only if asked or when HZ092 names a line.
|
|
44
|
+
|
|
45
|
+
## Topics (`npx hozu docs <topic>`; `--more`: options, edge cases)
|
|
46
|
+
| Task | Topic |
|
|
47
|
+
|---|---|
|
|
48
|
+
| a new feature, the files, a complete example | `feature` |
|
|
49
|
+
| elements, attributes, events, lists, links, reuse | `views` |
|
|
50
|
+
| states, events, invoke, timers, guards | `machine` |
|
|
51
|
+
| queries, mutations, tags, `fn()`, resolvers | `data` |
|
|
52
|
+
| where effects run, `fetch.ts`, browser storage, static hosts | `fetch` |
|
|
53
|
+
| contracts and the lock | `contracts` |
|
|
54
|
+
| routes, search, pages, `head`, 403 / 404 | `pages` |
|
|
55
|
+
| forms, checkboxes, bulk forms, no JS | `forms` |
|
|
56
|
+
| sign-in, sessions, per-user data | `auth` |
|
|
57
|
+
| filters, modes, per-item actions, load more | `patterns` |
|
|
58
|
+
| worked changes | `recipes` |
|
|
59
|
+
| webhooks, JSON APIs, redirects | `endpoints` |
|
|
60
|
+
| components, kits, browser APIs, DOM libraries, `previews.ts` | `components` |
|
|
61
|
+
| languages, env, HTTP, Markdown, tests, deploying, upgrading Hozu | `i18n` `env` `http` `content` `testing` `deploy` |
|
|
62
|
+
| a diagnostic code | `diagnostics` |
|
|
63
|
+
| requests from Hozu DevTools; showing a change (`hozu show`) | `requests` |
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { resolvers } from '@hozu/data'
|
|
2
|
+
import { app } from '@hozu/runtime-server'
|
|
3
|
+
import { addBookmark, getBookmark, listBookmarks, toggleRead } from './features/bookmarks/model.ts'
|
|
4
|
+
import project from './hozu.config.ts'
|
|
5
|
+
|
|
6
|
+
type Kind = 'article' | 'video' | 'podcast'
|
|
7
|
+
|
|
8
|
+
const demoBookmarks = [
|
|
9
|
+
{ id: 'b1', title: 'Closed-world UI', kind: 'article' as Kind, read: false },
|
|
10
|
+
{ id: 'b2', title: 'Islands explained', kind: 'video' as Kind, read: true },
|
|
11
|
+
]
|
|
12
|
+
let demoSeq = demoBookmarks.length
|
|
13
|
+
export default app({
|
|
14
|
+
resolvers: resolvers(project, (implement) => [
|
|
15
|
+
implement(listBookmarks, () => demoBookmarks.map((b) => ({ ...b }))),
|
|
16
|
+
implement(getBookmark, ({ id }, { fail }) => {
|
|
17
|
+
const b = demoBookmarks.find((x) => x.id === id)
|
|
18
|
+
return b ? { ...b } : fail('NotFound', { id })
|
|
19
|
+
}),
|
|
20
|
+
implement(addBookmark, ({ title, kind }, { fail }) => {
|
|
21
|
+
const clean = title.trim()
|
|
22
|
+
if (demoBookmarks.some((b) => b.title.toLowerCase() === clean.toLowerCase()))
|
|
23
|
+
return fail('Duplicate', { title: clean })
|
|
24
|
+
const b = { id: `b${++demoSeq}`, title: clean, kind, read: false }
|
|
25
|
+
demoBookmarks.unshift(b)
|
|
26
|
+
return { ...b }
|
|
27
|
+
}),
|
|
28
|
+
implement(toggleRead, ({ id }, { fail }) => {
|
|
29
|
+
const b = demoBookmarks.find((x) => x.id === id)
|
|
30
|
+
if (!b) return fail('NotFound', { id })
|
|
31
|
+
b.read = !b.read
|
|
32
|
+
return { ...b }
|
|
33
|
+
}),
|
|
34
|
+
]),
|
|
35
|
+
})
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { feature } from '@hozu/core'
|
|
2
|
+
import * as model from './model.ts'
|
|
3
|
+
import * as views from './views.ts'
|
|
4
|
+
|
|
5
|
+
export const bookmarks = feature({
|
|
6
|
+
id: 'bookmarks',
|
|
7
|
+
intent: {
|
|
8
|
+
summary:
|
|
9
|
+
'A shared reading list: add bookmarks with a kind, mark them read, filter unread, one page each.',
|
|
10
|
+
invariants: ['Titles are unique, case-insensitive', 'New bookmarks are listed first'],
|
|
11
|
+
},
|
|
12
|
+
declarations: [model, views],
|
|
13
|
+
})
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { event, fn, invoke, machine, mutation, on, query, tag, ui } from '@hozu/core'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { bookmarkPage, Show } from '../../routes.ts'
|
|
4
|
+
|
|
5
|
+
export const Kind = z.enum(['article', 'video', 'podcast'])
|
|
6
|
+
export const Bookmark = z.object({ id: z.string(), title: z.string(), kind: Kind, read: z.boolean() })
|
|
7
|
+
const Bookmarks = z.array(Bookmark)
|
|
8
|
+
const BookmarkKey = z.object({ id: z.string() })
|
|
9
|
+
const NewBookmark = z.object({
|
|
10
|
+
title: z.string().min(2, 'Use at least 2 characters').max(80, 'Use at most 80 characters'),
|
|
11
|
+
kind: Kind,
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
export const Draft = event({ payload: z.object({ text: z.string() }) })
|
|
15
|
+
export const Add = event({ payload: z.object({ title: z.string(), kind: Kind }) })
|
|
16
|
+
export const ToggleRead = event({ payload: BookmarkKey })
|
|
17
|
+
|
|
18
|
+
export const bookmarksTag = tag({ param: null })
|
|
19
|
+
|
|
20
|
+
export const listBookmarks = query({
|
|
21
|
+
input: z.object({}),
|
|
22
|
+
output: Bookmarks,
|
|
23
|
+
scope: 'public',
|
|
24
|
+
freshness: 'static',
|
|
25
|
+
tags: () => [bookmarksTag()],
|
|
26
|
+
runs: 'server',
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
export const getBookmark = query({
|
|
30
|
+
input: BookmarkKey,
|
|
31
|
+
output: Bookmark,
|
|
32
|
+
errors: { NotFound: BookmarkKey },
|
|
33
|
+
scope: 'public',
|
|
34
|
+
freshness: 'static',
|
|
35
|
+
tags: () => [bookmarksTag()],
|
|
36
|
+
runs: 'server',
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
export const addBookmark = mutation({
|
|
40
|
+
input: NewBookmark,
|
|
41
|
+
output: Bookmark,
|
|
42
|
+
errors: { Duplicate: z.object({ title: z.string() }) },
|
|
43
|
+
invalidates: () => [bookmarksTag()],
|
|
44
|
+
runs: 'server',
|
|
45
|
+
access: 'anyone',
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
export const toggleRead = mutation({
|
|
49
|
+
input: BookmarkKey,
|
|
50
|
+
output: Bookmark,
|
|
51
|
+
errors: { NotFound: BookmarkKey },
|
|
52
|
+
invalidates: () => [bookmarksTag()],
|
|
53
|
+
runs: 'server',
|
|
54
|
+
access: 'anyone',
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
const Visible = z.object({ items: Bookmarks, show: Show })
|
|
58
|
+
|
|
59
|
+
export const visible = fn({
|
|
60
|
+
input: Visible,
|
|
61
|
+
output: Bookmarks,
|
|
62
|
+
impl: ({ items, show }) => items.filter((b) => show === 'all' || !b.read),
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
export const isEmpty = fn({
|
|
66
|
+
input: Visible,
|
|
67
|
+
output: z.boolean(),
|
|
68
|
+
impl: ({ items, show }) => !items.some((b) => show === 'all' || !b.read),
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
export const DUPLICATE = 'This bookmark already exists'
|
|
72
|
+
|
|
73
|
+
export const bookmarksMachine = machine({
|
|
74
|
+
context: z.object({
|
|
75
|
+
draft: z.string(),
|
|
76
|
+
kind: Kind,
|
|
77
|
+
target: z.string(),
|
|
78
|
+
error: z.string().nullable(),
|
|
79
|
+
fields: z.object({ title: z.string().nullable(), kind: z.string().nullable() }),
|
|
80
|
+
}),
|
|
81
|
+
initialContext: {
|
|
82
|
+
draft: '',
|
|
83
|
+
kind: 'article',
|
|
84
|
+
target: '',
|
|
85
|
+
error: null,
|
|
86
|
+
fields: { title: null, kind: null },
|
|
87
|
+
},
|
|
88
|
+
initial: 'idle',
|
|
89
|
+
states: ({ ctx }) => ({
|
|
90
|
+
idle: {
|
|
91
|
+
on: [
|
|
92
|
+
on(Draft, {
|
|
93
|
+
target: 'idle',
|
|
94
|
+
assign: (e) => {
|
|
95
|
+
ctx.draft = e.text
|
|
96
|
+
},
|
|
97
|
+
}),
|
|
98
|
+
on(Add, {
|
|
99
|
+
target: 'adding',
|
|
100
|
+
assign: (e) => {
|
|
101
|
+
ctx.draft = e.title
|
|
102
|
+
ctx.kind = e.kind
|
|
103
|
+
ctx.error = null
|
|
104
|
+
ctx.fields = { title: null, kind: null }
|
|
105
|
+
},
|
|
106
|
+
}),
|
|
107
|
+
on(ToggleRead, {
|
|
108
|
+
target: 'toggling',
|
|
109
|
+
assign: (e) => {
|
|
110
|
+
ctx.target = e.id
|
|
111
|
+
},
|
|
112
|
+
}),
|
|
113
|
+
],
|
|
114
|
+
},
|
|
115
|
+
adding: {
|
|
116
|
+
invoke: invoke(addBookmark, {
|
|
117
|
+
input: { title: ctx.draft, kind: ctx.kind },
|
|
118
|
+
done: {
|
|
119
|
+
target: 'idle',
|
|
120
|
+
assign: () => {
|
|
121
|
+
ctx.draft = ''
|
|
122
|
+
},
|
|
123
|
+
navigate: (b) => ui.link(bookmarkPage, { id: b.id }),
|
|
124
|
+
},
|
|
125
|
+
failed: {
|
|
126
|
+
Duplicate: {
|
|
127
|
+
target: 'idle',
|
|
128
|
+
assign: () => {
|
|
129
|
+
ctx.error = DUPLICATE
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
Invalid: {
|
|
133
|
+
target: 'idle',
|
|
134
|
+
assign: (e) => {
|
|
135
|
+
ctx.fields = e.fields
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
Unexpected: {
|
|
139
|
+
target: 'idle',
|
|
140
|
+
assign: (e) => {
|
|
141
|
+
ctx.error = e.message
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
}),
|
|
146
|
+
},
|
|
147
|
+
toggling: {
|
|
148
|
+
invoke: invoke(toggleRead, {
|
|
149
|
+
input: { id: ctx.target },
|
|
150
|
+
done: 'idle',
|
|
151
|
+
failed: {
|
|
152
|
+
NotFound: 'idle',
|
|
153
|
+
Unexpected: {
|
|
154
|
+
target: 'idle',
|
|
155
|
+
assign: (e) => {
|
|
156
|
+
ctx.error = e.message
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
}),
|
|
161
|
+
},
|
|
162
|
+
}),
|
|
163
|
+
})
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { contract, ui } from '@hozu/core'
|
|
2
|
+
import { bookmarkPage, home } from '../../routes.ts'
|
|
3
|
+
import { Badge } from '../../ui/badge.ts'
|
|
4
|
+
import { Button } from '../../ui/button.ts'
|
|
5
|
+
import { Field } from '../../ui/field.ts'
|
|
6
|
+
import { Input } from '../../ui/input.ts'
|
|
7
|
+
import {
|
|
8
|
+
Add,
|
|
9
|
+
addBookmark,
|
|
10
|
+
bookmarksMachine,
|
|
11
|
+
Draft,
|
|
12
|
+
getBookmark,
|
|
13
|
+
isEmpty,
|
|
14
|
+
listBookmarks,
|
|
15
|
+
ToggleRead,
|
|
16
|
+
visible,
|
|
17
|
+
} from './model.ts'
|
|
18
|
+
|
|
19
|
+
const kinds = ['article', 'video', 'podcast'] as const
|
|
20
|
+
const shows = [
|
|
21
|
+
{ value: 'all', label: 'All' },
|
|
22
|
+
{ value: 'unread', label: 'Unread' },
|
|
23
|
+
] as const
|
|
24
|
+
|
|
25
|
+
export const Board = ui.view({
|
|
26
|
+
machine: bookmarksMachine,
|
|
27
|
+
route: home,
|
|
28
|
+
render: ({ ctx, search, when }) =>
|
|
29
|
+
ui.main({ class: 'mx-auto max-w-xl space-y-6 px-4 py-12' }, [
|
|
30
|
+
ui.h1({ class: 'text-3xl font-bold' }, ['Bookmarks']),
|
|
31
|
+
ui.form(
|
|
32
|
+
{
|
|
33
|
+
class: 'flex items-start gap-2',
|
|
34
|
+
on: { submit: ui.send(Add, { title: ui.dom.form('title'), kind: ui.dom.form('kind') }) },
|
|
35
|
+
},
|
|
36
|
+
[
|
|
37
|
+
ui.use(Field, {
|
|
38
|
+
props: { for: 'title', label: 'Title', error: ctx.fields.title, errorId: 'title-error' },
|
|
39
|
+
slots: {
|
|
40
|
+
control: ui.use(Input, {
|
|
41
|
+
props: {
|
|
42
|
+
id: 'title',
|
|
43
|
+
name: 'title',
|
|
44
|
+
value: ctx.draft,
|
|
45
|
+
required: true,
|
|
46
|
+
minlength: 2,
|
|
47
|
+
maxlength: 80,
|
|
48
|
+
invalid: ctx.fields.title !== null,
|
|
49
|
+
describedby: 'title-error',
|
|
50
|
+
},
|
|
51
|
+
on: { input: ui.send(Draft, { text: ui.dom.value }) },
|
|
52
|
+
}),
|
|
53
|
+
},
|
|
54
|
+
}),
|
|
55
|
+
ui.select(
|
|
56
|
+
{ name: 'kind', 'aria-label': 'Kind', class: 'rounded border px-2 py-2' },
|
|
57
|
+
kinds.map((k) => ui.option({ value: k, selected: ctx.kind === k }, [k])),
|
|
58
|
+
),
|
|
59
|
+
ui.use(Button, { props: { type: 'submit' } }, ['Add']),
|
|
60
|
+
],
|
|
61
|
+
),
|
|
62
|
+
ctx.error !== null && ui.p({ role: 'alert', class: 'text-rose-600' }, [ctx.error]),
|
|
63
|
+
when(
|
|
64
|
+
['adding'],
|
|
65
|
+
[
|
|
66
|
+
ui.p({ class: 'rounded border px-4 py-3 opacity-50', 'aria-busy': 'true' }, [
|
|
67
|
+
'Adding ',
|
|
68
|
+
ctx.draft,
|
|
69
|
+
'…',
|
|
70
|
+
]),
|
|
71
|
+
],
|
|
72
|
+
),
|
|
73
|
+
ui.nav(
|
|
74
|
+
{ class: 'flex gap-2', 'aria-label': 'Show' },
|
|
75
|
+
shows.map((s) =>
|
|
76
|
+
ui.a(
|
|
77
|
+
{
|
|
78
|
+
href: ui.link(home, null, { show: s.value }),
|
|
79
|
+
'aria-current': search.show === s.value,
|
|
80
|
+
class:
|
|
81
|
+
'rounded-full border px-3 py-1 aria-[current=true]:bg-indigo-600 aria-[current=true]:text-white',
|
|
82
|
+
},
|
|
83
|
+
[s.label],
|
|
84
|
+
),
|
|
85
|
+
),
|
|
86
|
+
),
|
|
87
|
+
ui.query(
|
|
88
|
+
listBookmarks,
|
|
89
|
+
{},
|
|
90
|
+
{
|
|
91
|
+
ready: (items) =>
|
|
92
|
+
isEmpty({ items, show: search.show })
|
|
93
|
+
? ui.p({ class: 'text-slate-500' }, ['No bookmarks'])
|
|
94
|
+
: ui.ul({ class: 'divide-y rounded border' }, [
|
|
95
|
+
ui.each(visible({ items, show: search.show }), 'id', (b) =>
|
|
96
|
+
ui.li({ class: 'flex items-center gap-3 px-4 py-3' }, [
|
|
97
|
+
ui.a({ href: ui.link(bookmarkPage, { id: b.id }), class: 'flex-1 underline' }, [
|
|
98
|
+
b.title,
|
|
99
|
+
]),
|
|
100
|
+
ui.use(Badge, {}, [b.kind]),
|
|
101
|
+
ui.use(
|
|
102
|
+
Button,
|
|
103
|
+
{ variant: { tone: 'quiet' }, on: { press: ui.send(ToggleRead, { id: b.id }) } },
|
|
104
|
+
[b.read ? 'Mark unread' : 'Mark read'],
|
|
105
|
+
),
|
|
106
|
+
]),
|
|
107
|
+
),
|
|
108
|
+
]),
|
|
109
|
+
pending: ui.p({}, ['Loading…']),
|
|
110
|
+
failed: { Unexpected: () => ui.p({ role: 'alert' }, ['Bookmarks are unavailable']) },
|
|
111
|
+
},
|
|
112
|
+
),
|
|
113
|
+
]),
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
export const Detail = ui.view({
|
|
117
|
+
route: bookmarkPage,
|
|
118
|
+
render: ({ params }) =>
|
|
119
|
+
ui.main({ class: 'mx-auto max-w-xl space-y-4 px-4 py-12' }, [
|
|
120
|
+
ui.query(
|
|
121
|
+
getBookmark,
|
|
122
|
+
{ id: params.id },
|
|
123
|
+
{
|
|
124
|
+
ready: (b) =>
|
|
125
|
+
ui.article({}, [
|
|
126
|
+
ui.h1({ class: 'text-3xl font-bold' }, [b.title]),
|
|
127
|
+
ui.p({}, ['Kind: ', b.kind]),
|
|
128
|
+
ui.use(Badge, {}, [b.read ? 'Read' : 'Unread']),
|
|
129
|
+
]),
|
|
130
|
+
pending: null,
|
|
131
|
+
failed: {
|
|
132
|
+
NotFound: () => ui.p({ role: 'alert' }, ['Bookmark not found']),
|
|
133
|
+
Unexpected: () => ui.p({ role: 'alert' }, ['Bookmark unavailable']),
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
),
|
|
137
|
+
ui.a({ href: ui.link(home, null), class: 'underline' }, ['Back']),
|
|
138
|
+
]),
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
export const addsBookmark = contract(bookmarksMachine, {
|
|
142
|
+
given: { state: 'idle' },
|
|
143
|
+
when: [
|
|
144
|
+
{ send: Add, payload: { title: 'Hozu talk', kind: 'podcast' } },
|
|
145
|
+
{ send: Draft, payload: { text: 'ignored while adding' } },
|
|
146
|
+
{ done: addBookmark, result: { id: 'b3', title: 'Hozu talk', kind: 'podcast', read: false } },
|
|
147
|
+
],
|
|
148
|
+
expect: {
|
|
149
|
+
state: 'idle',
|
|
150
|
+
changes: { kind: 'podcast' },
|
|
151
|
+
effects: [
|
|
152
|
+
{ effect: addBookmark, input: { title: 'Hozu talk', kind: 'podcast' } },
|
|
153
|
+
{ navigate: '/bookmarks/b3' },
|
|
154
|
+
],
|
|
155
|
+
},
|
|
156
|
+
})
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { project, ui } from '@hozu/core'
|
|
2
|
+
import { zodAdapter } from '@hozu/schema-zod'
|
|
3
|
+
import { bookmarks } from './features/bookmarks/feature.ts'
|
|
4
|
+
import { getBookmark, listBookmarks } from './features/bookmarks/model.ts'
|
|
5
|
+
import { Board, Detail } from './features/bookmarks/views.ts'
|
|
6
|
+
import { bookmarkPage, home } from './routes.ts'
|
|
7
|
+
import { kit as uiKit } from './ui/kit.ts'
|
|
8
|
+
|
|
9
|
+
export default project({
|
|
10
|
+
schema: zodAdapter,
|
|
11
|
+
app: new URL('./app.ts', import.meta.url),
|
|
12
|
+
previews: new URL('./previews.ts', import.meta.url),
|
|
13
|
+
styles: new URL('./app.css', import.meta.url),
|
|
14
|
+
site: { url: 'http://localhost:3000', name: 'Bookmarks', lang: 'en' },
|
|
15
|
+
routes: { home, bookmarkPage },
|
|
16
|
+
pages: [
|
|
17
|
+
ui.page(home, {
|
|
18
|
+
views: [Board],
|
|
19
|
+
head: { render: () => ({ title: 'Bookmarks', description: 'A shared reading list.' }) },
|
|
20
|
+
}),
|
|
21
|
+
ui.page(bookmarkPage, {
|
|
22
|
+
views: [Detail],
|
|
23
|
+
head: {
|
|
24
|
+
query: getBookmark,
|
|
25
|
+
input: (params) => ({ id: params.id }),
|
|
26
|
+
failed: { NotFound: 404 },
|
|
27
|
+
render: (b) => ({ title: b.title, description: b.title, type: 'article' }),
|
|
28
|
+
},
|
|
29
|
+
entries: { query: listBookmarks, input: {}, params: (b) => ({ id: b.id }) },
|
|
30
|
+
}),
|
|
31
|
+
],
|
|
32
|
+
kits: [uiKit],
|
|
33
|
+
features: [bookmarks],
|
|
34
|
+
})
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { previews } from '@hozu/core/preview'
|
|
2
|
+
import { listBookmarks } from './features/bookmarks/model.ts'
|
|
3
|
+
import { home } from './routes.ts'
|
|
4
|
+
import { Button } from './ui/button.ts'
|
|
5
|
+
|
|
6
|
+
export default previews((p) => [
|
|
7
|
+
p.component(Button, 'Long label', {
|
|
8
|
+
variant: { tone: 'primary' },
|
|
9
|
+
children: 'Add it to the shared reading list',
|
|
10
|
+
}),
|
|
11
|
+
p.page(home, 'Empty list', [p.data(listBookmarks, [])]),
|
|
12
|
+
p.page(home, 'List failed', [p.fail(listBookmarks, 'Unexpected')]),
|
|
13
|
+
])
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { route } from '@hozu/core'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
|
|
4
|
+
export const Show = z.enum(['all', 'unread'])
|
|
5
|
+
|
|
6
|
+
export const home = route({ path: '/', params: null, search: z.object({ show: Show.default('all') }) })
|
|
7
|
+
export const bookmarkPage = route({
|
|
8
|
+
path: '/bookmarks/:id',
|
|
9
|
+
params: z.object({ id: z.string() }),
|
|
10
|
+
search: null,
|
|
11
|
+
})
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ui } from '@hozu/core'
|
|
2
|
+
import { tv } from './tv.ts'
|
|
3
|
+
|
|
4
|
+
const styles = tv({ base: 'rounded-full bg-slate-100 px-2 py-0.5 text-xs text-slate-600' })
|
|
5
|
+
|
|
6
|
+
export const Badge = ui.component({
|
|
7
|
+
tag: 'span',
|
|
8
|
+
styles,
|
|
9
|
+
children: true,
|
|
10
|
+
render: ({ children }) => ui.span({}, children),
|
|
11
|
+
})
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ui } from '@hozu/core'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { tv } from './tv.ts'
|
|
4
|
+
|
|
5
|
+
const styles = tv({
|
|
6
|
+
base: 'inline-flex items-center justify-center gap-2 rounded px-4 py-2 font-medium',
|
|
7
|
+
variants: {
|
|
8
|
+
tone: {
|
|
9
|
+
primary: 'bg-indigo-600 text-white hover:bg-indigo-700',
|
|
10
|
+
quiet: 'px-2 py-1 text-sm text-slate-600 hover:text-slate-900',
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
defaultVariants: { tone: 'primary' },
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
export const Button = ui.component({
|
|
17
|
+
tag: 'button',
|
|
18
|
+
styles,
|
|
19
|
+
props: z.object({ type: z.enum(['button', 'submit']).default('button') }),
|
|
20
|
+
children: true,
|
|
21
|
+
events: ['press'],
|
|
22
|
+
render: ({ props, children, on }) => ui.button({ type: props.type, on: { click: on.press } }, children),
|
|
23
|
+
})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ui } from '@hozu/core'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { tv } from './tv.ts'
|
|
4
|
+
|
|
5
|
+
const styles = tv({
|
|
6
|
+
slots: { base: 'flex-1 space-y-1', label: 'sr-only', error: 'text-sm text-rose-600' },
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
export const Field = ui.component({
|
|
10
|
+
tag: 'div',
|
|
11
|
+
styles,
|
|
12
|
+
props: z.object({ for: z.string(), label: z.string(), error: z.string().nullable(), errorId: z.string() }),
|
|
13
|
+
slots: ['control'],
|
|
14
|
+
render: ({ props, slots, classes }) =>
|
|
15
|
+
ui.div({}, [
|
|
16
|
+
ui.label({ for: props.for, class: classes.label }, [props.label]),
|
|
17
|
+
slots.control,
|
|
18
|
+
ui.p({ id: props.errorId, class: classes.error }, [props.error]),
|
|
19
|
+
]),
|
|
20
|
+
})
|