gitoriaLog in with ident

calendar

All repositories: gitoria

ReadmeCodePull requestsReleasesTicketsSettings
Branchmain2881119fcalendar 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.pymremain/README.md

14.3 KB

  1. # calendar.worldapi.org
  2. The creator's calendar (CONCEPT.md), in Hybriel on hl:web. The month view at `/` — prev / next buttons on top, month name
  3. and year between them, today marked, weeks start on Monday, names in the browser's language — and, since ticket #2,
  4. **login with ident and private events**. https://calendar.worldapi.org
  5. ## Use
  6. - Signed out: the month view and "Log in with ident" (top right; the identity selector or the button).
  7. - 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 /
  8. 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
  9. chips, the fourth says "+N more"). Only the owner sees, changes or deletes an event.
  10. - Events are stored as **WorldAPI Events** (https://worldapi.org/standards/event): `$type $version $kind $title
  11. $description $start $end $timezone $location $status $visibility $created $updated` plus `owner` (users `@id`). Timed
  12. events: local wall-clock `$start`/`$end` + `$timezone` (the browser's); all-day: `$kind = 'allday'`, date `$start`, exclusive
  13. date `$end` (left out for one day); `$created`/`$updated` are UTC instants; `$visibility` is always `private`.
  14. - **Short ids (ident#23, antcolony mission 039)**: `users.identity` holds what ident's exchange answers — since ident#23 the identity's
  15. public 5-character short id (`a68sz`), before that the per-app id (32 hex); `lib/util.hl isIdentId` accepts both (the old
  16. `isHex` check refused short ids). The switch: one-off `tools/migrate-short-ids.hl` (old → short id, idempotent, never
  17. `finish`), gate `node tests/short-id-switch.mjs` (ports 8722/8723, no browser), runbook
  18. `antcolony-docs/docs/short-id-switch.md` (Byrodin: `/CONTAINERS/projects/antcolony/docs/short-id-switch.md` once synced).
  19. ## Reminders and notifications (ticket #4)
  20. - **Reminder settings** (button under the month, signed in): the calendar's default reminders — 1 hour before and the day before at
  21. 20:00 until changed. Every event without reminders of its own gets them. In an event, "Calendar's" / "Own" chooses; "Own" is a list
  22. of rows (N minutes before, N hours before, N days before at HH:MM), also none at all.
  23. - Stored as WorldAPI Event `$reminders` (`{ $before: 'PT1H' }`, `{ $days_before: 1, $time: '20:00' }`); no `$reminders` = inherit; the
  24. defaults are the Calendar's `$reminders` in `storage/mpackdb/settings.db` (one row per owner).
  25. - 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)
  26. and sets one browser timer per reminder of the next 7 days (`arm()`; set again after every save / delete / paging and every 10 minutes
  27. of use). A timer shows the notification (hl:web `notify`, through the service worker). "Turn on notifications" is the click the browser
  28. wants before it allows them.
  29. - **Offline:** the timers need no network, and the offline page has `soon`. An event saved while the server cannot be reached shows at once,
  30. its reminders are set, and the save waits in hl:web's emit queue until the connection is back.
  31. - **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,
  32. via ident's push notifications, ident#8). `notifyAt` cannot cancel, so `arm()` uses `setTimeout` directly and calls `notify`.
  33. - Code: `components/month-view/reminders.hl` (rows ↔ `$reminders`, describe, time-zone instants, the plan) shared by `lib/settings.hl` (settings
  34. table), `lib/events.hl` (`upcoming`) and `month-view.hl` (editor, timers).
  35. ## Layout
  36. Code order (antcolony `docs/code-order.md`, calendar mission 001): `project.hl` is the map (config, routes, audience, an
  37. index of which file handles what); one `lib/` file per topic holds its central logic, `lib/<topic>-helpers.hl` what it
  38. looks up; the function route and the faces are thin wrappers. `let` only for a variable that is reassigned (or re-bound
  39. in a loop body). Imports go one way: util ← users, settings, events-helpers ← events ← api-helpers ← api ← project.hl.
  40. | file | what |
  41. |---|---|
  42. | `project.hl` | the map: index comment, PWA config, routes, audience, server |
  43. | `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` |
  44. | `lib/events-helpers.hl` | `build` (page fields → WorldAPI Event, strict validation), `viewOf`, `isLocalId`, `validWindow`, `byStart`, size limits |
  45. | `lib/settings.hl` | `settings` table (`storage/mpackdb/settings.db`, unique `owner`): the default reminders (`defaultsOf`, `saveDefaults`) |
  46. | `lib/users.hl` | `users` table (ident identity → user `@id`), ident config + exchange, `userOfLoginCode` (both logins), `userIdOfSession`, `tagOf` |
  47. | `lib/api.hl` | `/login/callback` (+ `loginFailed`; take `&req`, `&sessions`) |
  48. | `lib/api-helpers.hl` | `safePath` (the login's `?next=`) |
  49. | `lib/util.hl` | `envOr`, `storageDir`, `countOfList`, `firstOf`, `isHex`, `isIdentId` |
  50. | `components/main.hl` | the shell with the login, faces `calendarLogin` / `calendarLogOut` |
  51. | `components/calendar.hl` | the page `/` and the faces `calLoad` / `calSave` / `calDelete` / `calSettingsLoad` / `calSettingsSave` / `calSaveQueued` |
  52. | `components/loginfailed.hl` | `/login/failed` |
  53. | `components/styles.hl` | all CSS (tokens from `shared/tokens.hl`, accent = light blue) |
  54. | `tools/migrate-short-ids.hl` | one-off ident#23 migration |
  55. | `tests/browser.mjs`, `tests/short-id-switch.mjs` | the gates (below) |
  56. | `tests/realdata-baseline.mjs`, `tests/realdata-compare.py` | same-output check of two code trees on a live-data copy (below) |
  57. | `tests/letcount.py` | code-order counts: root `.hl` files, project.hl lines, `let`s (`-v` lists any that should be plain) |
  58. - `components/month-view/` the month view, vendored from components.hybriel.worldapi.org and EXTENDED here (events on the
  59. days, the editor; `dates.hl` = date helpers shared with the server; it carries its own `Style`). Re-vendoring needs these changes carried over.
  60. - `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`.
  61. - 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.
  62. - `bin/hybriel` + `plugins/` (core crypto data fetch fs http http1 mpackdb proc time web) are vendored = hybriel **master
  63. 06617221** (2026-10-03, antcolony mission 074: plugin allocators 3a781359 + 413f60e4 (#126, plugins allocate with malloc via
  64. plugin_api.zig), mpackdb 2cb7ae5e, http1 773de63e, f0ac2d2d (plugin ABI field — bin and .so must match); no lambda semantics
  65. change), sha256 `21059cc7…d77bdb`, built the same way from `git archive 06617221` (`~/scratch-074/src`, removed); previous copy
  66. (190aa11d) in `.scratch/pre-074/`. Gates: `CALENDAR_GATE_PORT=8760 CALENDAR_GATE_IDENT_PORT=8761 CALENDAR_GATE_CHROME=8762-8769
  67. node tests/browser.mjs` 78/0, `CALENDAR_SWITCH_IDENT_PORT=8760 CALENDAR_SWITCH_PORT=8761 node tests/short-id-switch.mjs` 18/0.
  68. Memory: `LOADS=N PAGES=N node .scratch/w074/memtest.mjs <app dir> <mpackdb copy> <label> 0muh8y8myb5z` (port 8760, Chrome
  69. 8761–8769; RSS marks every 250): 1000 loads + 1000 pagings new 194 → 214 → 218 MB, old 190aa11d 194 → 227 → 231 MB.
  70. Before: master 190aa11d (2026-10-02, antcolony mission 072: fc838894 GC correctness (string index / plugin error read freed memory),
  71. 038d84b3 #126 returned closure scopes, #127 d98926c6/04df4428; no lambda semantics change since 8efba065), sha256
  72. `860f5e61…0a23626`, built read-only from `git archive 190aa11d`; previous copy (8efba065) in `.scratch/pre-072/`. Gates:
  73. `CALENDAR_GATE_PORT=8720 CALENDAR_GATE_IDENT_PORT=8721 CALENDAR_GATE_CHROME=8722-8729 node tests/browser.mjs` 78/0,
  74. `CALENDAR_SWITCH_IDENT_PORT=8730 CALENDAR_SWITCH_PORT=8731 node tests/short-id-switch.mjs` 18/0. Memory:
  75. `.scratch/w072/memtest.mjs` (port 8720, Chrome 8721-8728, same args): 200+200 new 194 → 214 → 215 MB (old 283 → 368 → 376),
  76. 1000+1000 new 229 → 233 MB — flat.
  77. Before: master 8efba065 (2026-10-02, antcolony mission 069: #126 memory freed by bytes too, #48 lambda parameters COPY; earlier ff51cf46 #113,
  78. #115/#116, #118, client SVG namespace, hl:markdown #111; #103–#106, #94, #83, #82, #95–#97). Built from a
  79. read-only `git archive master` (`zig build -Doptimize=ReleaseFast -Dtarget=x86_64-linux-gnu.2.39` in `native/`, zig =
  80. hybriel's `native/zig-toolchain` link), sha256 `56b108f3…f4f201ea`. **No local patch**: re-vendor = copy binary + the
  81. plugins above, run both gates. Previous copy (ff51cf46): `.scratch/pre-069/`.
  82. - **Lambdas copy their arguments (hybriel #48)**: `f = (x) => { x.n = 1 }` changes a COPY; a lambda that must change the
  83. caller's hybrid/list takes `&x`. Audit 2026-10-02 (`python3 .scratch/w069/lambdas.py` / `lambdas2.py`, 64 lambdas): none
  84. writes a parameter the caller reads — `project.hl` failed/loginCallback save `req.session` themselves, the rest build new
  85. values. WRONG for `failed(req, why)` with a cookie: its write was lost ("the login did not finish" instead of the
  86. 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.
  87. - Memory check (#126): `node .scratch/w069/memtest.mjs <app copy> <mpackdb copy> <label> <users @id>` (port 8730, Chrome
  88. 8722-8729; env LOADS/PAGES): RSS at boot, after N signed-in page loads, after N month pagings (calLoad), after 5 s idle.
  89. - `validWindow` / `soonOf` (were plain members of `components/calendar.hl` since antcolony mission 048) are statics in
  90. `lib/events-helpers.hl` / `lib/events.hl` since calendar mission 001; the gate checks both (bad window refused, `soon`).
  91. - Language of the first render (calendar#6): `components/calendar.hl` declares `headers = null` (hybriel #105) and hands
  92. `headers['accept-language']` to `MonthView { acceptLanguage = requestLanguage }`; `names.hl` knows en de fr es it nl pt,
  93. any other → neutral names until the browser fills in its own. Names written by the server stay while paging.
  94. - Reminder timers: `arm()` creates the `setTimeout` closure inline in its `while` loop — `let p` is a new binding per pass
  95. (hybriel #106). On an older Hybriel the closures shared `p` and only the last reminder fired (antcolony mission 037 proved it:
  96. gate on 13ef4f9b → "the notifications came on time" FAIL, only "Has its own reminder" came).
  97. ## Run, test, deploy
  98. - `CALENDAR_PORT=8700 ./bin/hybriel project.hl` (env: `CALENDAR_WATCH=0` no watcher, `HL_HOST`, `CALENDAR_SESSIONS`,
  99. `CALENDAR_STORAGE`, `CALENDAR_PUBLIC_URL` default https://calendar.worldapi.org, `IDENT_URL`, `IDENT_EXCHANGE_URL`,
  100. `IDENT_API_KEY`, `IDENT_API_SECRET` — `.env` beside `project.hl`, never read by workers). A login needs the ident key.
  101. - `node tests/browser.mjs` — the gate: own ident (a copy without `.env`), own server + storage, headless Chrome
  102. (ports 8700 app, 8701 ident, 8702–8709 Chrome; `CALENDAR_GATE_PORT`, `CALENDAR_GATE_IDENT_PORT`, `CALENDAR_GATE_CHROME`).
  103. Second gate: `node tests/short-id-switch.mjs` (ports 8722/8723; `CALENDAR_SWITCH_IDENT_PORT`, `CALENDAR_SWITCH_PORT`).
  104. `GATE_SKIP_EVENTS=1` skips the events part (40 checks run instead of 76, ~1.5 min instead of ~4). The language checks (`languagePart`)
  105. open a tab per language with CDP `Network.setExtraHTTPHeaders` Accept-Language de / en and the browser's own language
  106. (Intl locale + navigator.language) set to the OTHER one; first page, server HTML, next/next/prev.
  107. Known flake: one run in a few times out on "next month" (a loaded machine; see STATUS 2026-09-26) — run it again.
  108. - `python3 tests/letcount.py . [-v]` — code order: must say `never reassigned 0`, root .hl files 1.
  109. - **Same output** (for a cleanup that must not change behaviour; calendar mission 001): a copy of the live tables, then
  110. the old tree and the new tree each answer the pages (signed in as az5b2 = users `0muh8y8myb5z`, and signed out; de/fr),
  111. the browser modules, `calLoad` for every month with a live event, the login route/faces against a fake ident, and a
  112. fixed write sequence through the faces (create appointment / all-day / overnight / own reminders, 18 refusals, edit,
  113. offline queue with a `local-` id, delete, a live event changed and deleted, the defaults, logins, logout); then:
  114. ```
  115. tar -C /CONTAINERS/projects/calendar.worldapi.org -cf - storage/mpackdb | ssh loreana tar -C <repo>/.scratch/realdata -xf - # on Byrodin
  116. git archive <old commit> | tar -C .scratch/old -xf - && cp -a bin plugins .scratch/old/
  117. node tests/realdata-baseline.mjs .scratch/old 8762 .scratch/base-old # app on 8762, fake ident on 8767 (port + 5)
  118. node tests/realdata-baseline.mjs . 8762 .scratch/base-new
  119. python3 tests/realdata-compare.py .scratch/base-old .scratch/base-new -v # exit 0 = the same
  120. ```
  121. It masks only source positions, `?v=` / `hl-shell-` hashes, request times, the Host port and (writes) new ids, ms stamps
  122. and login tags; browser modules count as the same when their own code and the imported functions they use are. Two
  123. runs of the same tree compare clean. Run both trees on the same UTC day (`soon` follows the server's today). Delete
  124. the live copy and the outputs afterwards.
  125. - `./deploy.sh` on Loreana (architect): gate → backup → rsync → `docker compose up -d --build` on Byrodin (127.0.0.1:45005).
  126. ## First deploy of ticket #2 (architect)
  127. Register the app in ident with the origin `https://calendar.worldapi.org` and put `IDENT_API_KEY` / `IDENT_API_SECRET`
  128. into `.env` on Byrodin (as notes does), otherwise the login button has no key.
  129. ## History and worker briefs
  130. - `LOG.md` — append-only history, one dated line per step (moved here from the antcolony LOG on 2026-10-01).
  131. - `missions/NNN-*.md` — worker briefs for this app; `reports/NNN-*.md` — their reports (same name). Numbered per
  132. project since 2026-10-01 (antcolony#40); older text, code comments and commits use the old antcolony numbers →
  133. 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