calendar
All repositories: gitoria
14.3 KB
# calendar.worldapi.orgThe creator's calendar (CONCEPT.md), in Hybriel on hl:web. The month view at `/` — prev / next buttons on top, month nameand 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 fourchips, 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`). Timedevents: local wall-clock `$start`/`$end` + `$timezone` (the browser's); all-day: `$kind = 'allday'`, date `$start`, exclusivedate `$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'spublic 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 at20:00 until changed. Every event without reminders of its own gets them. In an event, "Calendar's" / "Own" chooses; "Own" is a listof 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; thedefaults 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 minutesof use). A timer shows the notification (hl:web `notify`, through the service worker). "Turn on notifications" is the click the browserwants 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` (settingstable), `lib/events.hl` (`upcoming`) and `month-view.hl` (editor, timers).## LayoutCode order (antcolony `docs/code-order.md`, calendar mission 001): `project.hl` is the map (config, routes, audience, anindex of which file handles what); one `lib/` file per topic holds its central logic, `lib/<topic>-helpers.hl` what itlooks up; the function route and the faces are thin wrappers. `let` only for a variable that is reassigned (or re-boundin 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, `let`s (`-v` lists any that should be plain) |- `components/month-view/` the month view, vendored from components.hybriel.worldapi.org and EXTENDED here (events on thedays, 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 **master06617221** (2026-10-03, antcolony mission 074: plugin allocators 3a781359 + 413f60e4 (#126, plugins allocate with malloc viaplugin_api.zig), mpackdb 2cb7ae5e, http1 773de63e, f0ac2d2d (plugin ABI field — bin and .so must match); no lambda semanticschange), 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-8769node 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, Chrome8761–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 aread-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 + theplugins 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 thecaller's hybrid/list takes `&x`. Audit 2026-10-02 (`python3 .scratch/w069/lambdas.py` / `lambdas2.py`, 64 lambdas): nonewrites a parameter the caller reads — `project.hl` failed/loginCallback save `req.session` themselves, the rest build newvalues. WRONG for `failed(req, why)` with a cookie: its write was lost ("the login did not finish" instead of thereason) — 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, Chrome8722-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, thenthe 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 afixed 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 Byrodingit 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-newpython3 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 stampsand login tags; browser modules count as the same when their own code and the imported functions they use are. Tworuns of the same tree compare clean. Run both trees on the same UTC day (`soon` follows the server's today). Deletethe 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 perproject 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`).
Branches
- mainmain branch
Latest commits
- 2881119fcalendar mission 001 (4/4): code order — README layout + same-output test, STATUS, LOG, report; tests/letcount.py (sub-folders), tests/realdata-baseline.mjs, tests/realdata-compare.pymre
- 3b2a4cd0calendar mission 001 (3/4): code order — let only where reassigned (147 dropped incl. components/month-view; 110 left: 67 reassigned, 43 loop-bound); gates 78/0 (3 of 4 runs; 1 known 'next month' flake) + 18/0, live-data run = step 2mre
- f5ce6b1dcalendar mission 001 (2/4): code order — topics, map, thin faces: lib/util.hl, lib/events(-helpers).hl (+calendarView/settingsView/soonOf), lib/settings.hl, lib/users.hl (+userOfLoginCode, tagOf), lib/api(-helpers).hl; project.hl = map; login route takes &req/&sessions (failed-login reason now kept); gates 78/0 + 18/0mre
- c6fbd011calendar mission 001 (1/4): code order — files moved: lib/events.hl, lib/users.hl, components/styles.hl (imports only); gates 78/0 + 18/0, live-data run identicalmre
- d6c59290calendar: Hybriel master 06617221 (plugin allocators 3a781359 + 413f60e4, mpackdb 2cb7ae5e, http1 773de63e); gates 78/0 + 18/0mre
- 7bd0337ccalendar: Hybriel master 190aa11d (fc838894 GC correctness, #126 closure scopes, #127); gates 78/0 + 18/0mre
- ff41310ccalendar: Hybriel master 8efba065 (#126 memory, #48 lambda copy; audit: no & needed)mre
- 14ba08c7antcolony#40: mission references point to the moved missionsmre
- 99c73346antcolony#40: history (LOG.md), worker briefs (missions/) and reports moved here from antcolony, numbered per project; old numbers in antcolony docs/mission-map.mdmre
- 76edaa62calendar: Hybriel master ff51cf46 (re-vendor round, static workaround removed)mre
- 90a3fc2cdeploy.sh: back up live storage/.sessions/.env before every deploy (newest 5 kept)mre
- 6722b72ddeploy.sh: never send .git or .gitignore to Byrodinmre
- be099807State of 2026-09-27, before the move to gitoriamre