@bendyline/gilde 0.1.94 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/data/craftbook-templates/ab/ab-test-readout/manifest.json +4 -0
- package/data/craftbook-templates/al/album-curate/manifest.json +4 -0
- package/data/craftbook-templates/an/anniversary-cut/manifest.json +4 -0
- package/data/craftbook-templates/an/annotated-bibliography/manifest.json +4 -0
- package/data/craftbook-templates/an/annual-document-review/manifest.json +4 -0
- package/data/craftbook-templates/an/anomaly-scan/manifest.json +3 -0
- package/data/craftbook-templates/au/audio-ad-spot/manifest.json +4 -0
- package/data/craftbook-templates/bo/board-deck/manifest.json +3 -0
- package/data/craftbook-templates/bo/board-game-web/manifest.json +4 -0
- package/data/craftbook-templates/br/branding-website/manifest.json +4 -0
- package/data/craftbook-templates/br/broadcast-announcement/manifest.json +4 -0
- package/data/craftbook-templates/ca/calendar-brief/manifest.json +3 -0
- package/data/craftbook-templates/ch/chapter-weave/manifest.json +4 -0
- package/data/craftbook-templates/ch/chart-pack/manifest.json +3 -0
- package/data/craftbook-templates/co/codebase-ux-review/manifest.json +3 -0
- package/data/craftbook-templates/co/cohort-analysis/manifest.json +3 -0
- package/data/craftbook-templates/co/company-research-brief/manifest.json +4 -0
- package/data/craftbook-templates/co/compat-check/manifest.json +4 -0
- package/data/craftbook-templates/co/competitive-analysis/manifest.json +4 -0
- package/data/craftbook-templates/co/content-accuracy-review/manifest.json +4 -0
- package/data/craftbook-templates/co/content-deck/manifest.json +4 -0
- package/data/craftbook-templates/co/contract-template/manifest.json +4 -0
- package/data/craftbook-templates/co/corpus-email-digest/manifest.json +3 -0
- package/data/craftbook-templates/co/cover-letter/manifest.json +3 -0
- package/data/craftbook-templates/cr/crm-update-batch/manifest.json +3 -0
- package/data/craftbook-templates/cr/crossword-forge/manifest.json +4 -0
- package/data/craftbook-templates/cs/csv-transformer/manifest.json +3 -0
- package/data/craftbook-templates/da/dashboard-spec/manifest.json +4 -0
- package/data/craftbook-templates/da/data-dictionary/manifest.json +3 -0
- package/data/craftbook-templates/da/data-export-migrate/manifest.json +4 -0
- package/data/craftbook-templates/da/data-join-merge/manifest.json +3 -0
- package/data/craftbook-templates/da/data-quality-audit/manifest.json +3 -0
- package/data/craftbook-templates/da/data-to-report/manifest.json +4 -0
- package/data/craftbook-templates/da/data-viz-page/manifest.json +4 -0
- package/data/craftbook-templates/da/dataset-clean/manifest.json +3 -0
- package/data/craftbook-templates/de/design-review/manifest.json +4 -0
- package/data/craftbook-templates/de/design-system-consultation/manifest.json +4 -0
- package/data/craftbook-templates/di/diagram-from-text/manifest.json +4 -0
- package/data/craftbook-templates/dr/drive-wrap-up/manifest.json +4 -0
- package/data/craftbook-templates/du/due-diligence-brief/manifest.json +4 -0
- package/data/craftbook-templates/en/engineering-retrospective/manifest.json +4 -0
- package/data/craftbook-templates/ex/expense-categorize/manifest.json +4 -0
- package/data/craftbook-templates/ex/explain-code-folder/manifest.json +26 -0
- package/data/craftbook-templates/ex/explain-code-folder/versions/1.0.0/craftbook.json +233 -0
- package/data/craftbook-templates/ex/explain-code-folder/versions/1.0.0/samples/todo-cli/README.md +29 -0
- package/data/craftbook-templates/ex/explain-code-folder/versions/1.0.0/samples/todo-cli/package.json +17 -0
- package/data/craftbook-templates/ex/explain-code-folder/versions/1.0.0/samples/todo-cli/src/format.js +15 -0
- package/data/craftbook-templates/ex/explain-code-folder/versions/1.0.0/samples/todo-cli/src/index.js +50 -0
- package/data/craftbook-templates/ex/explain-code-folder/versions/1.0.0/samples/todo-cli/src/store.js +45 -0
- package/data/craftbook-templates/ex/explain-code-folder/versions/1.0.0/samples/todo-cli/test/store.test.js +61 -0
- package/data/craftbook-templates/ex/explain-code-folder/versions/1.0.0/test.json +106 -0
- package/data/craftbook-templates/fa/family-story-chapter/manifest.json +4 -0
- package/data/craftbook-templates/fo/forecast-model/manifest.json +4 -0
- package/data/craftbook-templates/ga/game-with-screens/manifest.json +4 -0
- package/data/craftbook-templates/gl/glossary-build/manifest.json +3 -0
- package/data/craftbook-templates/ha/handbook-section/manifest.json +3 -0
- package/data/craftbook-templates/he/hero-image/manifest.json +4 -0
- package/data/craftbook-templates/ht/html-arcade-game/manifest.json +4 -0
- package/data/craftbook-templates/ht/html-puzzle-game/manifest.json +4 -0
- package/data/craftbook-templates/id/idle-clicker-game/manifest.json +4 -0
- package/data/craftbook-templates/in/inbox-triage/manifest.json +3 -0
- package/data/craftbook-templates/in/insurance-inventory/manifest.json +4 -0
- package/data/craftbook-templates/in/interactive-quiz/manifest.json +3 -0
- package/data/craftbook-templates/in/invoice-generator/manifest.json +3 -0
- package/data/craftbook-templates/in/invoice-run/manifest.json +3 -0
- package/data/craftbook-templates/index.json +1 -1
- package/data/craftbook-templates/ju/juice-pass/manifest.json +4 -0
- package/data/craftbook-templates/kp/kpi-scorecard/manifest.json +4 -0
- package/data/craftbook-templates/la/landing-page/manifest.json +4 -0
- package/data/craftbook-templates/le/lead-enrichment/manifest.json +4 -0
- package/data/craftbook-templates/li/literature-review/manifest.json +4 -0
- package/data/craftbook-templates/lo/logo-set/manifest.json +4 -0
- package/data/craftbook-templates/ma/market-scan/manifest.json +4 -0
- package/data/craftbook-templates/me/meeting-minutes/manifest.json +3 -0
- package/data/craftbook-templates/me/meeting-notes-to-actions/manifest.json +3 -0
- package/data/craftbook-templates/me/meeting-notes-to-actions/versions/1.1.2/craftbook.json +255 -0
- package/data/craftbook-templates/me/meeting-notes-to-actions/versions/1.1.2/samples/team-sync/notes.md +47 -0
- package/data/craftbook-templates/me/meeting-notes-to-actions/versions/1.1.2/test.json +78 -0
- package/data/craftbook-templates/me/meeting-prep-brief/manifest.json +3 -0
- package/data/craftbook-templates/me/meeting-summary-audio/manifest.json +3 -0
- package/data/craftbook-templates/me/memory-prompt-session/manifest.json +3 -0
- package/data/craftbook-templates/mi/micro-game-jam/manifest.json +4 -0
- package/data/craftbook-templates/mo/mock-interview/manifest.json +3 -0
- package/data/craftbook-templates/mo/mod-scaffold/manifest.json +4 -0
- package/data/craftbook-templates/mo/month-close/manifest.json +3 -0
- package/data/craftbook-templates/mo/monthly-recording-review/manifest.json +3 -0
- package/data/craftbook-templates/oc/ocr-extract/manifest.json +4 -0
- package/data/craftbook-templates/of/offer-compare/manifest.json +3 -0
- package/data/craftbook-templates/on/one-pager/manifest.json +4 -0
- package/data/craftbook-templates/ph/photo-cull/manifest.json +4 -0
- package/data/craftbook-templates/ph/photo-library-nightly/manifest.json +4 -0
- package/data/craftbook-templates/ph/physics-toy/manifest.json +4 -0
- package/data/craftbook-templates/pi/pitch-deck/manifest.json +4 -0
- package/data/craftbook-templates/pl/playtest-report/manifest.json +4 -0
- package/data/craftbook-templates/po/powerpoint-deck/manifest.json +4 -0
- package/data/craftbook-templates/pr/practice-exam/manifest.json +3 -0
- package/data/craftbook-templates/pr/practice-session/manifest.json +3 -0
- package/data/craftbook-templates/pr/price-tracker/manifest.json +4 -0
- package/data/craftbook-templates/pr/proposal-sow/manifest.json +3 -0
- package/data/craftbook-templates/pu/puzzle-pack-layout/manifest.json +4 -0
- package/data/craftbook-templates/qu/quarterly-summary/manifest.json +4 -0
- package/data/craftbook-templates/raw-index.json +1 -1
- package/data/craftbook-templates/re/reading-list/manifest.json +3 -0
- package/data/craftbook-templates/re/receipt-intake/manifest.json +4 -0
- package/data/craftbook-templates/re/receipt-ocr-ledger/manifest.json +3 -0
- package/data/craftbook-templates/re/recurring-invoice-run/manifest.json +3 -0
- package/data/craftbook-templates/re/release-announcement/manifest.json +4 -0
- package/data/craftbook-templates/re/resume-cv/manifest.json +3 -0
- package/data/craftbook-templates/re/review-code-folder/manifest.json +25 -0
- package/data/craftbook-templates/re/review-code-folder/versions/1.0.0/craftbook.json +223 -0
- package/data/craftbook-templates/re/review-code-folder/versions/1.0.0/samples/todo-cli-bugs/README.md +29 -0
- package/data/craftbook-templates/re/review-code-folder/versions/1.0.0/samples/todo-cli-bugs/package.json +17 -0
- package/data/craftbook-templates/re/review-code-folder/versions/1.0.0/samples/todo-cli-bugs/src/format.js +15 -0
- package/data/craftbook-templates/re/review-code-folder/versions/1.0.0/samples/todo-cli-bugs/src/index.js +50 -0
- package/data/craftbook-templates/re/review-code-folder/versions/1.0.0/samples/todo-cli-bugs/src/store.js +44 -0
- package/data/craftbook-templates/re/review-code-folder/versions/1.0.0/samples/todo-cli-bugs/test/store.test.js +37 -0
- package/data/craftbook-templates/re/review-code-folder/versions/1.0.0/test.json +108 -0
- package/data/craftbook-templates/sc/scrape-to-structured/manifest.json +3 -0
- package/data/craftbook-templates/sp/spa-dashboard/manifest.json +4 -0
- package/data/craftbook-templates/sp/spreadsheet-findings/manifest.json +26 -0
- package/data/craftbook-templates/sp/spreadsheet-findings/versions/1.0.0/craftbook.json +186 -0
- package/data/craftbook-templates/sp/spreadsheet-findings/versions/1.0.0/samples/shop-sales/sales.csv +49 -0
- package/data/craftbook-templates/sp/spreadsheet-findings/versions/1.0.0/test.json +88 -0
- package/data/craftbook-templates/sp/spreadsheet-model/manifest.json +4 -0
- package/data/craftbook-templates/sp/sprite-sheet/manifest.json +4 -0
- package/data/craftbook-templates/sq/sql-analysis/manifest.json +3 -0
- package/data/craftbook-templates/st/standup-summary/manifest.json +3 -0
- package/data/craftbook-templates/st/status-report/manifest.json +3 -0
- package/data/craftbook-templates/su/subscription-audit/manifest.json +4 -0
- package/data/craftbook-templates/su/survey-analysis/manifest.json +4 -0
- package/data/craftbook-templates/ta/tailor-resume/manifest.json +3 -0
- package/data/craftbook-templates/ti/tidy-spreadsheet/manifest.json +26 -0
- package/data/craftbook-templates/ti/tidy-spreadsheet/versions/1.0.0/craftbook.json +183 -0
- package/data/craftbook-templates/ti/tidy-spreadsheet/versions/1.0.0/samples/club-members/members.csv +43 -0
- package/data/craftbook-templates/ti/tidy-spreadsheet/versions/1.0.0/test.json +108 -0
- package/data/craftbook-templates/ti/tileset-batch/manifest.json +4 -0
- package/data/craftbook-templates/to/topic-explainer/manifest.json +4 -0
- package/data/craftbook-templates/tr/transcribe-audio/manifest.json +4 -0
- package/data/craftbook-templates/tr/transcribe-index/manifest.json +4 -0
- package/data/craftbook-templates/vi/visit-prep/manifest.json +4 -0
- package/data/craftbook-templates/we/weak-spot-drill/manifest.json +3 -0
- package/data/craftbook-templates/we/week-plan/manifest.json +3 -0
- package/data/craftbook-templates/we/weekly-pipeline-review/manifest.json +3 -0
- package/data/craftbook-templates/we/weekly-review/manifest.json +3 -0
- package/data/craftbook-templates/ye/year-in-review/manifest.json +4 -0
- package/data/image-models/an/animagine-xl-4/versions/4.0.1/manifest.json +16 -0
- package/data/image-models/fl/flux-2-klein-4b-q4/versions/1.0.1/manifest.json +29 -0
- package/data/image-models/index.json +1 -1
- package/data/image-models/ir/iris-3b-f32/manifest.json +39 -0
- package/data/image-models/ir/iris-3b-f32/versions/1.0.0/manifest.json +19 -0
- package/data/image-models/ir/iris-3b-f32/versions/1.0.1/manifest.json +25 -0
- package/data/image-models/kr/krea-2-turbo-q4/versions/1.0.1/manifest.json +29 -0
- package/data/image-models/kr/krea-2-turbo-q6/versions/1.0.1/manifest.json +29 -0
- package/data/image-models/kr/krea-2-turbo-q8/versions/1.0.1/manifest.json +29 -0
- package/data/image-models/mi/ming-image-0.1-design-bf16/versions/1.0.1/manifest.json +50 -0
- package/data/image-models/raw-index.json +1 -1
- package/data/image-models/sd/sd-1.5/versions/1.0.1/manifest.json +16 -0
- package/data/image-models/sd/sd-turbo/versions/1.0.1/manifest.json +16 -0
- package/data/image-models/sd/sdxl-base-1.0/versions/1.0.1/manifest.json +16 -0
- package/data/image-models/sd/sdxl-lightning-4step/versions/1.0.1/manifest.json +16 -0
- package/data/image-models/sd/sdxl-turbo/versions/1.0.1/manifest.json +16 -0
- package/data/project-types/ca/caregiving-binder/manifest.json +4 -0
- package/data/project-types/de/design-scheme/manifest.json +4 -0
- package/data/project-types/fi/fitness-coach/manifest.json +4 -0
- package/data/project-types/fr/freelance-office/manifest.json +3 -0
- package/data/project-types/fu/fundraiser-hq/manifest.json +4 -0
- package/data/project-types/ho/household-budget/manifest.json +3 -0
- package/data/project-types/index.json +1 -1
- package/data/project-types/jo/job-hunt/manifest.json +4 -0
- package/data/project-types/jo/job-hunt/versions/1.0.3/about.md +10 -0
- package/data/project-types/jo/job-hunt/versions/1.0.3/activity.json +4 -0
- package/data/project-types/jo/job-hunt/versions/1.0.3/manifest.json +212 -0
- package/data/project-types/jo/job-hunt/versions/1.0.3/mission.md +9 -0
- package/data/project-types/jo/job-hunt/versions/1.0.3/pages/dashboard/index.html +430 -0
- package/data/project-types/jo/job-hunt/versions/1.0.3/pipeline.json +6 -0
- package/data/project-types/li/life-binder/manifest.json +4 -0
- package/data/project-types/raw-index.json +1 -1
- package/data/project-types/so/social-feed/manifest.json +3 -0
- package/data/project-types/tr/trip-planner/versions/1.0.3/about.md +7 -0
- package/data/project-types/tr/trip-planner/versions/1.0.3/journal.json +4 -0
- package/data/project-types/tr/trip-planner/versions/1.0.3/manifest.json +226 -0
- package/data/project-types/tr/trip-planner/versions/1.0.3/mission.md +4 -0
- package/data/project-types/tr/trip-planner/versions/1.0.3/packing.json +8 -0
- package/data/project-types/tr/trip-planner/versions/1.0.3/pages/dashboard/index.html +368 -0
- package/data/project-types/tr/trip-planner/versions/1.0.3/trip.json +8 -0
- package/data/toolsets/do/docblocks/versions/2.7.0/manifest.json +95 -0
- package/data/toolsets/index.json +1 -1
- package/data/toolsets/raw-index.json +1 -1
- package/data/video-models/index.json +1 -1
- package/data/video-models/lt/ltx-2-19b/versions/1.0.1/manifest.json +375 -0
- package/data/video-models/lt/ltx-2.3-22b/versions/1.0.1/manifest.json +377 -0
- package/data/video-models/lt/ltx-2.3-22b-distilled/versions/1.0.1/manifest.json +377 -0
- package/data/video-models/lt/ltx-video-0.9.7-dev/versions/1.0.1/manifest.json +195 -0
- package/data/video-models/lt/ltx-video-0.9.8-13b-distilled/versions/1.0.1/manifest.json +190 -0
- package/data/video-models/raw-index.json +1 -1
- package/data/video-models/wa/wan2.2-ti2v-5b/versions/1.0.1/manifest.json +168 -0
- package/package.json +2 -1
- package/schemas/craftbook-doc.schema.json +35 -0
- package/schemas/craftbook-template-identity.schema.json +18 -0
- package/schemas/craftbook-template-version.schema.json +35 -0
- package/schemas/image-model-identity.schema.json +0 -2
- package/schemas/image-model-version.schema.json +50 -0
- package/schemas/project-type-identity.schema.json +18 -0
- package/schemas/video-model-identity.schema.json +0 -2
- package/schemas/video-model-version.schema.json +25 -0
- package/data/image-models/fl/flux-1-dev-q4/manifest.json +0 -38
- package/data/image-models/fl/flux-1-dev-q4/versions/1.0.0/manifest.json +0 -29
- package/data/image-models/fl/flux-1-schnell-q4/manifest.json +0 -39
- package/data/image-models/fl/flux-1-schnell-q4/versions/1.0.0/manifest.json +0 -29
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
The trip bench for **{{destination}}**, departing {{start}}. Your Reisleider
|
|
2
|
+
plans each day around one anchor with room to breathe, keeps confirmations and
|
|
3
|
+
addresses where they can be found, and builds a packing list that fits the trip.
|
|
4
|
+
|
|
5
|
+
The **Output** tab shows the countdown, the itinerary as it fills in, and the
|
|
6
|
+
packing list — tick items off right on the board. During the trip, a line a day
|
|
7
|
+
in the journal; afterward, it's the souvenir.
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"version": "1.0.3",
|
|
4
|
+
"releasedAt": "2026-10-12T00:00:00Z",
|
|
5
|
+
"meesterManaged": false,
|
|
6
|
+
"params": {
|
|
7
|
+
"type": "object",
|
|
8
|
+
"properties": {
|
|
9
|
+
"destination": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"title": "Where to?",
|
|
12
|
+
"description": "e.g. 'Kyoto', 'the Scottish Highlands'.",
|
|
13
|
+
"default": "somewhere wonderful"
|
|
14
|
+
},
|
|
15
|
+
"start": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"title": "Departure date",
|
|
18
|
+
"description": "e.g. 2026-09-12. A rough date is fine.",
|
|
19
|
+
"default": "2026-12-01"
|
|
20
|
+
},
|
|
21
|
+
"travellers": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"title": "Who's going?",
|
|
24
|
+
"description": "e.g. '4', or 'two adults and a toddler'. Leave empty if you're not sure yet.",
|
|
25
|
+
"default": ""
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"required": [
|
|
29
|
+
"destination",
|
|
30
|
+
"start"
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
"nameTemplate": "Trip: {{destination}}",
|
|
34
|
+
"aboutTemplate": "about.md",
|
|
35
|
+
"missionTemplate": "mission.md",
|
|
36
|
+
"gezels": [
|
|
37
|
+
{
|
|
38
|
+
"templateId": "reisleider",
|
|
39
|
+
"voorman": true
|
|
40
|
+
}
|
|
41
|
+
],
|
|
42
|
+
"tools": [
|
|
43
|
+
{
|
|
44
|
+
"name": "set_trip",
|
|
45
|
+
"description": "Change the trip's details when the traveller gives new ones: the departure date, who is going, or the destination. Record their words; never choose a date for them.",
|
|
46
|
+
"script": "trip-store",
|
|
47
|
+
"inputs": {
|
|
48
|
+
"type": "object",
|
|
49
|
+
"properties": {
|
|
50
|
+
"start": {
|
|
51
|
+
"type": "string",
|
|
52
|
+
"description": "The departure date they gave: YYYY-MM-DD when they named a day, otherwise their words ('next spring')."
|
|
53
|
+
},
|
|
54
|
+
"travellers": {
|
|
55
|
+
"type": "string",
|
|
56
|
+
"description": "How many are going ('4'), or who ('two adults and a toddler')."
|
|
57
|
+
},
|
|
58
|
+
"destination": {
|
|
59
|
+
"type": "string"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"bind": {
|
|
64
|
+
"action": "set_trip"
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"name": "plan_day",
|
|
69
|
+
"description": "Set one day's plan in plain sentences, one per line: what the day is for, then a couple of alternatives, then the free time. Use the date (YYYY-MM-DD) once it is known, otherwise 'Day 1', 'Day 2', … Never write 'Anchor:' or 'Options:' labels.",
|
|
70
|
+
"script": "trip-store",
|
|
71
|
+
"inputs": {
|
|
72
|
+
"type": "object",
|
|
73
|
+
"properties": {
|
|
74
|
+
"date": {
|
|
75
|
+
"type": "string",
|
|
76
|
+
"description": "YYYY-MM-DD, or 'Day 1' when the dates are not set yet."
|
|
77
|
+
},
|
|
78
|
+
"plan": {
|
|
79
|
+
"type": "string",
|
|
80
|
+
"description": "The day in plain sentences, one per line."
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
"required": [
|
|
84
|
+
"date",
|
|
85
|
+
"plan"
|
|
86
|
+
]
|
|
87
|
+
},
|
|
88
|
+
"bind": {
|
|
89
|
+
"action": "plan_day"
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"name": "pack_add",
|
|
94
|
+
"description": "Add an item to the packing list.",
|
|
95
|
+
"script": "trip-store",
|
|
96
|
+
"inputs": {
|
|
97
|
+
"type": "object",
|
|
98
|
+
"properties": {
|
|
99
|
+
"item": {
|
|
100
|
+
"type": "string"
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
"required": [
|
|
104
|
+
"item"
|
|
105
|
+
]
|
|
106
|
+
},
|
|
107
|
+
"bind": {
|
|
108
|
+
"action": "pack_add"
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"name": "pack_check",
|
|
113
|
+
"description": "Mark an item packed.",
|
|
114
|
+
"script": "trip-store",
|
|
115
|
+
"inputs": {
|
|
116
|
+
"type": "object",
|
|
117
|
+
"properties": {
|
|
118
|
+
"id": {
|
|
119
|
+
"type": "string"
|
|
120
|
+
},
|
|
121
|
+
"item": {
|
|
122
|
+
"type": "string"
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
"bind": {
|
|
127
|
+
"action": "pack_check"
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"name": "journal_entry",
|
|
132
|
+
"description": "Capture a trip moment — a line a day.",
|
|
133
|
+
"script": "trip-store",
|
|
134
|
+
"inputs": {
|
|
135
|
+
"type": "object",
|
|
136
|
+
"properties": {
|
|
137
|
+
"note": {
|
|
138
|
+
"type": "string"
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
"required": [
|
|
142
|
+
"note"
|
|
143
|
+
]
|
|
144
|
+
},
|
|
145
|
+
"bind": {
|
|
146
|
+
"action": "journal_entry"
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"name": "trip_status",
|
|
151
|
+
"description": "Who is going, the countdown, days planned, and the packing state.",
|
|
152
|
+
"script": "trip-store",
|
|
153
|
+
"inputs": {
|
|
154
|
+
"type": "object",
|
|
155
|
+
"properties": {}
|
|
156
|
+
},
|
|
157
|
+
"bind": {
|
|
158
|
+
"action": "status"
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"name": "itinerary",
|
|
163
|
+
"description": "Read the full day-by-day itinerary.",
|
|
164
|
+
"script": "trip-store",
|
|
165
|
+
"inputs": {
|
|
166
|
+
"type": "object",
|
|
167
|
+
"properties": {}
|
|
168
|
+
},
|
|
169
|
+
"bind": {
|
|
170
|
+
"action": "itinerary"
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"name": "mark_packed",
|
|
175
|
+
"description": "The dashboard's packing checkbox.",
|
|
176
|
+
"script": "trip-store",
|
|
177
|
+
"inputs": {
|
|
178
|
+
"type": "object",
|
|
179
|
+
"properties": {
|
|
180
|
+
"id": {
|
|
181
|
+
"type": "string"
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
"required": [
|
|
185
|
+
"id"
|
|
186
|
+
]
|
|
187
|
+
},
|
|
188
|
+
"bind": {
|
|
189
|
+
"action": "pack_check"
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
],
|
|
193
|
+
"craftbooks": [
|
|
194
|
+
"pre-departure-countdown"
|
|
195
|
+
],
|
|
196
|
+
"schedules": [],
|
|
197
|
+
"pages": {
|
|
198
|
+
"entry": "dashboard/index.html",
|
|
199
|
+
"api": 1,
|
|
200
|
+
"reads": [
|
|
201
|
+
{
|
|
202
|
+
"source": "workspace",
|
|
203
|
+
"path": "trip.json"
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"source": "workspace",
|
|
207
|
+
"path": "packing.json"
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"source": "workspace",
|
|
211
|
+
"path": "journal.json"
|
|
212
|
+
}
|
|
213
|
+
],
|
|
214
|
+
"tools": [
|
|
215
|
+
"mark_packed"
|
|
216
|
+
]
|
|
217
|
+
},
|
|
218
|
+
"workspaceSeed": [
|
|
219
|
+
"trip.json",
|
|
220
|
+
"packing.json",
|
|
221
|
+
"journal.json"
|
|
222
|
+
],
|
|
223
|
+
"scripts": {
|
|
224
|
+
"trip-store": "import { type InferredInput, defineScript, gezel } from '@bendyline/gezel-sdk';\nimport { logStore, rosterStore } from '@bendyline/gezel-sdk/stores';\n\nexport const meta = defineScript({\n name: 'trip-store',\n description:\n \"Read and update the trip: itinerary + packing (trip.json) and the journal (journal.json). Call action:'status' for the shape of the trip; 'set_trip' with start / travellers / destination as the traveller tells you; 'plan_day' with date + plan; 'pack_add' / 'pack_check' for the packing list; 'journal_entry' during the trip.\",\n inputs: {\n action: {\n type: 'string',\n description:\n \"One of 'status', 'set_trip', 'plan_day', 'pack_add', 'pack_check', 'journal_entry', or 'itinerary'.\",\n required: true,\n },\n date: {\n type: 'string',\n description: \"The day being planned: 2026-09-14 once the dates are known, otherwise 'Day 1', 'Day 2', ...\",\n },\n plan: {\n type: 'string',\n description:\n \"The day in plain sentences, one per line: what the day is for, then a couple of alternatives, then the free time. No 'Anchor:' or 'Options:' labels.\",\n },\n start: {\n type: 'string',\n description: \"The departure date they gave: 2026-09-12 when they named a day, otherwise their words ('next spring'). Never choose one for them.\",\n },\n travellers: { type: 'string', description: \"Who is going: a number ('4') or a phrase ('two adults and a toddler').\" },\n destination: { type: 'string', description: 'Where the trip goes.' },\n item: { type: 'string', description: 'Packing item name.' },\n id: { type: 'string', description: \"Packing item id, e.g. 'pack-3'.\" },\n note: { type: 'string', description: 'Journal entry text.' },\n },\n outputs: {\n daysPlanned: { type: 'number', description: 'Days with a plan.' },\n unpacked: { type: 'number', description: 'Packing items still unpacked.' },\n summary: { type: 'string', description: 'The trip picture (or the itinerary).' },\n },\n requires: ['workspace.read', 'workspace.write'],\n} as const);\n\nconst input = gezel.input as InferredInput<typeof meta>;\n\n// ── trip-rules-start ──\n// Pure derivations over the trip doc: countdown, day keys and itinerary rendering.\n\nfunction plural(n, one, many) {\n return n + ' ' + (n === 1 ? one : many);\n}\n\nfunction daysUntil(dateStr, nowMs) {\n if (!/^\\d{4}-\\d{2}-\\d{2}/.test(String(dateStr || ''))) return null;\n var when = Date.parse(dateStr);\n if (!isFinite(when)) return null;\n return Math.ceil((when - nowMs) / (24 * 60 * 60 * 1000));\n}\n\n// \"2026-09-14T10:00\" -> \"2026-09-14\"; \"day 3\" / \"Day3\" / \"3\" -> \"Day 3\".\nfunction dayKey(raw) {\n var text = String(raw || '').trim();\n var iso = text.match(/^\\d{4}-\\d{2}-\\d{2}/);\n if (iso) return iso[0];\n var numbered = text.match(/^(?:day\\s*)?(\\d{1,3})$/i);\n if (numbered) return 'Day ' + Number(numbered[1]);\n return text;\n}\n\n// Numbered days sort by number (\"Day 10\" after \"Day 9\"); dates sort as text.\nfunction sortedDays(days) {\n return Object.keys(days).sort(function (a, b) {\n var na = a.match(/^Day (\\d+)$/);\n var nb = b.match(/^Day (\\d+)$/);\n if (na && nb) return Number(na[1]) - Number(nb[1]);\n return a < b ? -1 : a > b ? 1 : 0;\n });\n}\n\n// \"4 people\" / \"4 travellers\" -> \"4\", so the board can say \"4 travellers\";\n// a phrase (\"two adults and a toddler\") is kept as written.\nfunction partyCount(raw) {\n var text = String(raw || '').trim();\n var counted = text.match(/^(\\d{1,3})\\s*(?:people|persons|travell?ers|adults|guests|of us)?$/i);\n return counted ? String(Number(counted[1])) : text;\n}\n\nfunction travellersLine(travellers) {\n var text = String(travellers || '').trim();\n if (!text) return '';\n if (/^\\d+$/.test(text)) return plural(Number(text), 'traveller', 'travellers');\n return text;\n}\n\nfunction renderItinerary(days) {\n var keys = sortedDays(days);\n if (keys.length === 0) return 'No days planned yet.';\n return keys\n .map(function (d) {\n return d + ': ' + days[d];\n })\n .join('\\n');\n}\n\nfunction tripLine(trip, unpackedCount, nowMs) {\n var start = daysUntil(trip.startDate, nowMs);\n var planned = Object.keys(trip.days || {}).length;\n var who = travellersLine(trip.travellers);\n var lead =\n start === null\n ? trip.startDate && trip.startDate !== 'soon'\n ? 'Leaving ' + trip.startDate + '. '\n : 'No departure date yet. '\n : start > 0\n ? plural(start, 'day', 'days') + ' until departure. '\n : start === 0\n ? 'Departure day! '\n : 'On the road (or home again). ';\n return (\n (who ? who + '. ' : '') +\n lead +\n plural(planned, 'day', 'days') + ' planned. Packing: ' +\n (unpackedCount === 0 ? 'all packed.' : plural(unpackedCount, 'item', 'items') + ' still unpacked.')\n );\n}\n// ── trip-rules-end ──\n\nconst TRIP_FILE = 'trip.json';\nconst packing = rosterStore(gezel.fs, 'packing.json', {\n stages: ['unpacked', 'packed'],\n idPrefix: 'pack',\n});\nconst journal = logStore(gezel.fs, 'journal.json');\n\ninterface Trip {\n version: number;\n destination: string;\n startDate: string;\n travellers: string;\n days: Record<string, string>;\n updatedAt: string;\n}\n\nasync function loadTrip(): Promise<Trip> {\n try {\n const parsed = JSON.parse(await gezel.fs.read(TRIP_FILE)) as Partial<Trip>;\n return {\n version: 1,\n destination: typeof parsed.destination === 'string' ? parsed.destination : '',\n startDate: typeof parsed.startDate === 'string' ? parsed.startDate : '',\n travellers: typeof parsed.travellers === 'string' ? parsed.travellers : '',\n days: parsed.days && typeof parsed.days === 'object' ? (parsed.days as Record<string, string>) : {},\n updatedAt: new Date().toISOString(),\n };\n } catch {\n return {\n version: 1,\n destination: '',\n startDate: '',\n travellers: '',\n days: {},\n updatedAt: new Date().toISOString(),\n };\n }\n}\n\nasync function saveTrip(trip: Trip): Promise<void> {\n trip.updatedAt = new Date().toISOString();\n await gezel.fs.write(TRIP_FILE, `${JSON.stringify(trip, null, 2)}\\n`);\n}\n\nconst trip = await loadTrip();\n\nif (input.action === 'set_trip') {\n if (!input.start && !input.travellers && !input.destination) {\n throw new Error(\"set_trip needs at least one of 'start', 'travellers', or 'destination'.\");\n }\n if (input.start) trip.startDate = input.start.trim();\n if (input.travellers) trip.travellers = partyCount(input.travellers);\n if (input.destination) trip.destination = input.destination.trim();\n await saveTrip(trip);\n} else if (input.action === 'plan_day') {\n if (!input.date || !input.plan) throw new Error(\"plan_day needs 'date' (YYYY-MM-DD, or 'Day 1') and 'plan'.\");\n trip.days[dayKey(input.date)] = input.plan;\n await saveTrip(trip);\n} else if (input.action === 'pack_add') {\n if (!input.item) throw new Error(\"pack_add needs 'item'.\");\n await packing.add({ status: 'unpacked', fields: { name: input.item } });\n} else if (input.action === 'pack_check') {\n let id = input.id;\n if (!id && input.item) {\n const wanted = input.item.toLowerCase();\n const matches = (await packing.list()).filter(\n (r) => String(r.fields.name ?? '').toLowerCase() === wanted,\n );\n if (matches.length === 1) id = matches[0]!.id;\n }\n if (!id) throw new Error(\"Pass 'id' (e.g. 'pack-3') or an exact packing 'item'.\");\n await packing.transition(id, 'packed');\n} else if (input.action === 'journal_entry') {\n if (!input.note) throw new Error(\"journal_entry needs 'note'.\");\n await journal.append({ kind: 'entry', note: input.note });\n} else if (input.action !== 'status' && input.action !== 'itinerary') {\n throw new Error(\n `Unknown action '${input.action}'. Use status, set_trip, plan_day, pack_add, pack_check, journal_entry, or itinerary.`,\n );\n}\n\nconst packItems = await packing.list();\nconst unpacked = packItems.filter((r) => r.status === 'unpacked').length;\n\ngezel.output({\n daysPlanned: Object.keys(trip.days).length,\n unpacked,\n summary:\n input.action === 'itinerary' ? renderItinerary(trip.days) : tripLine(trip, unpacked, Date.now()),\n});\n"
|
|
225
|
+
}
|
|
226
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
- Every trip day has one anchor and deliberate slack — a spine, not a cage.
|
|
2
|
+
- Bookings, confirmations, and addresses are recorded the moment they exist.
|
|
3
|
+
- Packing is a five-minute sweep the night before, not a panic.
|
|
4
|
+
- The journal gets a line a day; the trip ends, the record stays.
|
|
@@ -0,0 +1,368 @@
|
|
|
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>Trip planner</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: 680px; 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
|
+
.banner { border: 1px solid var(--accent); border-radius: 6px; padding: 0.45rem 0.7rem; font-size: 0.85rem; margin-bottom: 0.75rem; display: none; }
|
|
30
|
+
.banner.show { display: block; }
|
|
31
|
+
.note { margin: 0 0 0.85rem; padding: 0.5rem 0.65rem; border: 1px solid var(--track); border-radius: 6px; background: var(--card); color: var(--muted); font-size: 0.8rem; }
|
|
32
|
+
.cols { display: grid; grid-template-columns: 1.2fr 1fr; gap: 0.6rem; }
|
|
33
|
+
.panel { background: var(--card); border: 1px solid var(--track); border-radius: 10px; padding: 0.85rem 1rem; font-size: 0.9rem; }
|
|
34
|
+
.panel h2 { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); margin: 0 0 0.5rem; }
|
|
35
|
+
.day { display: grid; grid-template-columns: 6.5rem 1fr; gap: 0.6rem; padding: 0.4rem 0; }
|
|
36
|
+
.day .date { color: var(--muted); }
|
|
37
|
+
.day .date small { display: block; font-size: 0.8rem; }
|
|
38
|
+
.day .plan p { margin: 0; }
|
|
39
|
+
.day .plan p + p { color: var(--muted); }
|
|
40
|
+
.pack { list-style: none; margin: 0; padding: 0; }
|
|
41
|
+
.pack li { display: flex; gap: 0.5rem; align-items: center; min-height: 44px; margin: 0; cursor: pointer; }
|
|
42
|
+
.pack li.packed { color: var(--muted); text-decoration: line-through; cursor: default; }
|
|
43
|
+
.pack .box { display: inline-block; width: 0.85rem; height: 0.85rem; border: 1px solid var(--track); border-radius: 4px; flex: none; }
|
|
44
|
+
.pack li.packed .box { background: var(--good); border-color: var(--good); }
|
|
45
|
+
.journal li { color: var(--muted); margin: 0.25rem 0; }
|
|
46
|
+
.journal li strong { color: var(--ink); font-weight: 600; }
|
|
47
|
+
.empty { color: var(--muted); font-style: italic; }
|
|
48
|
+
@media (max-width: 640px) { .cols { grid-template-columns: 1fr; } }
|
|
49
|
+
.demo-banner {
|
|
50
|
+
display: flex;
|
|
51
|
+
align-items: center;
|
|
52
|
+
justify-content: space-between;
|
|
53
|
+
gap: 0.75rem;
|
|
54
|
+
margin: 0 0 0.85rem;
|
|
55
|
+
padding: 0.5rem 0.65rem;
|
|
56
|
+
border: 1px solid var(--accent);
|
|
57
|
+
border-radius: 6px;
|
|
58
|
+
background: color-mix(in srgb, var(--card) 88%, var(--accent));
|
|
59
|
+
color: var(--ink);
|
|
60
|
+
font-size: 0.8rem;
|
|
61
|
+
}
|
|
62
|
+
.demo-banner[hidden] { display: none; }
|
|
63
|
+
.demo-banner strong { font-weight: 700; }
|
|
64
|
+
.demo-reset {
|
|
65
|
+
flex: none;
|
|
66
|
+
padding: 0.25rem 0.5rem;
|
|
67
|
+
border: 1px solid var(--accent);
|
|
68
|
+
border-radius: 6px;
|
|
69
|
+
background: var(--card);
|
|
70
|
+
color: var(--ink);
|
|
71
|
+
cursor: pointer;
|
|
72
|
+
font: inherit;
|
|
73
|
+
}
|
|
74
|
+
.demo-reset:hover { border-color: var(--ink); }
|
|
75
|
+
</style>
|
|
76
|
+
</head>
|
|
77
|
+
<body>
|
|
78
|
+
<main>
|
|
79
|
+
<div class="demo-banner" data-gezel-demo-banner hidden>
|
|
80
|
+
<span><strong>Demo mode</strong> — sample data stays in this page.</span>
|
|
81
|
+
<button type="button" class="demo-reset" onclick="location.reload()">Reset demo</button>
|
|
82
|
+
</div>
|
|
83
|
+
<p class="note" id="browser-note" hidden>
|
|
84
|
+
Viewing from a browser tab — the trip stays live, but packing happens in the app's Output pane.
|
|
85
|
+
</p>
|
|
86
|
+
<div class="banner" id="banner"></div>
|
|
87
|
+
<h1 id="title">Trip planner</h1>
|
|
88
|
+
<p class="sub" id="sub">Loading the trip…</p>
|
|
89
|
+
<div class="cols">
|
|
90
|
+
<div class="panel">
|
|
91
|
+
<h2>Itinerary</h2>
|
|
92
|
+
<div id="days"><p class="empty">No days planned yet — sketch the spine with your Reisleider.</p></div>
|
|
93
|
+
<h2 style="margin-top: 0.9rem">Journal</h2>
|
|
94
|
+
<ul class="journal" id="journal" style="margin:0;padding-left:1.05rem"><li class="empty">A line a day, once you're on the road.</li></ul>
|
|
95
|
+
</div>
|
|
96
|
+
<div class="panel">
|
|
97
|
+
<h2>Packing <span id="packcount"></span></h2>
|
|
98
|
+
<ul class="pack" id="pack"><li class="empty">Nothing listed yet.</li></ul>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
</main>
|
|
102
|
+
<script>
|
|
103
|
+
/* gezel-page-demo-stub v1 */
|
|
104
|
+
/*
|
|
105
|
+
* The canonical demo stub for Gezel type pages. MIT License.
|
|
106
|
+
*
|
|
107
|
+
* Paste this file's bytes verbatim into a <script> block ahead of the
|
|
108
|
+
* page's own code, then call makeDemoGezel({ page, tools, data }) with the
|
|
109
|
+
* page's demo handlers. Served by Gezel, the real window.gezel is already
|
|
110
|
+
* injected and the stub steps aside; opened as a raw file (the live demos
|
|
111
|
+
* on gezelgilde.com), it installs a window.gezel whose tools and data are
|
|
112
|
+
* the page-supplied handler maps — one code path in every mode. `tools`
|
|
113
|
+
* and `data` map a tool name / declared read path to a function; data
|
|
114
|
+
* handlers back read() and list(), and url() returns the handler's value
|
|
115
|
+
* or passes a data: URI through. See docs/page-authoring.md.
|
|
116
|
+
*/
|
|
117
|
+
(function () {
|
|
118
|
+
'use strict';
|
|
119
|
+
window.makeDemoGezel = function (opts) {
|
|
120
|
+
if (window.gezel) return window.gezel;
|
|
121
|
+
var page = (opts && opts.page) || {};
|
|
122
|
+
var tools = (opts && opts.tools) || {};
|
|
123
|
+
var data = (opts && opts.data) || {};
|
|
124
|
+
var themeListeners = [];
|
|
125
|
+
var media = window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)') : null;
|
|
126
|
+
var ui = {
|
|
127
|
+
theme: { mode: media && media.matches ? 'dark' : 'light' },
|
|
128
|
+
onTheme: function (cb) {
|
|
129
|
+
themeListeners.push(cb);
|
|
130
|
+
return function () {
|
|
131
|
+
var at = themeListeners.indexOf(cb);
|
|
132
|
+
if (at >= 0) themeListeners.splice(at, 1);
|
|
133
|
+
};
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
if (media && media.addEventListener) {
|
|
137
|
+
media.addEventListener('change', function (ev) {
|
|
138
|
+
ui.theme = { mode: ev.matches ? 'dark' : 'light' };
|
|
139
|
+
themeListeners.slice().forEach(function (cb) { cb(ui.theme); });
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
function fail(code, message) {
|
|
143
|
+
var err = new Error(message);
|
|
144
|
+
err.code = code;
|
|
145
|
+
return Promise.reject(err);
|
|
146
|
+
}
|
|
147
|
+
function run(map, key, arg) {
|
|
148
|
+
if (!Object.prototype.hasOwnProperty.call(map, key)) return null;
|
|
149
|
+
return Promise.resolve().then(function () { return map[key](arg); });
|
|
150
|
+
}
|
|
151
|
+
window.gezel = {
|
|
152
|
+
page: {
|
|
153
|
+
api: 1,
|
|
154
|
+
projectId: 'demo',
|
|
155
|
+
source: 'type',
|
|
156
|
+
entry: page.entry || 'index.html',
|
|
157
|
+
typeName: page.typeName || 'demo',
|
|
158
|
+
params: page.params || {},
|
|
159
|
+
mode: 'demo',
|
|
160
|
+
},
|
|
161
|
+
tools: {
|
|
162
|
+
list: function () { return Promise.resolve(Object.keys(tools)); },
|
|
163
|
+
invoke: function (name, input) {
|
|
164
|
+
var call = run(tools, name, input || {});
|
|
165
|
+
if (!call) return fail('not-allowed', "The demo does not handle the tool '" + name + "'.");
|
|
166
|
+
return call.then(function (output) { return { output: output }; });
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
data: {
|
|
170
|
+
read: function (path, o) {
|
|
171
|
+
return run(data, path, o || {}) || fail('not-allowed', "The demo has no data for '" + path + "'.");
|
|
172
|
+
},
|
|
173
|
+
list: function (path, o) {
|
|
174
|
+
return run(data, path, o || {}) || Promise.resolve([]);
|
|
175
|
+
},
|
|
176
|
+
watch: function () { return function () {}; },
|
|
177
|
+
url: function (path) {
|
|
178
|
+
if (Object.prototype.hasOwnProperty.call(data, path)) return String(data[path]({}));
|
|
179
|
+
return typeof path === 'string' && path.indexOf('data:') === 0 ? path : '';
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
ui: ui,
|
|
183
|
+
refresh: function () {},
|
|
184
|
+
};
|
|
185
|
+
return window.gezel;
|
|
186
|
+
};
|
|
187
|
+
})();
|
|
188
|
+
</script>
|
|
189
|
+
<script>
|
|
190
|
+
(function () {
|
|
191
|
+
'use strict';
|
|
192
|
+
var busy = false;
|
|
193
|
+
|
|
194
|
+
// ── demo trip (used only when the page opens as a raw file) ──
|
|
195
|
+
var demoStart = new Date(Date.now() + 18 * 86400000);
|
|
196
|
+
var demoDate = function (offset) { return new Date(demoStart.getTime() + offset * 86400000).toISOString().slice(0, 10); };
|
|
197
|
+
var demoTrip = { destination: 'Lisbon', startDate: demoDate(0), travellers: '4', days: {} };
|
|
198
|
+
demoTrip.days[demoDate(0)] = 'Arrive, settle in, and walk the Alfama lanes.\nFado in the evening if you still have energy.';
|
|
199
|
+
demoTrip.days[demoDate(1)] = 'Markets, the tile museum, and a sunset miradouro.\nA slow lunch wherever looks good.';
|
|
200
|
+
demoTrip.days[demoDate(2)] = 'Day train to Sintra.\nBack in time for dinner, or stay for the sunset at Cabo da Roca.';
|
|
201
|
+
var demoPacking = { records: [
|
|
202
|
+
{ id: 'pack-1', status: 'packed', fields: { name: 'Passport and wallet' } },
|
|
203
|
+
{ id: 'pack-2', status: 'unpacked', fields: { name: 'Walking shoes' } },
|
|
204
|
+
{ id: 'pack-3', status: 'unpacked', fields: { name: 'Light rain jacket' } },
|
|
205
|
+
{ id: 'pack-4', status: 'unpacked', fields: { name: 'Phone charger' } },
|
|
206
|
+
] };
|
|
207
|
+
var demoJournal = { events: [
|
|
208
|
+
{ at: new Date().toISOString(), note: 'Reserved the Sintra train and palace entry.' },
|
|
209
|
+
{ at: new Date(Date.now() - 86400000).toISOString(), note: 'Saved three neighborhood dinner options.' },
|
|
210
|
+
] };
|
|
211
|
+
|
|
212
|
+
makeDemoGezel({
|
|
213
|
+
page: { entry: 'dashboard/index.html', typeName: 'Trip Planner', params: { destination: 'somewhere wonderful', start: '2026-12-01' } },
|
|
214
|
+
tools: {
|
|
215
|
+
mark_packed: function (input) {
|
|
216
|
+
var item = demoPacking.records.find(function (r) { return r.id === (input && input.id); });
|
|
217
|
+
if (!item) throw new Error("No packing item '" + (input && input.id) + "' in the demo list.");
|
|
218
|
+
item.status = 'packed';
|
|
219
|
+
return { summary: (item.fields.name || item.id) + ' packed.' };
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
data: {
|
|
223
|
+
'trip.json': function () { return demoTrip; },
|
|
224
|
+
'packing.json': function () { return demoPacking; },
|
|
225
|
+
'journal.json': function () { return demoJournal; },
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// ── one code path from here: window.gezel exists in every mode ──
|
|
230
|
+
var demoMode = gezel.page.mode === 'demo';
|
|
231
|
+
var browserMode = gezel.page.mode === 'browser';
|
|
232
|
+
|
|
233
|
+
function applyTheme(mode) {
|
|
234
|
+
document.documentElement.setAttribute('data-theme', mode === 'dark' ? 'dark' : 'light');
|
|
235
|
+
}
|
|
236
|
+
applyTheme(gezel.ui.theme.mode);
|
|
237
|
+
gezel.ui.onTheme(function (theme) { applyTheme(theme.mode); });
|
|
238
|
+
|
|
239
|
+
if (demoMode) document.querySelector('[data-gezel-demo-banner]').hidden = false;
|
|
240
|
+
if (browserMode) document.getElementById('browser-note').hidden = false;
|
|
241
|
+
|
|
242
|
+
function esc(s) { return String(s).replace(/[&<>"]/g, function (c) { return { '&': '&', '<': '<', '>': '>', '"': '"' }[c]; }); }
|
|
243
|
+
function setBanner(text) { var el = document.getElementById('banner'); el.textContent = text || ''; el.className = text ? 'banner show' : 'banner'; }
|
|
244
|
+
function invokeErrorText(err) {
|
|
245
|
+
if (err && err.code === 'unavailable') {
|
|
246
|
+
return "Packing happens in the app's Output pane — this browser tab stays view-only.";
|
|
247
|
+
}
|
|
248
|
+
return String(err && err.message ? err.message : err);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function plural(n, one, many) { return n + ' ' + (n === 1 ? one : many); }
|
|
252
|
+
|
|
253
|
+
// A plan written as "Anchor: … Options: … Slack: …" reads as plain
|
|
254
|
+
// lines without the labels; a plan with line breaks keeps them.
|
|
255
|
+
var PLAN_LABEL = /(?:^|\s)(?:anchor|options?|alternatives?|slack|free time)\s*:\s*/i;
|
|
256
|
+
var PLAN_LABELS = /(?:^|\s)(?:anchor|options?|alternatives?|slack|free time)\s*:\s*/gi;
|
|
257
|
+
function planLines(plan) {
|
|
258
|
+
var text = String(plan || '');
|
|
259
|
+
var lines = text.split(/\n+/);
|
|
260
|
+
if (lines.length === 1 && PLAN_LABEL.test(text)) lines = text.split(PLAN_LABELS);
|
|
261
|
+
return lines
|
|
262
|
+
.map(function (l) { return l.replace(PLAN_LABELS, ' ').replace(/^[\s*•-]+/, '').trim(); })
|
|
263
|
+
.filter(Boolean);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function isoDay(key) { return /^\d{4}-\d{2}-\d{2}$/.test(key); }
|
|
267
|
+
function dayNumber(key) { var m = /^day\s*(\d+)$/i.exec(key); return m ? Number(m[1]) : null; }
|
|
268
|
+
function longDate(iso) {
|
|
269
|
+
var when = new Date(iso + 'T12:00:00Z');
|
|
270
|
+
if (!isFinite(when.getTime())) return iso;
|
|
271
|
+
return when.toLocaleDateString(undefined, { weekday: 'short', day: 'numeric', month: 'short', timeZone: 'UTC' });
|
|
272
|
+
}
|
|
273
|
+
function sortDays(keys) {
|
|
274
|
+
return keys.slice().sort(function (a, b) {
|
|
275
|
+
var na = dayNumber(a), nb = dayNumber(b);
|
|
276
|
+
if (na !== null && nb !== null) return na - nb;
|
|
277
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
// "2026-12-03" reads as a date; "Day 3" stays "Day 3", with its
|
|
281
|
+
// date beneath once the departure date is known.
|
|
282
|
+
function dayLabel(key, startDate) {
|
|
283
|
+
if (isoDay(key)) return esc(longDate(key));
|
|
284
|
+
var n = dayNumber(key);
|
|
285
|
+
if (n === null) return esc(key);
|
|
286
|
+
var label = 'Day ' + n;
|
|
287
|
+
if (isoDay(String(startDate || '').slice(0, 10))) {
|
|
288
|
+
var when = new Date(String(startDate).slice(0, 10) + 'T12:00:00Z');
|
|
289
|
+
when.setUTCDate(when.getUTCDate() + n - 1);
|
|
290
|
+
return label + '<small>' + esc(longDate(when.toISOString().slice(0, 10))) + '</small>';
|
|
291
|
+
}
|
|
292
|
+
return label;
|
|
293
|
+
}
|
|
294
|
+
function travellersText(travellers) {
|
|
295
|
+
var text = String(travellers || '').trim();
|
|
296
|
+
if (!text) return '';
|
|
297
|
+
if (/^\d+$/.test(text)) return plural(Number(text), 'traveller', 'travellers');
|
|
298
|
+
return text.charAt(0).toUpperCase() + text.slice(1);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function render(trip, packing, journal) {
|
|
302
|
+
if (trip.destination) document.getElementById('title').textContent = 'Trip: ' + trip.destination;
|
|
303
|
+
var days = trip.days || {};
|
|
304
|
+
var dates = sortDays(Object.keys(days));
|
|
305
|
+
var parts = [];
|
|
306
|
+
var who = travellersText(trip.travellers);
|
|
307
|
+
if (who) parts.push(who);
|
|
308
|
+
var start = String(trip.startDate || '').trim();
|
|
309
|
+
if (isoDay(start.slice(0, 10))) {
|
|
310
|
+
var d = Math.ceil((Date.parse(start) - Date.now()) / 86400000);
|
|
311
|
+
if (isFinite(d)) parts.push(d > 0 ? plural(d, 'day', 'days') + ' until departure' : d === 0 ? 'Departure day!' : 'On the road');
|
|
312
|
+
} else if (start && start !== 'soon') {
|
|
313
|
+
parts.push('Leaving ' + start);
|
|
314
|
+
}
|
|
315
|
+
parts.push(dates.length ? plural(dates.length, 'day', 'days') + ' planned' : 'No days planned yet');
|
|
316
|
+
document.getElementById('sub').textContent = parts.join(' · ');
|
|
317
|
+
|
|
318
|
+
if (dates.length) {
|
|
319
|
+
document.getElementById('days').innerHTML = dates.map(function (d) {
|
|
320
|
+
var lines = planLines(days[d]).map(function (l) { return '<p>' + esc(l) + '</p>'; }).join('');
|
|
321
|
+
return '<div class="day"><span class="date">' + dayLabel(d, trip.startDate) + '</span><div class="plan">' + lines + '</div></div>';
|
|
322
|
+
}).join('');
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
var items = (packing && packing.records) || [];
|
|
326
|
+
var unpacked = items.filter(function (r) { return r.status === 'unpacked'; }).length;
|
|
327
|
+
document.getElementById('packcount').textContent = items.length ? '· ' + unpacked + ' to go' : '';
|
|
328
|
+
if (items.length) {
|
|
329
|
+
document.getElementById('pack').innerHTML = items.map(function (r) {
|
|
330
|
+
return '<li class="' + r.status + '" data-id="' + esc(r.id) + '"><span class="box"></span><span>' + esc((r.fields && r.fields.name) || r.id) + '</span></li>';
|
|
331
|
+
}).join('');
|
|
332
|
+
Array.prototype.forEach.call(document.querySelectorAll('.pack li.unpacked'), function (li) {
|
|
333
|
+
li.addEventListener('click', function () {
|
|
334
|
+
if (busy) return;
|
|
335
|
+
busy = true;
|
|
336
|
+
gezel.tools
|
|
337
|
+
.invoke('mark_packed', { id: li.getAttribute('data-id') })
|
|
338
|
+
.then(function () { setBanner(''); return loadTrip(); })
|
|
339
|
+
.catch(function (err) { setBanner(invokeErrorText(err)); })
|
|
340
|
+
.then(function () { busy = false; });
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
var entries = ((journal && journal.events) || []).slice(-4).reverse();
|
|
346
|
+
if (entries.length) {
|
|
347
|
+
document.getElementById('journal').innerHTML = entries.map(function (e) {
|
|
348
|
+
return '<li><strong>' + esc(String(e.at).slice(5, 10)) + '</strong> — ' + esc(e.note || '') + '</li>';
|
|
349
|
+
}).join('');
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function loadTrip() {
|
|
354
|
+
return Promise.all([
|
|
355
|
+
gezel.data.read('trip.json').catch(function () { return { days: {} }; }),
|
|
356
|
+
gezel.data.read('packing.json').catch(function () { return { records: [] }; }),
|
|
357
|
+
gezel.data.read('journal.json').catch(function () { return { events: [] }; }),
|
|
358
|
+
]).then(function (results) { render(results[0] || { days: {} }, results[1], results[2]); });
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
loadTrip();
|
|
362
|
+
gezel.data.watch('trip.json', function () { loadTrip(); });
|
|
363
|
+
gezel.data.watch('packing.json', function () { loadTrip(); });
|
|
364
|
+
gezel.data.watch('journal.json', function () { loadTrip(); });
|
|
365
|
+
})();
|
|
366
|
+
</script>
|
|
367
|
+
</body>
|
|
368
|
+
</html>
|