@bendyline/gilde 0.1.86 → 0.1.88

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 (108) hide show
  1. package/data/craftbook-templates/index.json +1 -1
  2. package/data/craftbook-templates/raw-index.json +1 -1
  3. package/data/craftbook-templates/ta/tailor-resume/versions/1.0.5/craftbook.json +226 -0
  4. package/data/craftbook-templates/ta/tailor-resume/versions/1.0.5/test.json +79 -0
  5. package/data/knowledge-catalogs/ca/caselaw-alaska-part-0001/manifest.json +24 -0
  6. package/data/knowledge-catalogs/ca/caselaw-alaska-part-0001/versions/2026.10.1/manifest.json +658 -0
  7. package/data/knowledge-catalogs/ca/caselaw-nevada-part-0001/manifest.json +24 -0
  8. package/data/knowledge-catalogs/ca/caselaw-nevada-part-0001/versions/2026.10.1/manifest.json +666 -0
  9. package/data/knowledge-catalogs/ca/caselaw-wyoming-part-0001/manifest.json +24 -0
  10. package/data/knowledge-catalogs/ca/caselaw-wyoming-part-0001/versions/2026.10.3/manifest.json +626 -0
  11. package/data/knowledge-catalogs/index.json +1 -1
  12. package/data/knowledge-catalogs/raw-index.json +1 -1
  13. package/data/project-types/ch/checkers/versions/1.2.1/about.md +9 -0
  14. package/data/project-types/ch/checkers/versions/1.2.1/game.json +77 -0
  15. package/data/project-types/ch/checkers/versions/1.2.1/manifest.json +168 -0
  16. package/data/project-types/ch/checkers/versions/1.2.1/mission.md +9 -0
  17. package/data/project-types/ch/checkers/versions/1.2.1/pages/board/index.html +1158 -0
  18. package/data/project-types/ch/chess/versions/1.0.4/about.md +9 -0
  19. package/data/project-types/ch/chess/versions/1.0.4/game.json +127 -0
  20. package/data/project-types/ch/chess/versions/1.0.4/manifest.json +182 -0
  21. package/data/project-types/ch/chess/versions/1.0.4/mission.md +8 -0
  22. package/data/project-types/ch/chess/versions/1.0.4/pages/board/index.html +851 -0
  23. package/data/project-types/de/design-scheme/versions/1.0.2/about.md +6 -0
  24. package/data/project-types/de/design-scheme/versions/1.0.2/manifest.json +70 -0
  25. package/data/project-types/de/design-scheme/versions/1.0.2/mission.md +3 -0
  26. package/data/project-types/de/design-scheme/versions/1.0.2/pages/gallery/index.html +212 -0
  27. package/data/project-types/de/design-scheme/versions/1.0.2/schemes/index.json +9 -0
  28. package/data/project-types/de/design-scheme/versions/1.0.2/schemes/warm-dusk.json +4 -0
  29. package/data/project-types/ev/event-planner/versions/1.0.2/about.md +7 -0
  30. package/data/project-types/ev/event-planner/versions/1.0.2/budget.json +5 -0
  31. package/data/project-types/ev/event-planner/versions/1.0.2/guests.json +10 -0
  32. package/data/project-types/ev/event-planner/versions/1.0.2/manifest.json +171 -0
  33. package/data/project-types/ev/event-planner/versions/1.0.2/mission.md +6 -0
  34. package/data/project-types/ev/event-planner/versions/1.0.2/pages/dashboard/index.html +283 -0
  35. package/data/project-types/fi/fitness-coach/versions/1.0.2/about.md +7 -0
  36. package/data/project-types/fi/fitness-coach/versions/1.0.2/manifest.json +126 -0
  37. package/data/project-types/fi/fitness-coach/versions/1.0.2/mission.md +7 -0
  38. package/data/project-types/fi/fitness-coach/versions/1.0.2/pages/dashboard/index.html +273 -0
  39. package/data/project-types/fi/fitness-coach/versions/1.0.2/training.json +6 -0
  40. package/data/project-types/fl/flashcards/versions/1.1.1/about.md +8 -0
  41. package/data/project-types/fl/flashcards/versions/1.1.1/deck.json +7 -0
  42. package/data/project-types/fl/flashcards/versions/1.1.1/manifest.json +168 -0
  43. package/data/project-types/fl/flashcards/versions/1.1.1/mission.md +6 -0
  44. package/data/project-types/fl/flashcards/versions/1.1.1/pages/review/index.html +477 -0
  45. package/data/project-types/fr/freelance-office/versions/1.0.2/about.md +6 -0
  46. package/data/project-types/fr/freelance-office/versions/1.0.2/invoices.json +9 -0
  47. package/data/project-types/fr/freelance-office/versions/1.0.2/manifest.json +143 -0
  48. package/data/project-types/fr/freelance-office/versions/1.0.2/mission.md +6 -0
  49. package/data/project-types/fr/freelance-office/versions/1.0.2/pages/dashboard/index.html +258 -0
  50. package/data/project-types/fu/fundraiser-hq/versions/1.0.2/about.md +7 -0
  51. package/data/project-types/fu/fundraiser-hq/versions/1.0.2/donations.json +7 -0
  52. package/data/project-types/fu/fundraiser-hq/versions/1.0.2/manifest.json +127 -0
  53. package/data/project-types/fu/fundraiser-hq/versions/1.0.2/mission.md +6 -0
  54. package/data/project-types/fu/fundraiser-hq/versions/1.0.2/pages/dashboard/index.html +261 -0
  55. package/data/project-types/go/go/versions/1.0.2/about.md +9 -0
  56. package/data/project-types/go/go/versions/1.0.2/game.json +103 -0
  57. package/data/project-types/go/go/versions/1.0.2/manifest.json +168 -0
  58. package/data/project-types/go/go/versions/1.0.2/mission.md +8 -0
  59. package/data/project-types/go/go/versions/1.0.2/pages/board/index.html +855 -0
  60. package/data/project-types/ho/household-budget/versions/1.0.3/about.md +6 -0
  61. package/data/project-types/ho/household-budget/versions/1.0.3/ledger.json +5 -0
  62. package/data/project-types/ho/household-budget/versions/1.0.3/manifest.json +164 -0
  63. package/data/project-types/ho/household-budget/versions/1.0.3/mission.md +5 -0
  64. package/data/project-types/ho/household-budget/versions/1.0.3/pages/dashboard/index.html +340 -0
  65. package/data/project-types/ho/household-manual/versions/1.0.2/about.md +7 -0
  66. package/data/project-types/ho/household-manual/versions/1.0.2/manifest.json +153 -0
  67. package/data/project-types/ho/household-manual/versions/1.0.2/mission.md +8 -0
  68. package/data/project-types/ho/household-manual/versions/1.0.2/pages/dashboard/index.html +258 -0
  69. package/data/project-types/ho/household-manual/versions/1.0.2/systems.json +8 -0
  70. package/data/project-types/index.json +1 -1
  71. package/data/project-types/jo/job-hunt/versions/1.0.2/about.md +10 -0
  72. package/data/project-types/jo/job-hunt/versions/1.0.2/activity.json +4 -0
  73. package/data/project-types/jo/job-hunt/versions/1.0.2/manifest.json +213 -0
  74. package/data/project-types/jo/job-hunt/versions/1.0.2/mission.md +9 -0
  75. package/data/project-types/jo/job-hunt/versions/1.0.2/pages/dashboard/index.html +430 -0
  76. package/data/project-types/jo/job-hunt/versions/1.0.2/pipeline.json +6 -0
  77. package/data/project-types/la/language-trainer/versions/1.0.2/about.md +6 -0
  78. package/data/project-types/la/language-trainer/versions/1.0.2/manifest.json +80 -0
  79. package/data/project-types/la/language-trainer/versions/1.0.2/mission.md +4 -0
  80. package/data/project-types/la/language-trainer/versions/1.0.2/pages/dashboard/index.html +268 -0
  81. package/data/project-types/la/language-trainer/versions/1.0.2/progress.json +7 -0
  82. package/data/project-types/li/life-binder/versions/1.0.3/about.md +7 -0
  83. package/data/project-types/li/life-binder/versions/1.0.3/documents.json +145 -0
  84. package/data/project-types/li/life-binder/versions/1.0.3/manifest.json +138 -0
  85. package/data/project-types/li/life-binder/versions/1.0.3/mission.md +6 -0
  86. package/data/project-types/li/life-binder/versions/1.0.3/pages/dashboard/index.html +248 -0
  87. package/data/project-types/me/meal-planner/versions/1.0.2/about.md +6 -0
  88. package/data/project-types/me/meal-planner/versions/1.0.2/manifest.json +160 -0
  89. package/data/project-types/me/meal-planner/versions/1.0.2/menu.json +5 -0
  90. package/data/project-types/me/meal-planner/versions/1.0.2/mission.md +5 -0
  91. package/data/project-types/me/meal-planner/versions/1.0.2/pages/dashboard/index.html +248 -0
  92. package/data/project-types/me/meal-planner/versions/1.0.2/pantry.json +9 -0
  93. package/data/project-types/no/novel-writing-room/versions/1.0.2/about.md +7 -0
  94. package/data/project-types/no/novel-writing-room/versions/1.0.2/chapters.json +10 -0
  95. package/data/project-types/no/novel-writing-room/versions/1.0.2/manifest.json +158 -0
  96. package/data/project-types/no/novel-writing-room/versions/1.0.2/mission.md +7 -0
  97. package/data/project-types/no/novel-writing-room/versions/1.0.2/pages/dashboard/index.html +264 -0
  98. package/data/project-types/no/novel-writing-room/versions/1.0.2/sessions.json +6 -0
  99. package/data/project-types/raw-index.json +1 -1
  100. package/data/project-types/tr/trip-planner/versions/1.0.2/about.md +7 -0
  101. package/data/project-types/tr/trip-planner/versions/1.0.2/journal.json +4 -0
  102. package/data/project-types/tr/trip-planner/versions/1.0.2/manifest.json +194 -0
  103. package/data/project-types/tr/trip-planner/versions/1.0.2/mission.md +4 -0
  104. package/data/project-types/tr/trip-planner/versions/1.0.2/packing.json +8 -0
  105. package/data/project-types/tr/trip-planner/versions/1.0.2/pages/dashboard/index.html +308 -0
  106. package/data/project-types/tr/trip-planner/versions/1.0.2/trip.json +7 -0
  107. package/package.json +1 -1
  108. package/schemas/project-type-version.schema.json +13 -0
