calendar
All repositories: gitoria
- Address
- https://calendar.gitoria.worldapi.org/
- Owner
- Caramboleyo
- Created
calendar.worldapi.org
The creator's calendar (CONCEPT.md), in Hybriel on hl:web. The month view at / — prev / next buttons on top, month name
and year between them, today marked, weeks start on Monday, names in the browser's language — and, since ticket #2,
login with ident and private events. https://calendar.worldapi.org
Use
- Signed out: the month view and "Log in with ident" (top right; the identity selector or the button).
- Signed in: click a day to add an event, click an event to edit or delete it. An event has a title, "At a time" (from / to, date and time) or "All day" (from / to, dates), a place and notes. It shows on every day it covers (a day shows four chips, the fourth says "+N more"). Only the owner sees, changes or deletes an event.
- Events are stored as WorldAPI Events (https://worldapi.org/standards/event):
$type $version $kind $title $description $start $end $timezone $location $status $visibility $created $updatedplusowner(users@id). Timed events: local wall-clock$start/$end+$timezone(the browser's); all-day:$kind = 'allday', date$start, exclusive date$end(left out for one day);$created/$updatedare UTC instants;$visibilityis alwaysprivate. - Short ids (ident#23, antcolony mission 039):
users.identityholds what ident's exchange answers — since ident#23 the identity's public 5-character short id (a68sz), before that the per-app id (32 hex);lib/util.hl isIdentIdaccepts both (the oldisHexcheck refused short ids). The switch: one-offtools/migrate-short-ids.hl(old → short id, idempotent, neverfinish), gatenode tests/short-id-switch.mjs(ports 8722/8723, no browser), runbookantcolony-docs/docs/short-id-switch.md(Byrodin:/CONTAINERS/projects/antcolony/docs/short-id-switch.mdonce synced).
Reminders and notifications (ticket #4)
- Reminder settings (button under the month, signed in): the calendar's default reminders — 1 hour before and the day before at 20:00 until changed. Every event without reminders of its own gets them. In an event, "Calendar's" / "Own" chooses; "Own" is a list of rows (N minutes before, N hours before, N days before at HH:MM), also none at all.
- Stored as WorldAPI Event
$reminders({ $before: 'PT1H' },{ $days_before: 1, $time: '20:00' }); no$reminders= inherit; the defaults are the Calendar's$remindersinstorage/mpackdb/settings.db(one row per owner). - The page keeps the events of the next days with the reminders that apply (
soon, part of the page state — the offline layer stores it) and sets one browser timer per reminder of the next 7 days (arm(); set again after every save / delete / paging and every 10 minutes of use). A timer shows the notification (hl:webnotify, through the service worker). "Turn on notifications" is the click the browser wants before it allows them. - Offline: the timers need no network, and the offline page has
soon. An event saved while the server cannot be reached shows at once, its reminders are set, and the save waits in hl:web's emit queue until the connection is back. - Limit (Hybriel): a timer lives while the app (or its tab) is open; a fully closed app cannot be woken without a server push (later,
via ident's push notifications, ident#8).
notifyAtcannot cancel, soarm()usessetTimeoutdirectly and callsnotify. - Code:
components/month-view/reminders.hl(rows ↔$reminders, describe, time-zone instants, the plan) shared bylib/settings.hl(settings table),lib/events.hl(upcoming) andmonth-view.hl(editor, timers).
Layout
Code order (antcolony docs/code-order.md, calendar mission 001): project.hl is the map (config, routes, audience, an
index of which file handles what); one lib/ file per topic holds its central logic, lib/<topic>-helpers.hl what it
looks up; the function route and the faces are thin wrappers. let only for a variable that is reassigned (or re-bound
in a loop body). Imports go one way: util ← users, settings, events-helpers ← events ← api-helpers ← api ← project.hl.
| file | what |
|---|---|
project.hl | the map: index comment, PWA config, routes, audience, server |
lib/events.hl | events table (storage/mpackdb/events.db, index owner) and every write to it (saveEvent / createEvent / changeEvent / deleteEvent), eventsBetween, upcoming, soonOf, the faces' answers calendarView / settingsView |
lib/events-helpers.hl | build (page fields → WorldAPI Event, strict validation), viewOf, isLocalId, validWindow, byStart, size limits |
lib/settings.hl | settings table (storage/mpackdb/settings.db, unique owner): the default reminders (defaultsOf, saveDefaults) |
lib/users.hl | users table (ident identity → user @id), ident config + exchange, userOfLoginCode (both logins), userIdOfSession, tagOf |
lib/api.hl | /login/callback (+ loginFailed; take &req, &sessions) |
lib/api-helpers.hl | safePath (the login's ?next=) |
lib/util.hl | envOr, storageDir, countOfList, firstOf, isHex, isIdentId |
components/main.hl | the shell with the login, faces calendarLogin / calendarLogOut |
components/calendar.hl | the page / and the faces calLoad / calSave / calDelete / calSettingsLoad / calSettingsSave / calSaveQueued |
components/loginfailed.hl | /login/failed |
components/styles.hl | all CSS (tokens from shared/tokens.hl, accent = light blue) |
tools/migrate-short-ids.hl | one-off ident#23 migration |
tests/browser.mjs, tests/short-id-switch.mjs | the gates (below) |
tests/realdata-baseline.mjs, tests/realdata-compare.py | same-output check of two code trees on a live-data copy (below) |
tests/letcount.py | code-order counts: root .hl files, project.hl lines, lets (-v lists any that should be plain) |
components/month-view/the month view, vendored from components.hybriel.worldapi.org and EXTENDED here (events on the days, the editor;dates.hl= date helpers shared with the server; it carries its ownStyle). Re-vendoring needs these changes carried over.login.jsident selector ↔ shell bridge (the only hand-written JavaScript). The month view reads the browser's today / language / time zone itself (HybrielDate/Intlin a handler, on the first pointer / focus / touch), nomonth-view.js.- PWA (ticket #3): installable and offline through hl:web's own support —
appIcons,appThemeColor,offline = [ Calendar ]in project.hl (manifest link in the head,/__hl/manifest.webmanifest,/__hl/sw.js); icons inicons/. No JavaScript of ours. bin/hybriel+plugins/(core crypto data fetch fs http http1 mpackdb proc time web) are vendored = hybriel master 06617221 (2026-10-03, antcolony mission 074: plugin allocators 3a781359 + 413f60e4 (#126, plugins allocate with malloc via plugin_api.zig), mpackdb 2cb7ae5e, http1 773de63e, f0ac2d2d (plugin ABI field — bin and .so must match); no lambda semantics change), sha25621059cc7…d77bdb, built the same way fromgit archive 06617221(~/scratch-074/src, removed); previous copy (190aa11d) in.scratch/pre-074/. Gates:CALENDAR_GATE_PORT=8760 CALENDAR_GATE_IDENT_PORT=8761 CALENDAR_GATE_CHROME=8762-8769 node tests/browser.mjs78/0,CALENDAR_SWITCH_IDENT_PORT=8760 CALENDAR_SWITCH_PORT=8761 node tests/short-id-switch.mjs18/0. Memory:LOADS=N PAGES=N node .scratch/w074/memtest.mjs <app dir> <mpackdb copy> <label> 0muh8y8myb5z(port 8760, Chrome 8761–8769; RSS marks every 250): 1000 loads + 1000 pagings new 194 → 214 → 218 MB, old 190aa11d 194 → 227 → 231 MB. Before: master 190aa11d (2026-10-02, antcolony mission 072: fc838894 GC correctness (string index / plugin error read freed memory), 038d84b3 #126 returned closure scopes, #127 d98926c6/04df4428; no lambda semantics change since 8efba065), sha256860f5e61…0a23626, built read-only fromgit archive 190aa11d; previous copy (8efba065) in.scratch/pre-072/. Gates:CALENDAR_GATE_PORT=8720 CALENDAR_GATE_IDENT_PORT=8721 CALENDAR_GATE_CHROME=8722-8729 node tests/browser.mjs78/0,CALENDAR_SWITCH_IDENT_PORT=8730 CALENDAR_SWITCH_PORT=8731 node tests/short-id-switch.mjs18/0. Memory:.scratch/w072/memtest.mjs(port 8720, Chrome 8721-8728, same args): 200+200 new 194 → 214 → 215 MB (old 283 → 368 → 376), 1000+1000 new 229 → 233 MB — flat. Before: master 8efba065 (2026-10-02, antcolony mission 069: #126 memory freed by bytes too, #48 lambda parameters COPY; earlier ff51cf46 #113, #115/#116, #118, client SVG namespace, hl:markdown #111; #103–#106, #94, #83, #82, #95–#97). Built from a read-onlygit archive master(zig build -Doptimize=ReleaseFast -Dtarget=x86_64-linux-gnu.2.39innative/, zig = hybriel'snative/zig-toolchainlink), sha25656b108f3…f4f201ea. No local patch: re-vendor = copy binary + the plugins above, run both gates. Previous copy (ff51cf46):.scratch/pre-069/.- Lambdas copy their arguments (hybriel #48):
f = (x) => { x.n = 1 }changes a COPY; a lambda that must change the caller's hybrid/list takes&x. Audit 2026-10-02 (python3 .scratch/w069/lambdas.py/lambdas2.py, 64 lambdas): none writes a parameter the caller reads —project.hlfailed/loginCallback savereq.sessionthemselves, the rest build new values. WRONG forfailed(req, why)with a cookie: its write was lost ("the login did not finish" instead of the reason) — fixed in calendar mission 001 by&req/&sessions(lib/api.hl). Probe:bin/hybriel .scratch/w069/probe48.hl. New lambdas: use&when the caller relies on the change. - Memory check (#126):
node .scratch/w069/memtest.mjs <app copy> <mpackdb copy> <label> <users @id>(port 8730, Chrome 8722-8729; env LOADS/PAGES): RSS at boot, after N signed-in page loads, after N month pagings (calLoad), after 5 s idle. validWindow/soonOf(were plain members ofcomponents/calendar.hlsince antcolony mission 048) are statics inlib/events-helpers.hl/lib/events.hlsince calendar mission 001; the gate checks both (bad window refused,soon).- Language of the first render (calendar#6):
components/calendar.hldeclaresheaders = null(hybriel #105) and handsheaders['accept-language']toMonthView { acceptLanguage = requestLanguage };names.hlknows en de fr es it nl pt, any other → neutral names until the browser fills in its own. Names written by the server stay while paging. - Reminder timers:
arm()creates thesetTimeoutclosure inline in itswhileloop —let pis a new binding per pass (hybriel #106). On an older Hybriel the closures sharedpand only the last reminder fired (antcolony mission 037 proved it: gate on 13ef4f9b → "the notifications came on time" FAIL, only "Has its own reminder" came).
Run, test, deploy
CALENDAR_PORT=8700 ./bin/hybriel project.hl(env:CALENDAR_WATCH=0no watcher,HL_HOST,CALENDAR_SESSIONS,CALENDAR_STORAGE,CALENDAR_PUBLIC_URLdefault https://calendar.worldapi.org,IDENT_URL,IDENT_EXCHANGE_URL,IDENT_API_KEY,IDENT_API_SECRET—.envbesideproject.hl, never read by workers). A login needs the ident key.node tests/browser.mjs— the gate: own ident (a copy without.env), own server + storage, headless Chrome (ports 8700 app, 8701 ident, 8702–8709 Chrome;CALENDAR_GATE_PORT,CALENDAR_GATE_IDENT_PORT,CALENDAR_GATE_CHROME). Second gate:node tests/short-id-switch.mjs(ports 8722/8723;CALENDAR_SWITCH_IDENT_PORT,CALENDAR_SWITCH_PORT).GATE_SKIP_EVENTS=1skips the events part (40 checks run instead of 76, ~1.5 min instead of ~4). The language checks (languagePart) open a tab per language with CDPNetwork.setExtraHTTPHeadersAccept-Language de / en and the browser's own language (Intl locale + navigator.language) set to the OTHER one; first page, server HTML, next/next/prev. Known flake: one run in a few times out on "next month" (a loaded machine; see STATUS 2026-09-26) — run it again.python3 tests/letcount.py . [-v]— code order: must saynever reassigned 0, root .hl files 1.- Same output (for a cleanup that must not change behaviour; calendar mission 001): a copy of the live tables, then
the old tree and the new tree each answer the pages (signed in as az5b2 = users
0muh8y8myb5z, and signed out; de/fr), the browser modules,calLoadfor every month with a live event, the login route/faces against a fake ident, and a fixed write sequence through the faces (create appointment / all-day / overnight / own reminders, 18 refusals, edit, offline queue with alocal-id, delete, a live event changed and deleted, the defaults, logins, logout); then:
tar -C /CONTAINERS/projects/calendar.worldapi.org -cf - storage/mpackdb | ssh loreana tar -C <repo>/.scratch/realdata -xf - # on Byrodin
git archive <old commit> | tar -C .scratch/old -xf - && cp -a bin plugins .scratch/old/
node tests/realdata-baseline.mjs .scratch/old 8762 .scratch/base-old # app on 8762, fake ident on 8767 (port + 5)
node tests/realdata-baseline.mjs . 8762 .scratch/base-new
python3 tests/realdata-compare.py .scratch/base-old .scratch/base-new -v # exit 0 = the sameIt masks only source positions, ?v= / hl-shell- hashes, request times, the Host port and (writes) new ids, ms stamps
and login tags; browser modules count as the same when their own code and the imported functions they use are. Two
runs of the same tree compare clean. Run both trees on the same UTC day (soon follows the server's today). Delete
the live copy and the outputs afterwards.
./deploy.shon Loreana (architect): gate → backup → rsync →docker compose up -d --buildon Byrodin (127.0.0.1:45005).
First deploy of ticket #2 (architect)
Register the app in ident with the origin https://calendar.worldapi.org and put IDENT_API_KEY / IDENT_API_SECRET
into .env on Byrodin (as notes does), otherwise the login button has no key.
History and worker briefs
LOG.md— append-only history, one dated line per step (moved here from the antcolony LOG on 2026-10-01).missions/NNN-*.md— worker briefs for this app;reports/NNN-*.md— their reports (same name). Numbered per project since 2026-10-01 (antcolony#40); older text, code comments and commits use the old antcolony numbers → map:/media/STORAGE/projects/antcolony-docs/docs/mission-map.md(Byrodin:/CONTAINERS/projects/antcolony/docs/mission-map.md).