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.
@@ -0,0 +1,27 @@
1
+ export type Runner = 'pnpm exec' | 'npx' | 'yarn' | 'bunx';
2
+ export declare const RUNNERS: readonly Runner[];
3
+ export declare const BEGIN = "<!-- hozu: generated by hozu skill; edits between these markers are replaced -->";
4
+ export declare const END = "<!-- /hozu -->";
5
+ export declare const TARGETS: readonly [{
6
+ readonly guide: 'CLAUDE.md';
7
+ readonly skill: '.claude/skills/hozu';
8
+ readonly read: 'Use the `hozu` skill (`__SKILL__/SKILL.md`).';
9
+ }, {
10
+ readonly guide: 'AGENTS.md';
11
+ readonly skill: '.agents/skills/hozu';
12
+ readonly read: 'Read `__SKILL__/SKILL.md` before writing or changing any Hozu code.';
13
+ }];
14
+ export type Target = (typeof TARGETS)[number];
15
+ export declare function currentGuide(): string;
16
+ /** The 0.8 guide for one target, between the markers. */
17
+ export declare const guideBlock: (target: Target, name: string, runner: Runner, template?: string) => string;
18
+ export type BlockResult = {
19
+ kind: 'replaced' | 'written' | 'current';
20
+ code: string;
21
+ } | {
22
+ kind: 'custom';
23
+ block: string;
24
+ };
25
+ /** The new Hozu block of a CLAUDE.md / AGENTS.md, between markers; a file without markers is `custom`. */
26
+ export declare function migrateGuide(existing: string | null, target: Target, dir: string, runner: Runner, template?: string): BlockResult;
27
+ //# sourceMappingURL=guide.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guide.d.ts","sourceRoot":"","sources":["../src/guide.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,MAAM,GAAG,WAAW,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAA;AAE1D,eAAO,MAAM,OAAO,EAAE,SAAS,MAAM,EAAyC,CAAA;AAE9E,eAAO,MAAM,KAAK,qFAAqF,CAAA;AACvG,eAAO,MAAM,GAAG,mBAAmB,CAAA;AAEnC,eAAO,MAAM,OAAO;aAChB,KAAK,EAAE,WAAW;aAAE,KAAK,EAAE,qBAAqB;aAAE,IAAI,EAAE,8CAA8C;;aAEtG,KAAK,EAAE,WAAW;aAClB,KAAK,EAAE,qBAAqB;aAC5B,IAAI,EAAE,qEAAqE;EAErE,CAAA;AAEV,MAAM,MAAM,MAAM,GAAG,CAAC,OAAO,OAAO,CAAC,CAAC,MAAM,CAAC,CAAA;AAmB7C,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED,yDAAyD;AACzD,eAAO,MAAM,UAAU,WAAY,MAAM,QAAQ,MAAM,UAAU,MAAM,8BACW,CAAA;AAElF,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC1D;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAErC,0GAA0G;AAC1G,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,EACd,QAAQ,SAAiB,GACxB,WAAW,CAab"}
package/dist/guide.js ADDED
@@ -0,0 +1,48 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { basename } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ export const RUNNERS = ['pnpm exec', 'npx', 'yarn', 'bunx'];
5
+ export const BEGIN = '<!-- hozu: generated by hozu skill; edits between these markers are replaced -->';
6
+ export const END = '<!-- /hozu -->';
7
+ export const TARGETS = [
8
+ { guide: 'CLAUDE.md', skill: '.claude/skills/hozu', read: 'Use the `hozu` skill (`__SKILL__/SKILL.md`).' },
9
+ {
10
+ guide: 'AGENTS.md',
11
+ skill: '.agents/skills/hozu',
12
+ read: 'Read `__SKILL__/SKILL.md` before writing or changing any Hozu code.',
13
+ },
14
+ ];
15
+ const fill = (text, values) => Object.entries(values).reduce((out, [key, value]) => out.replaceAll(`__${key}__`, value), text);
16
+ const note = (runner, skillRunner) => runner === skillRunner
17
+ ? ''
18
+ : `\nThe skill writes commands as \`${skillRunner} …\`; in this app use \`${runner} …\`.\n`;
19
+ function filled(template, target, name, runner, skillRunner) {
20
+ return fill(fill(template, { READ: target.read }), {
21
+ NAME: name,
22
+ RUN: runner,
23
+ SKILL: target.skill,
24
+ NOTE: note(runner, skillRunner),
25
+ });
26
+ }
27
+ export function currentGuide() {
28
+ return readFileSync(fileURLToPath(new URL('../templates/guide.md', import.meta.url)), 'utf8');
29
+ }
30
+ /** The 0.8 guide for one target, between the markers. */
31
+ export const guideBlock = (target, name, runner, template = currentGuide()) => `${BEGIN}\n${filled(template, target, name, runner, 'npx').trimEnd()}\n${END}\n`;
32
+ /** The new Hozu block of a CLAUDE.md / AGENTS.md, between markers; a file without markers is `custom`. */
33
+ export function migrateGuide(existing, target, dir, runner, template = currentGuide()) {
34
+ const begin = existing?.indexOf(BEGIN) ?? -1;
35
+ const end = existing?.indexOf(END) ?? -1;
36
+ const own = begin >= 0 && end > begin ? existing.slice(begin, end) : existing;
37
+ const name = own?.match(/^# (.+)$/m)?.[1] ?? basename(dir);
38
+ const detected = RUNNERS.find((r) => own?.includes(`${r} hozu `)) ?? runner;
39
+ const block = guideBlock(target, name, detected, template);
40
+ if (existing === null)
41
+ return { kind: 'written', code: block };
42
+ if (begin >= 0 && end > begin) {
43
+ const code = existing.slice(0, begin) + block + existing.slice(end + END.length).replace(/^\n/, '');
44
+ return { kind: code === existing ? 'current' : 'replaced', code };
45
+ }
46
+ return { kind: 'custom', block };
47
+ }
48
+ //# sourceMappingURL=guide.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guide.js","sourceRoot":"","sources":["../src/guide.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
+ import { type Runner } from './guide.ts';
1
2
  export type Agent = 'claude' | 'agents' | 'both';
2
- export type Runner = 'pnpm exec' | 'npx' | 'yarn' | 'bunx';
3
+ export type { BlockResult, Runner, Target } from './guide.ts';
4
+ export { BEGIN, currentGuide, END, guideBlock, migrateGuide, RUNNERS, TARGETS } from './guide.ts';
3
5
  export declare const AGENTS: readonly Agent[];
4
6
  export declare const defaultSkill: string;
5
7
  export declare const runnerOf: (userAgent: string | undefined) => Runner;
@@ -8,7 +10,14 @@ export interface AgentOptions {
8
10
  runner: Runner;
9
11
  skillSource?: string;
10
12
  }
11
- export declare function writeAgentFiles(dir: string, agent: Agent, options: AgentOptions): Promise<string[]>;
13
+ export interface AgentFiles {
14
+ written: string[];
15
+ custom: {
16
+ guide: string;
17
+ block: string;
18
+ }[];
19
+ }
20
+ export declare function writeAgentFiles(dir: string, agent: Agent, options: AgentOptions): Promise<AgentFiles>;
12
21
  export interface CreateOptions extends AgentOptions {
13
22
  agent: Agent;
14
23
  version: string;
@@ -24,6 +33,7 @@ export declare const packageJson: (name: string, version: string) => {
24
33
  };
25
34
  dependencies: {
26
35
  '@hozu/adapter-node': string;
36
+ '@hozu/cli': string;
27
37
  '@hozu/core': string;
28
38
  '@hozu/css': string;
29
39
  '@hozu/data': string;
@@ -33,7 +43,6 @@ export declare const packageJson: (name: string, version: string) => {
33
43
  zod: string;
34
44
  };
35
45
  devDependencies: {
36
- '@hozu/cli': string;
37
46
  '@hozu/testing': string;
38
47
  '@types/node': string;
39
48
  typescript: string;
@@ -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,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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAgB,KAAK,MAAM,EAAW,MAAM,YAAY,CAAA;AAE/D,MAAM,MAAM,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAA;AAChD,YAAY,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAC7D,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,GAAG,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAEjG,eAAO,MAAM,MAAM,EAAE,SAAS,KAAK,EAAiC,CAAA;AAGpE,eAAO,MAAM,YAAY,QAAsB,CAAA;AAM/C,eAAO,MAAM,QAAQ,cAAe,MAAM,GAAG,SAAS,KAAG,MAGxD,CAAA;AAQD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAC3C;AAED,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC,CAkB3G;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,WAAW;QACX,YAAY;QACZ,WAAW;QACX,YAAY;QACZ,sBAAsB;QACtB,kBAAkB;QAClB,iBAAiB;QACjB,GAAG;;;QAGH,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
@@ -1,52 +1,37 @@
1
1
  import { cp, mkdir, readdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
2
2
  import { dirname, join, relative } from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
+ import { migrateGuide, TARGETS } from './guide.js';
5
+ export { BEGIN, currentGuide, END, guideBlock, migrateGuide, RUNNERS, TARGETS } from './guide.js';
4
6
  export const AGENTS = ['claude', 'agents', 'both'];
5
7
  const root = fileURLToPath(new URL('../', import.meta.url));
6
8
  export const defaultSkill = join(root, 'skill');
7
9
  const templates = join(root, 'templates');
8
- const targets = (agent) => [
9
- {
10
- guide: 'CLAUDE.md',
11
- skill: '.claude/skills/hozu',
12
- read: 'Use the `hozu` skill (`__SKILL__/SKILL.md`).',
13
- },
14
- {
15
- guide: 'AGENTS.md',
16
- skill: '.agents/skills/hozu',
17
- read: 'Read `__SKILL__/SKILL.md` before writing or changing any Hozu code.',
18
- },
19
- ].filter((_, i) => agent === 'both' || (agent === 'claude' ? i === 0 : i === 1));
10
+ const targets = (agent) => TARGETS.filter((_, i) => agent === 'both' || (agent === 'claude' ? i === 0 : i === 1));
20
11
  export const runnerOf = (userAgent) => {
21
12
  const name = userAgent?.split('/')[0];
22
13
  return name === 'npm' ? 'npx' : name === 'yarn' ? 'yarn' : name === 'bun' ? 'bunx' : 'pnpm exec';
23
14
  };
24
15
  const exists = (path) => stat(path).then(() => true, () => false);
25
- const fill = (text, values) => Object.entries(values).reduce((out, [key, value]) => out.replaceAll(`__${key}__`, value), text);
26
16
  export async function writeAgentFiles(dir, agent, options) {
27
- const written = [];
28
- const guide = await readFile(join(templates, 'guide.md'), 'utf8');
17
+ const out = { written: [], custom: [] };
29
18
  for (const t of targets(agent)) {
30
19
  const skill = join(dir, t.skill);
31
20
  await rm(skill, { recursive: true, force: true });
32
21
  await mkdir(dirname(skill), { recursive: true });
33
22
  await cp(options.skillSource ?? defaultSkill, skill, { recursive: true });
34
- written.push(t.skill);
23
+ out.written.push(t.skill);
35
24
  const file = join(dir, t.guide);
36
- if (await exists(file))
37
- continue;
38
- const values = {
39
- NAME: options.name,
40
- RUN: options.runner,
41
- SKILL: t.skill,
42
- NOTE: options.runner === 'pnpm exec'
43
- ? ''
44
- : `\nThe skill writes commands as \`pnpm exec …\`; in this app use \`${options.runner} …\`.\n`,
45
- };
46
- await writeFile(file, fill(fill(guide, { READ: t.read }), values));
47
- written.push(t.guide);
25
+ const existing = (await exists(file)) ? await readFile(file, 'utf8') : null;
26
+ const result = migrateGuide(existing, t, dir, options.runner);
27
+ if (result.kind === 'custom')
28
+ out.custom.push({ guide: t.guide, block: result.block });
29
+ else if (result.kind !== 'current') {
30
+ await writeFile(file, result.code);
31
+ out.written.push(t.guide);
32
+ }
48
33
  }
49
- return written;
34
+ return out;
50
35
  }
51
36
  async function files(dir) {
52
37
  const out = [];
@@ -65,11 +50,12 @@ export const packageJson = (name, version) => ({
65
50
  type: 'module',
66
51
  scripts: {
67
52
  check: 'hozu check',
68
- start: 'node --import @hozu/transform/register serve.ts',
53
+ start: 'hozu serve',
69
54
  build: 'hozu build',
70
55
  },
71
56
  dependencies: {
72
57
  '@hozu/adapter-node': `^${version}`,
58
+ '@hozu/cli': `^${version}`,
73
59
  '@hozu/core': `^${version}`,
74
60
  '@hozu/css': `^${version}`,
75
61
  '@hozu/data': `^${version}`,
@@ -79,7 +65,6 @@ export const packageJson = (name, version) => ({
79
65
  zod: '^4.6.5',
80
66
  },
81
67
  devDependencies: {
82
- '@hozu/cli': `^${version}`,
83
68
  '@hozu/testing': `^${version}`,
84
69
  '@types/node': '^22.20.4',
85
70
  typescript: '^7.0.2',
@@ -95,12 +80,12 @@ export async function createApp(dir, options) {
95
80
  const rel = relative(app, source);
96
81
  const target = join(dir, rel === 'gitignore' ? '.gitignore' : rel);
97
82
  await mkdir(dirname(target), { recursive: true });
98
- await writeFile(target, fill(await readFile(source, 'utf8'), { NAME: options.name }));
83
+ await writeFile(target, (await readFile(source, 'utf8')).replaceAll('__NAME__', options.name));
99
84
  written.push(relative(dir, target));
100
85
  }
101
86
  await writeFile(join(dir, 'package.json'), `${JSON.stringify(packageJson(options.name, options.version), null, 2)}\n`);
102
87
  written.push('package.json');
103
- written.push(...(await writeAgentFiles(dir, options.agent, options)));
88
+ written.push(...(await writeAgentFiles(dir, options.agent, options)).written);
104
89
  return written;
105
90
  }
106
91
  //# sourceMappingURL=index.js.map
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":"AAAA,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACpF,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,YAAY,EAAe,OAAO,EAAE,MAAM,YAAY,CAAA;AAI/D,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,GAAG,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAEjG,MAAM,CAAC,MAAM,MAAM,GAAqB,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAA;AAEpE,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,KAAK,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;AAC3D,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;AAC/C,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAA;AAEzC,MAAM,OAAO,GAAG,CAAC,KAAY,EAAE,EAAE,CAC/B,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,MAAM,IAAI,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;AAExF,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,SAA6B,EAAU,EAAE;IAChE,MAAM,IAAI,GAAG,SAAS,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;IACrC,OAAO,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,CAAA;AAClG,CAAC,CAAA;AAED,MAAM,MAAM,GAAG,CAAC,IAAY,EAAE,EAAE,CAC9B,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CACb,GAAG,EAAE,CAAC,IAAI,EACV,GAAG,EAAE,CAAC,KAAK,CACZ,CAAA;AAaH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,GAAW,EAAE,KAAY,EAAE,OAAqB;IACpF,MAAM,GAAG,GAAe,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAA;IACnD,KAAK,MAAM,CAAC,IAAI,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,CAAA;QAChC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;QACjD,MAAM,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;QAChD,MAAM,EAAE,CAAC,OAAO,CAAC,WAAW,IAAI,YAAY,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;QACzE,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA;QACzB,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,CAAA;QAC/B,MAAM,QAAQ,GAAG,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;QAC3E,MAAM,MAAM,GAAG,YAAY,CAAC,QAAQ,EAAE,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;QAC7D,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ;YAAE,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAA;aACjF,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YACnC,MAAM,SAAS,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAA;YAClC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA;QAC3B,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,KAAK,UAAU,KAAK,CAAC,GAAW;IAC9B,MAAM,GAAG,GAAa,EAAE,CAAA;IACxB,KAAK,MAAM,KAAK,IAAI,MAAM,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;QAChE,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;QAClC,IAAI,KAAK,CAAC,WAAW,EAAE;YAAE,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;;YACpD,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACrB,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAOD,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,IAAY,EAAE,OAAe,EAAE,EAAE,CAAC,CAAC;IAC7D,IAAI;IACJ,OAAO,EAAE,IAAI;IACb,IAAI,EAAE,QAAQ;IACd,OAAO,EAAE;QACP,KAAK,EAAE,YAAY;QACnB,KAAK,EAAE,YAAY;QACnB,KAAK,EAAE,YAAY;KACpB;IACD,YAAY,EAAE;QACZ,oBAAoB,EAAE,IAAI,OAAO,EAAE;QACnC,WAAW,EAAE,IAAI,OAAO,EAAE;QAC1B,YAAY,EAAE,IAAI,OAAO,EAAE;QAC3B,WAAW,EAAE,IAAI,OAAO,EAAE;QAC1B,YAAY,EAAE,IAAI,OAAO,EAAE;QAC3B,sBAAsB,EAAE,IAAI,OAAO,EAAE;QACrC,kBAAkB,EAAE,IAAI,OAAO,EAAE;QACjC,iBAAiB,EAAE,IAAI,OAAO,EAAE;QAChC,GAAG,EAAE,QAAQ;KACd;IACD,eAAe,EAAE;QACf,eAAe,EAAE,IAAI,OAAO,EAAE;QAC9B,aAAa,EAAE,UAAU;QACzB,UAAU,EAAE,QAAQ;KACrB;IACD,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;CAC7B,CAAC,CAAA;AAEF,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,GAAW,EAAE,OAAsB;IACjE,IAAI,CAAC,MAAM,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM;QACpD,MAAM,IAAI,KAAK,CAAC,GAAG,GAAG,6CAA6C,CAAC,CAAA;IACtE,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;IAClC,MAAM,OAAO,GAAa,EAAE,CAAA;IAC5B,KAAK,MAAM,MAAM,IAAI,MAAM,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACtC,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;QACjC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,WAAW,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;QAClE,MAAM,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;QACjD,MAAM,SAAS,CAAC,MAAM,EAAE,CAAC,MAAM,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,UAAU,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAA;QAC9F,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAA;IACrC,CAAC;IACD,MAAM,SAAS,CACb,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,EACzB,GAAG,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAC3E,CAAA;IACD,OAAO,CAAC,IAAI,CAAC,cAAc,CAAC,CAAA;IAC5B,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,eAAe,CAAC,GAAG,EAAE,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAA;IAC7E,OAAO,OAAO,CAAA;AAChB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-hozu",
3
- "version": "0.7.0",
3
+ "version": "0.9.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
@@ -5,108 +5,51 @@ description: Build or change an app with the Hozu framework (packages @hozu/*, f
5
5
 
6
6
  # Hozu
7
7
 
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.
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 when you break them: apply their fix.
12
10
 
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`).
23
- - A filter in the URL starts the machine: `seed: ({ search }) => ({ q: search.q })` on the view, then read `ctx.q`.
24
- `machine({ on })` holds transitions every idle state shares; `fn` bodies may call helpers from the same module.
11
+ ## The change loop
12
+ 1. `npx hozu map`: the session shape, the verify line, every file's role, each declaration's `file:line`. Open
13
+ only the lines the change touches. 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 '…'`. It runs
19
+ the same app as `npm start`, with and without JS; do not start a server or use `curl`.
25
20
 
26
- ## Files and commands
27
- ```
28
- hozu.config.ts project({ schema, site, routes, pages, features }) routes.ts route() declarations
29
- features/<name>/model.ts schemas, events, effects, fns, machine views.ts views, contracts
30
- features/<name>/feature.ts feature({ declarations: [model, views] }) server.ts implement(...) resolvers
31
- ```
32
- ```
33
- npx hozu check # after every edit: types, rules, contracts
34
- npx hozu check --update-lock # accept an intended behaviour change
35
- npx hozu add feature items --page / --with auth,detail,toggle,filter,remove
36
- npx hozu map # outline with file:line
37
- npx hozu get / --select button --forms # a page, no server needed
38
- npx hozu post / --field title=A --next / # a no-JS form post, cookies shown
39
- npx hozu browse / --do 'fill Search=a' --do 'click Save' # real browser: errors, widgets, text
40
- npx hozu docs views # one topic
41
- ```
42
- `get` / `post` / `browse` replace a running server for checks; `npm start` runs the app. Relative imports end in `.ts`.
21
+ ## What to touch
22
+ | Change | Touch |
23
+ |---|---|
24
+ | UI-only state (a tab) | model: a context field, an event, an `on` whose `assign` sets it → views: the control |
25
+ | Filter / sort in the URL | the route's `search` (with a default) → `ui.link(route, params, { key })` → `search.key`; filtering while typing: `seed: ({ search }) => ({ key: search.key })`, read `ctx.key` |
26
+ | A per-item action stored on the server (pin, archive) | model: the item field, an event, a mutation that `invalidates` the list tag, `on(E, { target: 'pinning', assign: (e) => { ctx.target = e.id } })`, a state with `invoke` → views: the per-item form (`hozu docs patterns`) → app: store it, sort in the list resolver |
27
+ | A control every state handles | `machine({ on: [...] })` |
28
+ | New page | `routes.ts` → a view with `route` → `ui.page(...)` in `hozu.config.ts` |
29
+ | UI (a button, a field) | `ui.use` of a kit component; the catalog: `npx hozu docs components` |
43
30
 
44
- ## A feature in one screen
45
- ```ts
46
- // model.ts
47
- export const Item = z.object({ id: z.string(), title: z.string(), done: z.boolean() })
48
- export const Add = event({ payload: z.object({ title: z.string() }) })
49
- export const itemsTag = tag({ param: null })
50
- export const listItems = query({ input: z.object({}), output: z.array(Item), scope: 'public',
51
- freshness: 'static', tags: () => [itemsTag()] })
52
- export const addItem = mutation({ input: z.object({ title: z.string().min(2, 'Too short') }), output: Item,
53
- errors: { Duplicate: z.object({ title: z.string() }) }, invalidates: () => [itemsTag()] })
54
- export const items = machine({
55
- context: z.object({ draft: z.string(), error: z.string().nullable() }),
56
- initialContext: { draft: '', error: null },
57
- initial: 'idle',
58
- states: ({ ctx }) => ({
59
- idle: { on: [on(Add, { target: 'adding', assign: (e) => { ctx.draft = e.title; ctx.error = null } })] },
60
- adding: {
61
- invoke: invoke(addItem, {
62
- input: { title: ctx.draft },
63
- done: { target: 'idle', assign: () => { ctx.draft = '' } },
64
- failed: {
65
- Duplicate: { target: 'idle', assign: () => { ctx.error = 'Already listed' } },
66
- Unexpected: { target: 'idle', assign: (e) => { ctx.error = e.message } },
67
- },
68
- }),
69
- },
70
- }),
71
- })
72
- // views.ts
73
- export const Board = ui.view({
74
- machine: items,
75
- render: ({ ctx, when }) =>
76
- ui.main({ class: 'mx-auto max-w-xl' }, [
77
- ui.form({ on: { submit: ui.send(Add, { title: ui.dom.form('title') }) } }, [
78
- ui.input({ name: 'title', required: true, value: ctx.draft }),
79
- ui.button({ type: 'submit' }, ['Add']),
80
- ]),
81
- ctx.error !== null && ui.p({ role: 'alert' }, [ctx.error]),
82
- when(['adding'], [ui.p({ 'aria-busy': 'true' }, [`Adding ${ctx.draft}…`])]),
83
- ui.query(listItems, {}, {
84
- ready: (list) => ui.ul({}, [ui.each(list, 'id', (i) => ui.li({}, [i.title, i.done ? ' ✓' : '']))]),
85
- failed: { Unexpected: () => ui.p({ role: 'alert' }, ['Unavailable']) },
86
- }),
87
- ]),
88
- })
89
- // feature.ts
90
- import * as model from './model.ts'
91
- import * as views from './views.ts'
92
- export const todos = feature({ id: 'todos', intent: { summary: 'A to-do list' }, declarations: [model, views] })
93
- ```
94
- Every declaration a listed module exports is registered under its name; schemas and helpers are ignored. Resolvers:
95
- `implement(addItem, ({ title }, { fail }) => exists ? fail('Duplicate', { title }) : save(title))`.
31
+ ## Rules no diagnostic checks
32
+ - **Query resolvers only read.** Writes belong in mutation and endpoint resolvers; a prefetched link runs queries.
33
+ - **Other users, reloads, sign-out:** verify them in one `browse` chain with `--js both` (`--as <name>` per user).
34
+ - **Contracts only where a transition decides:** a guard, a `navigate`, a `fn` or computed value. Copy-only
35
+ transitions are reviewed in the lock.
36
+ - **`'live'` only for push** to pages that are already open.
37
+ - **`invalidates` drives the client refresh** of every query with those tags, whatever its freshness.
96
38
 
97
- ## Topics (`hozu docs <topic>`)
39
+ ## Topics (`npx hozu docs <topic>`)
98
40
  | Task | Topic |
99
41
  |---|---|
100
- | elements, attributes, classes, events, DOM fields, lists, links | `views` |
101
- | states, events, invoke, timers, guards, updates | `machine` |
102
- | queries, mutations, tags, errors, `fn()`, resolvers | `data` |
103
- | writing and checking contracts | `contracts` |
104
- | routes, params, search, pages, `head`, 404, sitemap | `pages` |
105
- | forms without JS, field errors, selects | `forms` |
42
+ | a new feature, the files, a complete example | `feature` |
43
+ | elements, attributes, events, lists, links, reuse | `views` |
44
+ | states, events, invoke, timers, guards | `machine` |
45
+ | queries, mutations, tags, `fn()`, resolvers | `data` |
46
+ | contracts and the lock | `contracts` |
47
+ | routes, search, pages, `head`, 403 / 404 | `pages` |
48
+ | forms, checkboxes, bulk forms, no JS | `forms` |
106
49
  | sign-in, sessions, per-user data | `auth` |
107
- | common UI: filters, search in the URL, modes, per-item actions, load more | `patterns` |
108
- | worked changes: enum field, bulk action, detail field / page | `recipes` |
109
- | webhooks and JSON APIs | `endpoints` |
110
- | browser APIs and DOM libraries (maps, charts) | `widgets` |
111
- | languages, env, HTTP, Markdown, images, preview, PWA, tests, deployment | `i18n`, `env`, `http`, `content`, `testing`, `deploy` |
50
+ | filters, modes, per-item actions, load more | `patterns` |
51
+ | worked changes | `recipes` |
52
+ | webhooks, JSON APIs, redirects | `endpoints` |
53
+ | components, kits, browser APIs, DOM libraries | `components` |
54
+ | languages, env, HTTP, Markdown, tests, deploying | `i18n` `env` `http` `content` `testing` `deploy` |
112
55
  | a diagnostic code | `diagnostics` |
@@ -1,16 +1,17 @@
1
1
  import { resolvers } from '@hozu/data'
2
+ import { app } from '@hozu/runtime-server'
2
3
  import { addBookmark, getBookmark, listBookmarks, toggleRead } from './features/bookmarks/model.ts'
3
4
  import project from './hozu.config.ts'
4
5
 
5
6
  type Kind = 'article' | 'video' | 'podcast'
6
7
 
7
- export function createResolvers() {
8
- const items = [
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 seq = items.length
13
- return resolvers(project, (implement) => [
8
+ const items = [
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 seq = items.length
13
+ export default app({
14
+ resolvers: resolvers(project, (implement) => [
14
15
  implement(listBookmarks, () => items.map((b) => ({ ...b }))),
15
16
  implement(getBookmark, ({ id }, { fail }) => {
16
17
  const b = items.find((x) => x.id === id)
@@ -30,5 +31,5 @@ export function createResolvers() {
30
31
  b.read = !b.read
31
32
  return { ...b }
32
33
  }),
33
- ])
34
- }
34
+ ]),
35
+ })
@@ -5,12 +5,10 @@ import {
5
5
  addBookmark,
6
6
  bookmarksMachine,
7
7
  Draft,
8
- DUPLICATE,
9
8
  getBookmark,
10
9
  isEmpty,
11
10
  listBookmarks,
12
11
  ToggleRead,
13
- toggleRead,
14
12
  visible,
15
13
  } from './model.ts'
16
14
 
@@ -131,16 +129,10 @@ export const Detail = ui.view({
131
129
  },
132
130
  },
133
131
  ),
134
- ui.a({ href: ui.link(home, null, null), class: 'underline' }, ['Back']),
132
+ ui.a({ href: ui.link(home, null), class: 'underline' }, ['Back']),
135
133
  ]),
136
134
  })
137
135
 
138
- export const typesDraft = contract(bookmarksMachine, {
139
- given: { state: 'idle' },
140
- when: [{ send: Draft, payload: { text: 'Hozu' } }],
141
- expect: { state: 'idle', changes: { draft: 'Hozu' } },
142
- })
143
-
144
136
  export const addsBookmark = contract(bookmarksMachine, {
145
137
  given: { state: 'idle' },
146
138
  when: [
@@ -157,55 +149,3 @@ export const addsBookmark = contract(bookmarksMachine, {
157
149
  ],
158
150
  },
159
151
  })
160
-
161
- export const rejectsDuplicate = contract(bookmarksMachine, {
162
- given: { state: 'adding' },
163
- when: [{ failed: addBookmark, error: 'Duplicate', data: { title: 'Hozu talk' } }],
164
- expect: { state: 'idle', changes: { error: DUPLICATE } },
165
- })
166
-
167
- export const rejectsInvalidTitle = contract(bookmarksMachine, {
168
- given: { state: 'adding' },
169
- when: [
170
- {
171
- failed: addBookmark,
172
- error: 'Invalid',
173
- data: {
174
- message: 'title: Use at least 2 characters',
175
- fields: { title: 'Use at least 2 characters', kind: null },
176
- },
177
- },
178
- ],
179
- expect: { state: 'idle', changes: { fields: { title: 'Use at least 2 characters' } } },
180
- })
181
-
182
- export const addFails = contract(bookmarksMachine, {
183
- given: { state: 'adding' },
184
- when: [{ failed: addBookmark, error: 'Unexpected', data: { message: 'offline' } }],
185
- expect: { state: 'idle', changes: { error: 'offline' } },
186
- })
187
-
188
- export const togglesRead = contract(bookmarksMachine, {
189
- given: { state: 'idle' },
190
- when: [
191
- { send: ToggleRead, payload: { id: 'b1' } },
192
- { done: toggleRead, result: { id: 'b1', title: 'A', kind: 'article', read: true } },
193
- ],
194
- expect: {
195
- state: 'idle',
196
- changes: { target: 'b1' },
197
- effects: [{ effect: toggleRead, input: { id: 'b1' } }],
198
- },
199
- })
200
-
201
- export const toggleMissing = contract(bookmarksMachine, {
202
- given: { state: 'toggling' },
203
- when: [{ failed: toggleRead, error: 'NotFound', data: { id: 'b9' } }],
204
- expect: { state: 'idle' },
205
- })
206
-
207
- export const toggleFails = contract(bookmarksMachine, {
208
- given: { state: 'toggling' },
209
- when: [{ failed: toggleRead, error: 'Unexpected', data: { message: 'offline' } }],
210
- expect: { state: 'idle', changes: { error: 'offline' } },
211
- })
@@ -7,6 +7,7 @@ import { bookmarkPage, home } from './routes.ts'
7
7
 
8
8
  export default project({
9
9
  schema: zodAdapter,
10
+ app: new URL('./app.ts', import.meta.url),
10
11
  styles: new URL('./app.css', import.meta.url),
11
12
  site: { url: 'http://localhost:3000', name: 'Bookmarks', lang: 'en' },
12
13
  routes: { home, bookmarkPage },
@@ -20,6 +21,7 @@ export default project({
20
21
  head: {
21
22
  query: getBookmark,
22
23
  input: (params) => ({ id: params.id }),
24
+ failed: { NotFound: 404 },
23
25
  render: (b) => ({ title: b.title, description: b.title, type: 'article' }),
24
26
  },
25
27
  entries: { query: listBookmarks, input: {}, params: (b) => ({ id: b.id }) },
@@ -1,12 +1,16 @@
1
1
  # Sign-in, sessions, per-user data
2
2
 
3
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).
4
+ sign-out, `me`), per-user resolvers, and a redirect to `/login` when signed out. Replace the name-only sign-in with
5
+ real credentials before production; set `SESSION_SECRET` (production without it refuses to start).
7
6
  - `project({ session: z.object({ user: z.string() }) })` declares the identity. Queries with `scope: 'user'` and all
8
7
  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 }, … }`.
8
+ - Sessions live on the server: the default store is `memorySessions()` (from `@hozu/runtime-server`); the cookie holds
9
+ only an opaque, signed, HttpOnly id, so `setSession(null)` revokes it and the session never reaches browser
10
+ JavaScript. It is per process: a restart signs everyone out, and an edge or multi-instance deployment passes a
11
+ shared store explicitly (`createHandler({ session })`).
12
+ - `setSession` also applies in a failing mutation (expiry: `setSession(null)` then `fail('Expired', …)`).
13
+ - After a sign-in or sign-out the page's queries are re-read with the new session; nothing from the old one stays.
14
+ - Guard pages: `head: { query: me, …, failed: { Unauthorized: login } }`; a role check answers 403 with
15
+ `failed: { Unauthorized: login, Forbidden: 403 }` (`hozu docs pages`).
12
16
  - Calling another API with a token: keep the token in the session (server side) and read it in the resolver.