@@ -0,0 +1,153 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "version": "1.0.2",
4
+ "releasedAt": "2026-10-06T00:00:00Z",
5
+ "meesterManaged": false,
6
+ "params": {
7
+ "type": "object",
8
+ "properties": {
9
+ "home": {
10
+ "type": "string",
11
+ "title": "What kind of home?",
12
+ "description": "e.g. 'a 1960s house', 'a third-floor apartment'.",
13
+ "default": "our home"
14
+ }
15
+ },
16
+ "required": [
17
+ "home"
18
+ ]
19
+ },
20
+ "nameTemplate": "Household Manual",
21
+ "aboutTemplate": "about.md",
22
+ "missionTemplate": "mission.md",
23
+ "gezels": [
24
+ {
25
+ "templateId": "huismeester",
26
+ "voorman": true
27
+ }
28
+ ],
29
+ "tools": [
30
+ {
31
+ "name": "record_system",
32
+ "description": "Add a system or appliance card (with a care interval in days when known).",
33
+ "script": "house-store",
34
+ "inputs": {
35
+ "type": "object",
36
+ "properties": {
37
+ "name": {
38
+ "type": "string"
39
+ },
40
+ "kind": {
41
+ "type": "string"
42
+ },
43
+ "location": {
44
+ "type": "string"
45
+ },
46
+ "care": {
47
+ "type": "string"
48
+ },
49
+ "intervalDays": {
50
+ "type": "number"
51
+ }
52
+ },
53
+ "required": [
54
+ "name"
55
+ ]
56
+ },
57
+ "bind": {
58
+ "action": "record_system"
59
+ }
60
+ },
61
+ {
62
+ "name": "log_maintenance",
63
+ "description": "Log that care happened (resets the due clock).",
64
+ "script": "house-store",
65
+ "inputs": {
66
+ "type": "object",
67
+ "properties": {
68
+ "id": {
69
+ "type": "string"
70
+ },
71
+ "name": {
72
+ "type": "string"
73
+ },
74
+ "note": {
75
+ "type": "string"
76
+ }
77
+ }
78
+ },
79
+ "bind": {
80
+ "action": "log_maintenance"
81
+ }
82
+ },
83
+ {
84
+ "name": "flag_attention",
85
+ "description": "Flag a problem on a system (a noise, a drip, a trip).",
86
+ "script": "house-store",
87
+ "inputs": {
88
+ "type": "object",
89
+ "properties": {
90
+ "id": {
91
+ "type": "string"
92
+ },
93
+ "name": {
94
+ "type": "string"
95
+ },
96
+ "note": {
97
+ "type": "string"
98
+ }
99
+ },
100
+ "required": [
101
+ "note"
102
+ ]
103
+ },
104
+ "bind": {
105
+ "action": "flag_attention"
106
+ }
107
+ },
108
+ {
109
+ "name": "house_status",
110
+ "description": "What the house needs: care due and flagged problems.",
111
+ "script": "house-store",
112
+ "inputs": {
113
+ "type": "object",
114
+ "properties": {}
115
+ },
116
+ "bind": {
117
+ "action": "status"
118
+ }
119
+ },
120
+ {
121
+ "name": "house_list",
122
+ "description": "Read every system card.",
123
+ "script": "house-store",
124
+ "inputs": {
125
+ "type": "object",
126
+ "properties": {}
127
+ },
128
+ "bind": {
129
+ "action": "house_list"
130
+ }
131
+ }
132
+ ],
133
+ "craftbooks": [
134
+ "seasonal-maintenance-sweep"
135
+ ],
136
+ "schedules": [],
137
+ "pages": {
138
+ "entry": "dashboard/index.html",
139
+ "api": 1,
140
+ "reads": [
141
+ {
142
+ "source": "workspace",
143
+ "path": "systems.json"
144
+ }
145
+ ]
146
+ },
147
+ "workspaceSeed": [
148
+ "systems.json"
149
+ ],
150
+ "scripts": {
151
+ "house-store": "import { type InferredInput, defineScript, gezel } from '@bendyline/gezel-sdk';\nimport { rosterStore } from '@bendyline/gezel-sdk/stores';\n\nexport const meta = defineScript({\n name: 'house-store',\n description:\n \"Read and update the home's system cards (systems.json). Call action:'status' for what's due or flagged; 'record_system' when an appliance/system comes up (with care interval in days when known); 'log_maintenance' when care happens; 'flag_attention' for problems; 'house_list' to read every card.\",\n inputs: {\n action: {\n type: 'string',\n description:\n \"One of 'status', 'record_system', 'log_maintenance', 'flag_attention', or 'house_list'.\",\n required: true,\n },\n name: { type: 'string', description: \"System name, e.g. 'Furnace', 'Smoke alarms'.\" },\n kind: { type: 'string', description: 'What it is (appliance, safety, structure, utility).' },\n location: { type: 'string', description: 'Where it lives in the house.' },\n care: { type: 'string', description: \"The care it needs, e.g. 'replace filter'.\" },\n intervalDays: { type: 'number', description: 'Care interval in days, when known.' },\n note: { type: 'string', description: 'Maintenance or attention note.' },\n id: { type: 'string', description: \"System id, e.g. 'sys-2'.\" },\n },\n outputs: {\n systems: { type: 'number', description: 'System cards in the manual.' },\n due: { type: 'number', description: 'Systems with care due.' },\n flagged: { type: 'number', description: 'Systems flagged for attention.' },\n summary: { type: 'string', description: 'What the house needs.' },\n },\n requires: ['workspace.read', 'workspace.write'],\n} as const);\n\nconst input = gezel.input as InferredInput<typeof meta>;\n\n// ── house-rules-start ──\n// Pure derivations over rosterStore-shaped system cards: statuses are\n// ok -> attention; \"due\" derives from lastDoneAt + intervalDays.\n\nvar DAY_MS = 24 * 60 * 60 * 1000;\n\nfunction isDue(record, nowMs) {\n var f = record.fields || {};\n if (typeof f.intervalDays !== 'number' || f.intervalDays <= 0) return false;\n var last = typeof f.lastDoneAt === 'string' ? Date.parse(f.lastDoneAt) : Date.parse(record.createdAt);\n return isFinite(last) && nowMs - last >= f.intervalDays * DAY_MS;\n}\n\nfunction houseShape(records, nowMs) {\n var due = [];\n var flagged = [];\n records.forEach(function (r) {\n if (isDue(r, nowMs)) due.push(r);\n if (r.status === 'attention') flagged.push(r);\n });\n return { due: due, flagged: flagged };\n}\n\nfunction houseLine(records, nowMs) {\n var shape = houseShape(records, nowMs);\n var names = function (rows) {\n return rows\n .map(function (r) {\n return (r.fields && r.fields.name) || r.id;\n })\n .join(', ');\n };\n if (records.length === 0) return 'No systems recorded yet — start with the ones that need regular care.';\n var bits = [];\n if (shape.due.length) bits.push('care due: ' + names(shape.due));\n if (shape.flagged.length) bits.push('flagged: ' + names(shape.flagged));\n return records.length + ' system(s) on record. ' + (bits.length ? bits.join('; ') + '.' : 'Nothing due — the house is quiet.');\n}\n// ── house-rules-end ──\n\nconst house = rosterStore(gezel.fs, 'systems.json', {\n stages: ['ok', 'attention'],\n idPrefix: 'sys',\n});\n\nasync function resolveSystem(): Promise<string> {\n if (input.id) return input.id;\n if (input.name) {\n const wanted = input.name.toLowerCase();\n const matches = (await house.list()).filter(\n (r) => String(r.fields.name ?? '').toLowerCase() === wanted,\n );\n if (matches.length === 1) return matches[0]!.id;\n }\n throw new Error(\"Pass 'id' (e.g. 'sys-2') or an exact system 'name'.\");\n}\n\nif (input.action === 'record_system') {\n if (!input.name) throw new Error(\"record_system needs 'name'.\");\n await house.add({\n status: 'ok',\n fields: {\n name: input.name,\n ...(input.kind ? { kind: input.kind } : {}),\n ...(input.location ? { location: input.location } : {}),\n ...(input.care ? { care: input.care } : {}),\n ...(typeof input.intervalDays === 'number' ? { intervalDays: input.intervalDays } : {}),\n lastDoneAt: new Date().toISOString(),\n },\n });\n} else if (input.action === 'log_maintenance') {\n const id = await resolveSystem();\n await house.update(id, {\n fields: { lastDoneAt: new Date().toISOString() },\n ...(input.note ? { note: input.note } : {}),\n });\n await house.transition(id, 'ok');\n} else if (input.action === 'flag_attention') {\n const id = await resolveSystem();\n await house.transition(id, 'attention', input.note ? { note: input.note } : {});\n} else if (input.action !== 'status' && input.action !== 'house_list') {\n throw new Error(\n `Unknown action '${input.action}'. Use status, record_system, log_maintenance, flag_attention, or house_list.`,\n );\n}\n\nconst records = await house.list();\nconst shape = houseShape(records, Date.now());\nconst listing =\n input.action === 'house_list'\n ? records\n .map(\n (r) =>\n `${r.id} [${r.status}${isDue(r, Date.now()) ? ', due' : ''}] ${String(r.fields.name ?? '?')}${r.fields.location ? ` — ${String(r.fields.location)}` : ''}${r.fields.care ? ` (${String(r.fields.care)}${r.fields.intervalDays ? ` every ${String(r.fields.intervalDays)}d` : ''})` : ''}`,\n )\n .join('\\n') || 'No systems recorded yet.'\n : '';\n\ngezel.output({\n systems: records.length,\n due: shape.due.length,\n flagged: shape.flagged.length,\n summary: listing || houseLine(records, Date.now()),\n});\n"
152
+ }
153
+ }
@@ -0,0 +1,8 @@
1
+ - Every system that needs regular care has a card with its interval; nothing
2
+ relies on memory.
3
+ - Care gets logged the day it happens; "what does the house need?" is answered
4
+ from the record, not a generic checklist.
5
+ - Problems get flagged on the system's card the day they're noticed, so repairs
6
+ start from history.
7
+ - Safety honesty: gas, mains electricity, and structure always get the "call a
8
+ professional" line when it belongs.
@@ -0,0 +1,258 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <meta name="gezel-demo" content="standalone" />
7
+ <title>Household manual</title>
8
+ <style>
9
+ :root {
10
+ color-scheme: light dark;
11
+ --bg: #faf7f2; --card: #fff; --ink: #2b2620; --muted: #7a7166;
12
+ --accent: #b5651d; --track: #ece5da; --good: #4a7c59; --warn: #a4552e;
13
+ }
14
+ :root[data-theme='dark'] {
15
+ --bg: #1c1a17; --card: #262320; --ink: #efe9e0; --muted: #a59a8c;
16
+ --accent: #e0894a; --track: #3a352f; --good: #7fae8d; --warn: #d98a63;
17
+ }
18
+ @media (prefers-color-scheme: dark) {
19
+ :root:not([data-theme='light']) {
20
+ --bg: #1c1a17; --card: #262320; --ink: #efe9e0; --muted: #a59a8c;
21
+ --accent: #e0894a; --track: #3a352f; --good: #7fae8d; --warn: #d98a63;
22
+ }
23
+ }
24
+ * { box-sizing: border-box; }
25
+ body { margin: 0; font: 15px/1.5 system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif; background: var(--bg); color: var(--ink); padding: 1.5rem 1.25rem; }
26
+ main { max-width: 640px; margin: 0 auto; }
27
+ h1 { font-family: Georgia, 'PT Serif', 'Times New Roman', serif; font-size: 1.35rem; font-weight: 600; margin: 0 0 0.15rem; }
28
+ .sub { color: var(--muted); font-size: 0.9rem; margin: 0 0 0.9rem; }
29
+ .tiles { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.6rem; margin-bottom: 0.9rem; }
30
+ .tile { background: var(--card); border: 1px solid var(--track); border-radius: 10px; padding: 0.7rem 0.85rem; }
31
+ .tile .n { font-size: 1.35rem; font-weight: 700; color: var(--accent); }
32
+ .tile .n.warn { color: var(--warn); }
33
+ .tile .l { font-size: 0.72rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; }
34
+ .list { background: var(--card); border: 1px solid var(--track); border-radius: 10px; padding: 0.5rem 0; }
35
+ .row { display: grid; grid-template-columns: 1fr auto; gap: 0.6rem; padding: 0.45rem 1rem; font-size: 0.9rem; align-items: baseline; }
36
+ .row + .row { border-top: 1px solid var(--track); }
37
+ .row .where { color: var(--muted); font-size: 0.78rem; }
38
+ .chip { display: inline-block; border: 1px solid var(--track); border-radius: 4px; padding: 0 0.3rem; font-size: 0.7rem; line-height: 1.4; margin-left: 0.3rem; }
39
+ .chip.due { color: var(--accent); border-color: var(--accent); }
40
+ .chip.attention { color: var(--warn); border-color: var(--warn); }
41
+ .empty { color: var(--muted); font-style: italic; padding: 0.85rem 1rem; }
42
+ .demo-banner {
43
+ display: flex;
44
+ align-items: center;
45
+ justify-content: space-between;
46
+ gap: 0.75rem;
47
+ margin: 0 0 0.85rem;
48
+ padding: 0.5rem 0.65rem;
49
+ border: 1px solid var(--accent);
50
+ border-radius: 6px;
51
+ background: color-mix(in srgb, var(--card) 88%, var(--accent));
52
+ color: var(--ink);
53
+ font-size: 0.8rem;
54
+ }
55
+ .demo-banner[hidden] { display: none; }
56
+ .demo-banner strong { font-weight: 700; }
57
+ .demo-reset {
58
+ flex: none;
59
+ padding: 0.25rem 0.5rem;
60
+ border: 1px solid var(--accent);
61
+ border-radius: 6px;
62
+ background: var(--card);
63
+ color: var(--ink);
64
+ cursor: pointer;
65
+ font: inherit;
66
+ }
67
+ .demo-reset:hover { border-color: var(--ink); }
68
+ </style>
69
+ </head>
70
+ <body>
71
+ <main>
72
+ <div class="demo-banner" data-gezel-demo-banner hidden>
73
+ <span><strong>Demo mode</strong> — sample data stays in this page.</span>
74
+ <button type="button" class="demo-reset" onclick="location.reload()">Reset demo</button>
75
+ </div>
76
+ <h1>Household manual</h1>
77
+ <p class="sub" id="sub">Loading the system cards…</p>
78
+ <div class="tiles">
79
+ <div class="tile"><div class="n" id="t-sys">—</div><div class="l">Systems</div></div>
80
+ <div class="tile"><div class="n" id="t-due">—</div><div class="l">Care due</div></div>
81
+ <div class="tile"><div class="n warn" id="t-flag">—</div><div class="l">Flagged</div></div>
82
+ </div>
83
+ <div class="list" id="list"><p class="empty">No systems yet — tell your Huismeester about the furnace.</p></div>
84
+ </main>
85
+ <script>
86
+ /* gezel-page-demo-stub v1 */
87
+ /*
88
+ * The canonical demo stub for Gezel type pages. MIT License.
89
+ *
90
+ * Paste this file's bytes verbatim into a <script> block ahead of the
91
+ * page's own code, then call makeDemoGezel({ page, tools, data }) with the
92
+ * page's demo handlers. Served by Gezel, the real window.gezel is already
93
+ * injected and the stub steps aside; opened as a raw file (the live demos
94
+ * on gezelgilde.com), it installs a window.gezel whose tools and data are
95
+ * the page-supplied handler maps — one code path in every mode. `tools`
96
+ * and `data` map a tool name / declared read path to a function; data
97
+ * handlers back read() and list(), and url() returns the handler's value
98
+ * or passes a data: URI through. See docs/page-authoring.md.
99
+ */
100
+ (function () {
101
+ 'use strict';
102
+ window.makeDemoGezel = function (opts) {
103
+ if (window.gezel) return window.gezel;
104
+ var page = (opts && opts.page) || {};
105
+ var tools = (opts && opts.tools) || {};
106
+ var data = (opts && opts.data) || {};
107
+ var themeListeners = [];
108
+ var media = window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)') : null;
109
+ var ui = {
110
+ theme: { mode: media && media.matches ? 'dark' : 'light' },
111
+ onTheme: function (cb) {
112
+ themeListeners.push(cb);
113
+ return function () {
114
+ var at = themeListeners.indexOf(cb);
115
+ if (at >= 0) themeListeners.splice(at, 1);
116
+ };
117
+ },
118
+ };
119
+ if (media && media.addEventListener) {
120
+ media.addEventListener('change', function (ev) {
121
+ ui.theme = { mode: ev.matches ? 'dark' : 'light' };
122
+ themeListeners.slice().forEach(function (cb) { cb(ui.theme); });
123
+ });
124
+ }
125
+ function fail(code, message) {
126
+ var err = new Error(message);
127
+ err.code = code;
128
+ return Promise.reject(err);
129
+ }
130
+ function run(map, key, arg) {
131
+ if (!Object.prototype.hasOwnProperty.call(map, key)) return null;
132
+ return Promise.resolve().then(function () { return map[key](arg); });
133
+ }
134
+ window.gezel = {
135
+ page: {
136
+ api: 1,
137
+ projectId: 'demo',
138
+ source: 'type',
139
+ entry: page.entry || 'index.html',
140
+ typeName: page.typeName || 'demo',
141
+ params: page.params || {},
142
+ mode: 'demo',
143
+ },
144
+ tools: {
145
+ list: function () { return Promise.resolve(Object.keys(tools)); },
146
+ invoke: function (name, input) {
147
+ var call = run(tools, name, input || {});
148
+ if (!call) return fail('not-allowed', "The demo does not handle the tool '" + name + "'.");
149
+ return call.then(function (output) { return { output: output }; });
150
+ },
151
+ },
152
+ data: {
153
+ read: function (path, o) {
154
+ return run(data, path, o || {}) || fail('not-allowed', "The demo has no data for '" + path + "'.");
155
+ },
156
+ list: function (path, o) {
157
+ return run(data, path, o || {}) || Promise.resolve([]);
158
+ },
159
+ watch: function () { return function () {}; },
160
+ url: function (path) {
161
+ if (Object.prototype.hasOwnProperty.call(data, path)) return String(data[path]({}));
162
+ return typeof path === 'string' && path.indexOf('data:') === 0 ? path : '';
163
+ },
164
+ },
165
+ ui: ui,
166
+ refresh: function () {},
167
+ };
168
+ return window.gezel;
169
+ };
170
+ })();
171
+ </script>
172
+ <script>
173
+ (function () {
174
+ 'use strict';
175
+ var DAY = 24 * 60 * 60 * 1000;
176
+
177
+ // ── demo manual (used only when the page opens as a raw file) ──
178
+ var demoHouse = { records: [
179
+ { id: 'sys-1', status: 'attention', createdAt: new Date(Date.now() - 120 * DAY).toISOString(), fields: { name: 'Boiler pressure', location: 'Utility cupboard', care: 'Check gauge and top up', intervalDays: 30, lastDoneAt: new Date(Date.now() - 42 * DAY).toISOString() } },
180
+ { id: 'sys-2', status: 'ok', createdAt: new Date(Date.now() - 80 * DAY).toISOString(), fields: { name: 'Smoke alarms', location: 'Each floor', care: 'Test and note batteries', intervalDays: 30, lastDoneAt: new Date(Date.now() - 9 * DAY).toISOString() } },
181
+ { id: 'sys-3', status: 'ok', createdAt: new Date(Date.now() - 200 * DAY).toISOString(), fields: { name: 'Water shutoff', location: 'Under kitchen sink', care: 'Keep access clear' } },
182
+ { id: 'sys-4', status: 'ok', createdAt: new Date(Date.now() - 90 * DAY).toISOString(), fields: { name: 'Range hood filter', location: 'Kitchen', care: 'Wash filter', intervalDays: 60, lastDoneAt: new Date(Date.now() - 67 * DAY).toISOString() } },
183
+ ] };
184
+
185
+ makeDemoGezel({
186
+ page: { entry: 'dashboard/index.html', typeName: 'Household Manual', params: { home: 'our home' } },
187
+ data: {
188
+ 'systems.json': function () { return demoHouse; },
189
+ },
190
+ });
191
+
192
+ // ── one code path from here: window.gezel exists in every mode ──
193
+ var demoMode = gezel.page.mode === 'demo';
194
+
195
+ function applyTheme(mode) {
196
+ document.documentElement.setAttribute('data-theme', mode === 'dark' ? 'dark' : 'light');
197
+ }
198
+ applyTheme(gezel.ui.theme.mode);
199
+ gezel.ui.onTheme(function (theme) { applyTheme(theme.mode); });
200
+
201
+ if (demoMode) document.querySelector('[data-gezel-demo-banner]').hidden = false;
202
+
203
+ function esc(s) { return String(s).replace(/[&<>"]/g, function (c) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]; }); }
204
+ function isDue(r, now) {
205
+ var f = r.fields || {};
206
+ if (typeof f.intervalDays !== 'number' || f.intervalDays <= 0) return false;
207
+ var last = typeof f.lastDoneAt === 'string' ? Date.parse(f.lastDoneAt) : Date.parse(r.createdAt);
208
+ return isFinite(last) && now - last >= f.intervalDays * DAY;
209
+ }
210
+
211
+ function render(house) {
212
+ var records = Array.isArray(house.records) ? house.records : [];
213
+ var now = Date.now();
214
+ var due = records.filter(function (r) { return isDue(r, now); });
215
+ var flagged = records.filter(function (r) { return r.status === 'attention'; });
216
+ document.getElementById('sub').textContent =
217
+ records.length === 0 ? 'An empty manual — the house has secrets to record.'
218
+ : due.length + flagged.length === 0 ? 'The house is quiet. Nothing due.'
219
+ : 'The house needs a little care.';
220
+ document.getElementById('t-sys').textContent = records.length;
221
+ document.getElementById('t-due').textContent = due.length;
222
+ document.getElementById('t-flag').textContent = flagged.length;
223
+
224
+ if (records.length) {
225
+ document.getElementById('list').innerHTML = records
226
+ .slice()
227
+ .sort(function (a, b) {
228
+ var pa = (a.status === 'attention' ? 0 : isDue(a, now) ? 1 : 2);
229
+ var pb = (b.status === 'attention' ? 0 : isDue(b, now) ? 1 : 2);
230
+ return pa - pb;
231
+ })
232
+ .map(function (r) {
233
+ var f = r.fields || {};
234
+ return '<div class="row"><span>' + esc(f.name || r.id) +
235
+ (f.location ? '<span class="where"> — ' + esc(f.location) + '</span>' : '') +
236
+ (f.care ? '<span class="where"> · ' + esc(f.care) + (f.intervalDays ? ' every ' + f.intervalDays + 'd' : '') + '</span>' : '') +
237
+ '</span><span>' +
238
+ (r.status === 'attention' ? '<span class="chip attention">attention</span>' : '') +
239
+ (isDue(r, now) ? '<span class="chip due">due</span>' : '') +
240
+ '</span></div>';
241
+ })
242
+ .join('');
243
+ }
244
+ }
245
+
246
+ function loadManual() {
247
+ return gezel.data
248
+ .read('systems.json')
249
+ .then(function (next) { if (next) render(next); })
250
+ .catch(function () {});
251
+ }
252
+
253
+ loadManual();
254
+ gezel.data.watch('systems.json', function () { loadManual(); });
255
+ })();
256
+ </script>
257
+ </body>
258
+ </html>
@@ -0,0 +1,8 @@
1
+ {
2
+ "version": 1,
3
+ "stages": [
4
+ "ok",
5
+ "attention"
6
+ ],
7
+ "records": []
8
+ }