@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.
- package/data/craftbook-templates/index.json +1 -1
- package/data/craftbook-templates/raw-index.json +1 -1
- package/data/craftbook-templates/ta/tailor-resume/versions/1.0.5/craftbook.json +226 -0
- package/data/craftbook-templates/ta/tailor-resume/versions/1.0.5/test.json +79 -0
- package/data/knowledge-catalogs/ca/caselaw-alaska-part-0001/manifest.json +24 -0
- package/data/knowledge-catalogs/ca/caselaw-alaska-part-0001/versions/2026.10.1/manifest.json +658 -0
- package/data/knowledge-catalogs/ca/caselaw-nevada-part-0001/manifest.json +24 -0
- package/data/knowledge-catalogs/ca/caselaw-nevada-part-0001/versions/2026.10.1/manifest.json +666 -0
- package/data/knowledge-catalogs/ca/caselaw-wyoming-part-0001/manifest.json +24 -0
- package/data/knowledge-catalogs/ca/caselaw-wyoming-part-0001/versions/2026.10.3/manifest.json +626 -0
- package/data/knowledge-catalogs/index.json +1 -1
- package/data/knowledge-catalogs/raw-index.json +1 -1
- package/data/project-types/ch/checkers/versions/1.2.1/about.md +9 -0
- package/data/project-types/ch/checkers/versions/1.2.1/game.json +77 -0
- package/data/project-types/ch/checkers/versions/1.2.1/manifest.json +168 -0
- package/data/project-types/ch/checkers/versions/1.2.1/mission.md +9 -0
- package/data/project-types/ch/checkers/versions/1.2.1/pages/board/index.html +1158 -0
- package/data/project-types/ch/chess/versions/1.0.4/about.md +9 -0
- package/data/project-types/ch/chess/versions/1.0.4/game.json +127 -0
- package/data/project-types/ch/chess/versions/1.0.4/manifest.json +182 -0
- package/data/project-types/ch/chess/versions/1.0.4/mission.md +8 -0
- package/data/project-types/ch/chess/versions/1.0.4/pages/board/index.html +851 -0
- package/data/project-types/de/design-scheme/versions/1.0.2/about.md +6 -0
- package/data/project-types/de/design-scheme/versions/1.0.2/manifest.json +70 -0
- package/data/project-types/de/design-scheme/versions/1.0.2/mission.md +3 -0
- package/data/project-types/de/design-scheme/versions/1.0.2/pages/gallery/index.html +212 -0
- package/data/project-types/de/design-scheme/versions/1.0.2/schemes/index.json +9 -0
- package/data/project-types/de/design-scheme/versions/1.0.2/schemes/warm-dusk.json +4 -0
- package/data/project-types/ev/event-planner/versions/1.0.2/about.md +7 -0
- package/data/project-types/ev/event-planner/versions/1.0.2/budget.json +5 -0
- package/data/project-types/ev/event-planner/versions/1.0.2/guests.json +10 -0
- package/data/project-types/ev/event-planner/versions/1.0.2/manifest.json +171 -0
- package/data/project-types/ev/event-planner/versions/1.0.2/mission.md +6 -0
- package/data/project-types/ev/event-planner/versions/1.0.2/pages/dashboard/index.html +283 -0
- package/data/project-types/fi/fitness-coach/versions/1.0.2/about.md +7 -0
- package/data/project-types/fi/fitness-coach/versions/1.0.2/manifest.json +126 -0
- package/data/project-types/fi/fitness-coach/versions/1.0.2/mission.md +7 -0
- package/data/project-types/fi/fitness-coach/versions/1.0.2/pages/dashboard/index.html +273 -0
- package/data/project-types/fi/fitness-coach/versions/1.0.2/training.json +6 -0
- package/data/project-types/fl/flashcards/versions/1.1.1/about.md +8 -0
- package/data/project-types/fl/flashcards/versions/1.1.1/deck.json +7 -0
- package/data/project-types/fl/flashcards/versions/1.1.1/manifest.json +168 -0
- package/data/project-types/fl/flashcards/versions/1.1.1/mission.md +6 -0
- package/data/project-types/fl/flashcards/versions/1.1.1/pages/review/index.html +477 -0
- package/data/project-types/fr/freelance-office/versions/1.0.2/about.md +6 -0
- package/data/project-types/fr/freelance-office/versions/1.0.2/invoices.json +9 -0
- package/data/project-types/fr/freelance-office/versions/1.0.2/manifest.json +143 -0
- package/data/project-types/fr/freelance-office/versions/1.0.2/mission.md +6 -0
- package/data/project-types/fr/freelance-office/versions/1.0.2/pages/dashboard/index.html +258 -0
- package/data/project-types/fu/fundraiser-hq/versions/1.0.2/about.md +7 -0
- package/data/project-types/fu/fundraiser-hq/versions/1.0.2/donations.json +7 -0
- package/data/project-types/fu/fundraiser-hq/versions/1.0.2/manifest.json +127 -0
- package/data/project-types/fu/fundraiser-hq/versions/1.0.2/mission.md +6 -0
- package/data/project-types/fu/fundraiser-hq/versions/1.0.2/pages/dashboard/index.html +261 -0
- package/data/project-types/go/go/versions/1.0.2/about.md +9 -0
- package/data/project-types/go/go/versions/1.0.2/game.json +103 -0
- package/data/project-types/go/go/versions/1.0.2/manifest.json +168 -0
- package/data/project-types/go/go/versions/1.0.2/mission.md +8 -0
- package/data/project-types/go/go/versions/1.0.2/pages/board/index.html +855 -0
- package/data/project-types/ho/household-budget/versions/1.0.3/about.md +6 -0
- package/data/project-types/ho/household-budget/versions/1.0.3/ledger.json +5 -0
- package/data/project-types/ho/household-budget/versions/1.0.3/manifest.json +164 -0
- package/data/project-types/ho/household-budget/versions/1.0.3/mission.md +5 -0
- package/data/project-types/ho/household-budget/versions/1.0.3/pages/dashboard/index.html +340 -0
- package/data/project-types/ho/household-manual/versions/1.0.2/about.md +7 -0
- package/data/project-types/ho/household-manual/versions/1.0.2/manifest.json +153 -0
- package/data/project-types/ho/household-manual/versions/1.0.2/mission.md +8 -0
- package/data/project-types/ho/household-manual/versions/1.0.2/pages/dashboard/index.html +258 -0
- package/data/project-types/ho/household-manual/versions/1.0.2/systems.json +8 -0
- package/data/project-types/index.json +1 -1
- package/data/project-types/jo/job-hunt/versions/1.0.2/about.md +10 -0
- package/data/project-types/jo/job-hunt/versions/1.0.2/activity.json +4 -0
- package/data/project-types/jo/job-hunt/versions/1.0.2/manifest.json +213 -0
- package/data/project-types/jo/job-hunt/versions/1.0.2/mission.md +9 -0
- package/data/project-types/jo/job-hunt/versions/1.0.2/pages/dashboard/index.html +430 -0
- package/data/project-types/jo/job-hunt/versions/1.0.2/pipeline.json +6 -0
- package/data/project-types/la/language-trainer/versions/1.0.2/about.md +6 -0
- package/data/project-types/la/language-trainer/versions/1.0.2/manifest.json +80 -0
- package/data/project-types/la/language-trainer/versions/1.0.2/mission.md +4 -0
- package/data/project-types/la/language-trainer/versions/1.0.2/pages/dashboard/index.html +268 -0
- package/data/project-types/la/language-trainer/versions/1.0.2/progress.json +7 -0
- package/data/project-types/li/life-binder/versions/1.0.3/about.md +7 -0
- package/data/project-types/li/life-binder/versions/1.0.3/documents.json +145 -0
- package/data/project-types/li/life-binder/versions/1.0.3/manifest.json +138 -0
- package/data/project-types/li/life-binder/versions/1.0.3/mission.md +6 -0
- package/data/project-types/li/life-binder/versions/1.0.3/pages/dashboard/index.html +248 -0
- package/data/project-types/me/meal-planner/versions/1.0.2/about.md +6 -0
- package/data/project-types/me/meal-planner/versions/1.0.2/manifest.json +160 -0
- package/data/project-types/me/meal-planner/versions/1.0.2/menu.json +5 -0
- package/data/project-types/me/meal-planner/versions/1.0.2/mission.md +5 -0
- package/data/project-types/me/meal-planner/versions/1.0.2/pages/dashboard/index.html +248 -0
- package/data/project-types/me/meal-planner/versions/1.0.2/pantry.json +9 -0
- package/data/project-types/no/novel-writing-room/versions/1.0.2/about.md +7 -0
- package/data/project-types/no/novel-writing-room/versions/1.0.2/chapters.json +10 -0
- package/data/project-types/no/novel-writing-room/versions/1.0.2/manifest.json +158 -0
- package/data/project-types/no/novel-writing-room/versions/1.0.2/mission.md +7 -0
- package/data/project-types/no/novel-writing-room/versions/1.0.2/pages/dashboard/index.html +264 -0
- package/data/project-types/no/novel-writing-room/versions/1.0.2/sessions.json +6 -0
- package/data/project-types/raw-index.json +1 -1
- package/data/project-types/tr/trip-planner/versions/1.0.2/about.md +7 -0
- package/data/project-types/tr/trip-planner/versions/1.0.2/journal.json +4 -0
- package/data/project-types/tr/trip-planner/versions/1.0.2/manifest.json +194 -0
- package/data/project-types/tr/trip-planner/versions/1.0.2/mission.md +4 -0
- package/data/project-types/tr/trip-planner/versions/1.0.2/packing.json +8 -0
- package/data/project-types/tr/trip-planner/versions/1.0.2/pages/dashboard/index.html +308 -0
- package/data/project-types/tr/trip-planner/versions/1.0.2/trip.json +7 -0
- package/package.json +1 -1
- package/schemas/project-type-version.schema.json +13 -0
|
@@ -0,0 +1,248 @@
|
|
|
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>Life binder</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: 620px; 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
|
+
.meter { background: var(--card); border: 1px solid var(--track); border-radius: 10px; padding: 0.85rem 1rem; margin-bottom: 0.75rem; }
|
|
30
|
+
.meter .line { display: flex; justify-content: space-between; font-size: 0.85rem; margin-bottom: 0.4rem; }
|
|
31
|
+
.bar { background: var(--track); border-radius: 4px; height: 10px; overflow: hidden; }
|
|
32
|
+
.bar > span { display: block; height: 100%; background: var(--good); border-radius: 4px; transition: width 0.3s ease; }
|
|
33
|
+
.list { background: var(--card); border: 1px solid var(--track); border-radius: 10px; padding: 0.5rem 0; }
|
|
34
|
+
.row { display: grid; grid-template-columns: 1.4rem 1fr auto; gap: 0.6rem; align-items: baseline; padding: 0.45rem 1rem; font-size: 0.9rem; }
|
|
35
|
+
.row + .row { border-top: 1px solid var(--track); }
|
|
36
|
+
.mark { font-weight: 700; text-align: center; }
|
|
37
|
+
.row.present .mark { color: var(--good); }
|
|
38
|
+
.row.missing .mark { color: var(--warn); }
|
|
39
|
+
.row .where { color: var(--muted); font-size: 0.78rem; }
|
|
40
|
+
.chip { display: inline-block; border: 1px solid var(--warn); color: var(--warn); border-radius: 4px; padding: 0 0.3rem; font-size: 0.7rem; line-height: 1.4; }
|
|
41
|
+
.privacy { color: var(--muted); font-size: 0.78rem; text-align: center; margin-top: 0.75rem; }
|
|
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>Life binder</h1>
|
|
77
|
+
<p class="sub" id="sub">Loading the checklist…</p>
|
|
78
|
+
<div class="meter">
|
|
79
|
+
<div class="line"><span id="m-label">—</span><span id="m-count">—</span></div>
|
|
80
|
+
<div class="bar"><span id="m-bar" style="width: 0%"></span></div>
|
|
81
|
+
</div>
|
|
82
|
+
<div class="list" id="list"></div>
|
|
83
|
+
<p class="privacy">Locations, never contents.</p>
|
|
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 YEAR = 365 * 24 * 60 * 60 * 1000;
|
|
176
|
+
|
|
177
|
+
// ── demo checklist (used only when the page opens as a raw file) ──
|
|
178
|
+
var demoBinder = { records: [
|
|
179
|
+
{ id: 'doc-1', status: 'present', updatedAt: new Date(Date.now() - 40 * 86400000).toISOString(), fields: { name: 'Will', location: 'Blue fire box', reviewedAt: new Date(Date.now() - 40 * 86400000).toISOString() } },
|
|
180
|
+
{ id: 'doc-2', status: 'present', updatedAt: new Date(Date.now() - 420 * 86400000).toISOString(), fields: { name: 'Home insurance', location: 'Blue fire box', reviewedAt: new Date(Date.now() - 420 * 86400000).toISOString() } },
|
|
181
|
+
{ id: 'doc-3', status: 'missing', updatedAt: new Date().toISOString(), fields: { name: 'Emergency contacts' } },
|
|
182
|
+
{ id: 'doc-4', status: 'present', updatedAt: new Date(Date.now() - 20 * 86400000).toISOString(), fields: { name: 'Passport copies', location: 'Travel folder' } },
|
|
183
|
+
{ id: 'doc-5', status: 'missing', updatedAt: new Date().toISOString(), fields: { name: 'Pet care instructions' } },
|
|
184
|
+
] };
|
|
185
|
+
|
|
186
|
+
makeDemoGezel({
|
|
187
|
+
page: { entry: 'dashboard/index.html', typeName: 'Life Binder', params: { household: 'our family' } },
|
|
188
|
+
data: {
|
|
189
|
+
'documents.json': function () { return demoBinder; },
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// ── one code path from here: window.gezel exists in every mode ──
|
|
194
|
+
var demoMode = gezel.page.mode === 'demo';
|
|
195
|
+
|
|
196
|
+
function applyTheme(mode) {
|
|
197
|
+
document.documentElement.setAttribute('data-theme', mode === 'dark' ? 'dark' : 'light');
|
|
198
|
+
}
|
|
199
|
+
applyTheme(gezel.ui.theme.mode);
|
|
200
|
+
gezel.ui.onTheme(function (theme) { applyTheme(theme.mode); });
|
|
201
|
+
|
|
202
|
+
if (demoMode) document.querySelector('[data-gezel-demo-banner]').hidden = false;
|
|
203
|
+
|
|
204
|
+
function esc(s) { return String(s).replace(/[&<>"]/g, function (c) { return { '&': '&', '<': '<', '>': '>', '"': '"' }[c]; }); }
|
|
205
|
+
|
|
206
|
+
function render(binder) {
|
|
207
|
+
var records = Array.isArray(binder.records) ? binder.records : [];
|
|
208
|
+
var present = records.filter(function (r) { return r.status === 'present'; });
|
|
209
|
+
var now = Date.now();
|
|
210
|
+
document.getElementById('sub').textContent =
|
|
211
|
+
present.length === records.length && records.length > 0
|
|
212
|
+
? 'Everything in place. Well done.'
|
|
213
|
+
: (records.length - present.length) + ' gap(s) to close.';
|
|
214
|
+
document.getElementById('m-label').textContent = 'In place';
|
|
215
|
+
document.getElementById('m-count').textContent = present.length + ' of ' + records.length;
|
|
216
|
+
document.getElementById('m-bar').style.width =
|
|
217
|
+
(records.length ? Math.round((present.length / records.length) * 100) : 0) + '%';
|
|
218
|
+
|
|
219
|
+
document.getElementById('list').innerHTML = records
|
|
220
|
+
.slice()
|
|
221
|
+
.sort(function (a, b) { return a.status === b.status ? 0 : a.status === 'missing' ? -1 : 1; })
|
|
222
|
+
.map(function (r) {
|
|
223
|
+
var f = r.fields || {};
|
|
224
|
+
var reviewed = typeof f.reviewedAt === 'string' ? Date.parse(f.reviewedAt) : Date.parse(r.updatedAt);
|
|
225
|
+
var stale = r.status === 'present' && isFinite(reviewed) && now - reviewed > YEAR;
|
|
226
|
+
return (
|
|
227
|
+
'<div class="row ' + r.status + '"><span class="mark">' + (r.status === 'present' ? '+' : '-') + '</span>' +
|
|
228
|
+
'<span>' + esc(f.name || r.id) +
|
|
229
|
+
(f.location ? '<span class="where"> — ' + esc(f.location) + '</span>' : '') + '</span>' +
|
|
230
|
+
'<span>' + (stale ? '<span class="chip">review due</span>' : '') + '</span></div>'
|
|
231
|
+
);
|
|
232
|
+
})
|
|
233
|
+
.join('');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function loadBinder() {
|
|
237
|
+
return gezel.data
|
|
238
|
+
.read('documents.json')
|
|
239
|
+
.then(function (next) { if (next) render(next); })
|
|
240
|
+
.catch(function () {});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
loadBinder();
|
|
244
|
+
gezel.data.watch('documents.json', function () { loadBinder(); });
|
|
245
|
+
})();
|
|
246
|
+
</script>
|
|
247
|
+
</body>
|
|
248
|
+
</html>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
The weekly answer to "what's for dinner?" — planned once, for {{household}}.
|
|
2
|
+
Your Maaltijdplanner builds the week's menu from what's in the pantry, learns
|
|
3
|
+
what the table actually likes, and keeps the grocery run short.
|
|
4
|
+
|
|
5
|
+
The **Output** tab shows the week at a glance: every dinner, tonight
|
|
6
|
+
highlighted, and the grocery gap.
|
|
@@ -0,0 +1,160 @@
|
|
|
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
|
+
"household": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"title": "Who eats here?",
|
|
12
|
+
"description": "e.g. 'two adults, two picky kids', 'just me, vegetarian'.",
|
|
13
|
+
"default": "the household"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"required": [
|
|
17
|
+
"household"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
"nameTemplate": "Meal Planner",
|
|
21
|
+
"aboutTemplate": "about.md",
|
|
22
|
+
"missionTemplate": "mission.md",
|
|
23
|
+
"gezels": [
|
|
24
|
+
{
|
|
25
|
+
"templateId": "maaltijdplanner",
|
|
26
|
+
"voorman": true
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"tools": [
|
|
30
|
+
{
|
|
31
|
+
"name": "set_menu",
|
|
32
|
+
"description": "Set a day's dinner (monday..sunday).",
|
|
33
|
+
"script": "kitchen-store",
|
|
34
|
+
"inputs": {
|
|
35
|
+
"type": "object",
|
|
36
|
+
"properties": {
|
|
37
|
+
"day": {
|
|
38
|
+
"type": "string"
|
|
39
|
+
},
|
|
40
|
+
"meal": {
|
|
41
|
+
"type": "string"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"required": [
|
|
45
|
+
"day",
|
|
46
|
+
"meal"
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
"bind": {
|
|
50
|
+
"action": "set_menu"
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"name": "pantry_add",
|
|
55
|
+
"description": "Add a pantry staple (level stocked, low, or out).",
|
|
56
|
+
"script": "kitchen-store",
|
|
57
|
+
"inputs": {
|
|
58
|
+
"type": "object",
|
|
59
|
+
"properties": {
|
|
60
|
+
"name": {
|
|
61
|
+
"type": "string"
|
|
62
|
+
},
|
|
63
|
+
"level": {
|
|
64
|
+
"type": "string",
|
|
65
|
+
"enum": [
|
|
66
|
+
"stocked",
|
|
67
|
+
"low",
|
|
68
|
+
"out"
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"required": [
|
|
73
|
+
"name"
|
|
74
|
+
]
|
|
75
|
+
},
|
|
76
|
+
"bind": {
|
|
77
|
+
"action": "pantry_add"
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"name": "pantry_level",
|
|
82
|
+
"description": "Update a staple's level as the kitchen changes.",
|
|
83
|
+
"script": "kitchen-store",
|
|
84
|
+
"inputs": {
|
|
85
|
+
"type": "object",
|
|
86
|
+
"properties": {
|
|
87
|
+
"id": {
|
|
88
|
+
"type": "string"
|
|
89
|
+
},
|
|
90
|
+
"name": {
|
|
91
|
+
"type": "string"
|
|
92
|
+
},
|
|
93
|
+
"level": {
|
|
94
|
+
"type": "string",
|
|
95
|
+
"enum": [
|
|
96
|
+
"stocked",
|
|
97
|
+
"low",
|
|
98
|
+
"out"
|
|
99
|
+
]
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
"required": [
|
|
103
|
+
"level"
|
|
104
|
+
]
|
|
105
|
+
},
|
|
106
|
+
"bind": {
|
|
107
|
+
"action": "pantry_level"
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"name": "kitchen_status",
|
|
112
|
+
"description": "Tonight's dinner, the week's coverage, and the grocery gap.",
|
|
113
|
+
"script": "kitchen-store",
|
|
114
|
+
"inputs": {
|
|
115
|
+
"type": "object",
|
|
116
|
+
"properties": {}
|
|
117
|
+
},
|
|
118
|
+
"bind": {
|
|
119
|
+
"action": "status"
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"name": "grocery_list",
|
|
124
|
+
"description": "The shopping run: everything low or out.",
|
|
125
|
+
"script": "kitchen-store",
|
|
126
|
+
"inputs": {
|
|
127
|
+
"type": "object",
|
|
128
|
+
"properties": {}
|
|
129
|
+
},
|
|
130
|
+
"bind": {
|
|
131
|
+
"action": "grocery_list"
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
],
|
|
135
|
+
"craftbooks": [
|
|
136
|
+
"weekly-menu-plan"
|
|
137
|
+
],
|
|
138
|
+
"schedules": [],
|
|
139
|
+
"pages": {
|
|
140
|
+
"entry": "dashboard/index.html",
|
|
141
|
+
"api": 1,
|
|
142
|
+
"reads": [
|
|
143
|
+
{
|
|
144
|
+
"source": "workspace",
|
|
145
|
+
"path": "menu.json"
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"source": "workspace",
|
|
149
|
+
"path": "pantry.json"
|
|
150
|
+
}
|
|
151
|
+
]
|
|
152
|
+
},
|
|
153
|
+
"workspaceSeed": [
|
|
154
|
+
"menu.json",
|
|
155
|
+
"pantry.json"
|
|
156
|
+
],
|
|
157
|
+
"scripts": {
|
|
158
|
+
"kitchen-store": "import { type InferredInput, defineScript, gezel } from '@bendyline/gezel-sdk';\nimport { rosterStore } from '@bendyline/gezel-sdk/stores';\n\nexport const meta = defineScript({\n name: 'kitchen-store',\n description:\n \"Read and update the week's menu (menu.json) and the pantry (pantry.json). Call action:'status' for today's dinner + the grocery gap; 'set_menu' day by day; 'pantry_add' / 'pantry_level' as staples change; 'grocery_list' for the run.\",\n inputs: {\n action: {\n type: 'string',\n description:\n \"One of 'status', 'set_menu', 'pantry_add', 'pantry_level', or 'grocery_list'.\",\n required: true,\n },\n day: {\n type: 'string',\n description: \"Day for set_menu: monday..sunday.\",\n },\n meal: { type: 'string', description: 'The dinner for that day.' },\n name: { type: 'string', description: 'Pantry item name.' },\n level: { type: 'string', description: \"Pantry level: 'stocked', 'low', or 'out'.\" },\n id: { type: 'string', description: \"Pantry item id, e.g. 'item-3'.\" },\n },\n outputs: {\n planned: { type: 'number', description: 'Days with a planned dinner.' },\n groceries: { type: 'number', description: 'Items on the grocery list.' },\n summary: { type: 'string', description: 'The kitchen picture.' },\n },\n requires: ['workspace.read', 'workspace.write'],\n} as const);\n\nconst input = gezel.input as InferredInput<typeof meta>;\n\n// ── kitchen-rules-start ──\n// Pure derivations: the weekly menu shape and the grocery gap (pantry\n// items marked low or out).\n\nvar DAYS = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];\n\nfunction normalizeDay(day) {\n if (typeof day !== 'string') return null;\n var d = day.toLowerCase();\n return DAYS.indexOf(d) >= 0 ? d : null;\n}\n\nfunction todayName(nowMs) {\n // getUTCDay: 0=Sunday.\n var idx = new Date(nowMs).getUTCDay();\n return DAYS[(idx + 6) % 7];\n}\n\nfunction groceryGap(records) {\n return records.filter(function (r) {\n return r.status === 'low' || r.status === 'out';\n });\n}\n\nfunction kitchenLine(menu, records, nowMs) {\n var today = todayName(nowMs);\n var tonight = menu.days && menu.days[today];\n var gap = groceryGap(records);\n var planned = DAYS.filter(function (d) {\n return menu.days && menu.days[d];\n }).length;\n return (\n (tonight ? \"Tonight: \" + tonight + '.' : 'Nothing planned for tonight.') +\n ' ' +\n planned +\n ' of 7 dinners planned. Grocery list: ' +\n (gap.length\n ? gap\n .map(function (r) {\n return (r.fields && r.fields.name) || r.id;\n })\n .join(', ')\n : 'empty')\n );\n}\n// ── kitchen-rules-end ──\n\nconst MENU_FILE = 'menu.json';\nconst pantry = rosterStore(gezel.fs, 'pantry.json', {\n stages: ['stocked', 'low', 'out'],\n idPrefix: 'item',\n});\n\ninterface Menu {\n version: number;\n days: Record<string, string>;\n updatedAt: string;\n}\n\nasync function loadMenu(): Promise<Menu> {\n try {\n const parsed = JSON.parse(await gezel.fs.read(MENU_FILE)) as Partial<Menu>;\n return {\n version: 1,\n days: parsed.days && typeof parsed.days === 'object' ? (parsed.days as Record<string, string>) : {},\n updatedAt: new Date().toISOString(),\n };\n } catch {\n return { version: 1, days: {}, updatedAt: new Date().toISOString() };\n }\n}\n\nasync function saveMenu(menu: Menu): Promise<void> {\n menu.updatedAt = new Date().toISOString();\n await gezel.fs.write(MENU_FILE, `${JSON.stringify(menu, null, 2)}\\n`);\n}\n\nasync function resolveItem(): Promise<string> {\n if (input.id) return input.id;\n if (input.name) {\n const wanted = input.name.toLowerCase();\n const matches = (await pantry.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. 'item-3') or an exact pantry item 'name'.\");\n}\n\nconst menu = await loadMenu();\n\nif (input.action === 'set_menu') {\n const day = normalizeDay(input.day);\n if (!day || !input.meal) throw new Error(\"set_menu needs 'day' (monday..sunday) and 'meal'.\");\n menu.days[day] = input.meal;\n await saveMenu(menu);\n} else if (input.action === 'pantry_add') {\n if (!input.name) throw new Error(\"pantry_add needs 'name'.\");\n const level = input.level && ['stocked', 'low', 'out'].includes(input.level) ? input.level : 'stocked';\n await pantry.add({ status: level, fields: { name: input.name } });\n} else if (input.action === 'pantry_level') {\n if (!input.level || !['stocked', 'low', 'out'].includes(input.level)) {\n throw new Error(\"pantry_level needs level 'stocked', 'low', or 'out'.\");\n }\n await pantry.transition(await resolveItem(), input.level);\n} else if (input.action !== 'status' && input.action !== 'grocery_list') {\n throw new Error(\n `Unknown action '${input.action}'. Use status, set_menu, pantry_add, pantry_level, or grocery_list.`,\n );\n}\n\nconst records = await pantry.list();\nconst gap = groceryGap(records);\nconst listing =\n input.action === 'grocery_list'\n ? gap\n .map((r) => `${String(r.fields.name ?? r.id)} (${r.status})`)\n .join('\\n') || 'The grocery list is empty.'\n : '';\n\ngezel.output({\n planned: DAYS.filter((d) => menu.days[d]).length,\n groceries: gap.length,\n summary: listing || kitchenLine(menu, records, Date.now()),\n});\n"
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
- The week's dinners get planned in one sitting, weeknight-easy skewed, from
|
|
2
|
+
what's already in the pantry.
|
|
3
|
+
- The grocery list is the derived gap — grouped, short, one run.
|
|
4
|
+
- Taste memory compounds: loved dishes return, vetoed ones never sneak back.
|
|
5
|
+
- Waste goes down: what's low or turning gets cooked first.
|