gitoriaLog in with ident

calendar

All repositories: gitoria

ReadmeCodePull requestsReleasesTicketsSettings
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 $updated plus owner (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/$updated are UTC instants; $visibility is always private.
  • Short ids (ident#23, antcolony mission 039): users.identity holds 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 isIdentId accepts both (the old isHex check refused short ids). The switch: one-off tools/migrate-short-ids.hl (old → short id, idempotent, never finish), gate node tests/short-id-switch.mjs (ports 8722/8723, no browser), runbook antcolony-docs/docs/short-id-switch.md (Byrodin: /CONTAINERS/projects/antcolony/docs/short-id-switch.md once 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 $reminders in storage/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:web notify, 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). notifyAt cannot cancel, so arm() uses setTimeout directly and calls notify.
  • Code: components/month-view/reminders.hl (rows ↔ $reminders, describe, time-zone instants, the plan) shared by lib/settings.hl (settings table), lib/events.hl (upcoming) and month-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.

filewhat
project.hlthe map: index comment, PWA config, routes, audience, server
lib/events.hlevents 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.hlbuild (page fields → WorldAPI Event, strict validation), viewOf, isLocalId, validWindow, byStart, size limits
lib/settings.hlsettings table (storage/mpackdb/settings.db, unique owner): the default reminders (defaultsOf, saveDefaults)
lib/users.hlusers 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.hlsafePath (the login's ?next=)
lib/util.hlenvOr, storageDir, countOfList, firstOf, isHex, isIdentId
components/main.hlthe shell with the login, faces calendarLogin / calendarLogOut
components/calendar.hlthe page / and the faces calLoad / calSave / calDelete / calSettingsLoad / calSettingsSave / calSaveQueued
components/loginfailed.hl/login/failed
components/styles.hlall CSS (tokens from shared/tokens.hl, accent = light blue)
tools/migrate-short-ids.hlone-off ident#23 migration
tests/browser.mjs, tests/short-id-switch.mjsthe gates (below)
tests/realdata-baseline.mjs, tests/realdata-compare.pysame-output check of two code trees on a live-data copy (below)
tests/letcount.pycode-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 own Style). Re-vendoring needs these changes carried over.
  • login.js ident selector ↔ shell bridge (the only hand-written JavaScript). The month view reads the browser's today / language / time zone itself (Hybriel Date / Intl in a handler, on the first pointer / focus / touch), no month-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 in icons/. 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), sha256 21059cc7…d77bdb, built the same way from git 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.mjs 78/0, CALENDAR_SWITCH_IDENT_PORT=8760 CALENDAR_SWITCH_PORT=8761 node tests/short-id-switch.mjs 18/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), sha256 860f5e61…0a23626, built read-only from git 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.mjs 78/0, CALENDAR_SWITCH_IDENT_PORT=8730 CALENDAR_SWITCH_PORT=8731 node tests/short-id-switch.mjs 18/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-only git archive master (zig build -Doptimize=ReleaseFast -Dtarget=x86_64-linux-gnu.2.39 in native/, zig = hybriel's native/zig-toolchain link), sha256 56b108f3…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.hl failed/loginCallback save req.session themselves, the rest build new values. WRONG for failed(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 of components/calendar.hl since antcolony mission 048) are statics in lib/events-helpers.hl / lib/events.hl since calendar mission 001; the gate checks both (bad window refused, soon).
  • Language of the first render (calendar#6): components/calendar.hl declares headers = null (hybriel #105) and hands headers['accept-language'] to MonthView { acceptLanguage = requestLanguage }; names.hl knows 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 the setTimeout closure inline in its while loop — let p is a new binding per pass (hybriel #106). On an older Hybriel the closures shared p and 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=0 no watcher, HL_HOST, CALENDAR_SESSIONS, CALENDAR_STORAGE, CALENDAR_PUBLIC_URL default https://calendar.worldapi.org, IDENT_URL, IDENT_EXCHANGE_URL, IDENT_API_KEY, IDENT_API_SECRET — .env beside project.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=1 skips 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 CDP Network.setExtraHTTPHeaders Accept-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 say never 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, calLoad for 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 a local- 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 same

It 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.sh on Loreana (architect): gate → backup → rsync → docker compose up -d --build on 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).