gitoriaLog in with ident

ident

All repositories: gitoria

ReadmeCodePull requestsReleasesTicketsSettings
Commitf8bdcbc2f8bdcbc2ident: Hybriel master 06617221 (plugin allocators 3a781359 + 413f60e4, mpackdb 2cb7ae5e, http1 773de63e); all gates greenmref8bdcbc2/STATUS.md

46.2 KB

  1. # STATUS
  2. ## 2026-10-03 — antcolony mission 074: re-vendor hybriel master 06617221 (plugin allocators 3a781359 + 413f60e4) — NOT deployed by me
  3. bin sha256 21059cc7…d77bdb; old vendor (190aa11d) in `.scratch/pre-074/`. No app code change. Gates: browser 152/0 (8760–8769);
  4. `node .scratch/w074/runall.mjs` apps 107/0, selector 99/0, migration 33/0, iplimit 39/0, notify 130/0, hardening 17/0,
  5. signoutall 6/0, shortid 19/0, invites 44/0. Real-data copy (deleted), 0mufd5afy0xm signed in, Chrome: 200 loads new 211 → 211 MB,
  6. old 211 → 217 MB (38 ms both); 2500 loads new 211 → 212 MB flat, old 211 → 246 MB. Load time rises 39 → 70 ms over 2500 loads on
  7. BOTH binaries in Chrome; curl over the same pages stays 7–9 ms, so it is the browser tab.
  8. ## 2026-10-02 — antcolony mission 072: re-vendor hybriel master 190aa11d (GC correctness fc838894, #127) — NOT deployed by me
  9. bin sha256 860f5e61…0a23626, plugins = master's (no patch); old vendor (8efba065) in `.scratch/pre-072/`. No app code change
  10. (no lambda semantics change since 8efba065). Gates: browser 152/0; runall (w072, 8720–8727) apps 107/0, selector 99/0,
  11. migration 33/0, iplimit 39/0, notify 130/0, hardening 17/0, signoutall 6/0, shortid 19/0, invites 44/0. Real-data memory
  12. (200 signed-in Chrome loads): RSS 211 → 216 MB (old 282 → 305 MB). Commands: README "Vendored Hybriel".
  13. ## 2026-10-02 — antcolony mission 069: re-vendor hybriel master 8efba065 (#126 memory, #48 lambda copy) — NOT deployed, awaiting architect
  14. bin sha256 ce4c7507…df4eb, plugins = master's (no patch); old vendor (73267707) in `.scratch/pre-069/`. #48 broke the
  15. session writes: 5 lambdas in `store.hl` + 2 in `selector.hl` now take `&session` (README "Vendored Hybriel" → lesson).
  16. Lambda audit: all 231 lambdas (`python3 .scratch/w069/lambdas.py`, `lambdas2.py`); only those 7 write a passed value the
  17. caller relies on (testapp `logIn(data…)` writes a copy but saves it itself — left). Proof: browser gate without the fix
  18. aborted at the code page; one-at-a-time reverts → beginSession aborts (24/1), dropPending 148/4, accountOfSession hardening 16/1.
  19. Gates (Loreana): `IDENT_GATE_PORT=8760 IDENT_GATE_SHORT_PORT=8761 IDENT_GATE_CHROME=8762-8769 node tests/browser.mjs` →
  20. 152/0; `node .scratch/w069/runall.mjs` → apps 107/0, selector 99/0, migration 33/0, iplimit 39/0, notify 130/0,
  21. hardening 17/0, signoutall 6/0, shortid 19/0, invites 44/0. Memory on a live-storage copy (deleted), 200 signed-in Chrome
  22. page loads (/, /apps, /inbox, /start): new RSS 311 → 354 MB, old binary 600 → 1287 MB.
  23. ## 2026-10-01 — antcolony mission 048: re-vendor hybriel master 73267707 (incl. #122 = 4850d798) — NOT deployed, awaiting architect
  24. bin sha256 55b85049…bdf4, plugins = master's (no patch); old vendor in `.scratch/pre-048/`. Fixes the re-vendor needed (README
  25. "Vendored Hybriel" → lessons): `project.hl codePage` → `server.page(m, s, host, headers)` (#105 signature; /code was empty);
  26. Home's pending address comes in as param `pending` from codePage (a session-reading member is re-synced after every face →
  27. the code form vanished after the 5th wrong code); `tests/oldstore-009.hl` explicit ids from 0 (#4: fresh *id tables count
  28. from 1). New "listed ONCE" checks (hybriel#121): identities (browser), apps ×2 (apps), invites (invites) — no list pushes rows.
  29. No #115/#116/#118 workarounds existed; no browser-built SVG. Offline /start stays data-free (19 PWA checks).
  30. Gates (Loreana): `IDENT_GATE_PORT=8750 IDENT_GATE_SHORT_PORT=8751 IDENT_GATE_CHROME=8752-8759 node tests/browser.mjs` →
  31. 152/0; `node .scratch/w048/runall.mjs` → apps 107/0, selector 99/0, migration 33/0, iplimit 39/0, notify 130/0,
  32. hardening 17/0, signoutall 6/0, shortid 19/0, invites 44/0. Live storage copy (deleted): 0 duplicate ids in all 13 tables
  33. (accounts 2, apps 5, connections 8, identities 5, requests 10, sends 6, otp 2, ipsends 1, others 0) and the 8 old ident-* tables.
  34. ## 2026-10-01 — antcolony mission 046: installable app (PWA) + own icons — NOT deployed, awaiting architect
  35. `project.hl`: `appThemeColor = darker.value` (header colour), `appBackgroundColor = dark.value`, `appFavicon`,
  36. `appTouchIcon`, `appIcons` (192/512 any + maskable), `offline = [ Start ]`, `appManifest = { start_url = '/start' }`, routes
  37. `/start`, `/favicon.ico` (real ICO, was `direct ""`), `/icons/*`, `/api/online` (204). New: `icons/` (key in orange; README
  38. "PWA" has the re-render commands), `components/start.hl`. `components/main.hl` + `styles.hl`: header note "Offline — …"
  39. from a CSS-animation-timed probe of `/api/online` (no JS file; README "PWA"); on `/start` an answer → `/`.
  40. NO PERSONAL DATA OFFLINE (architect): `/` is NOT kept (hl:web would cache its document as rendered for the signed-in user +
  41. its state in IndexedDB); only the data-free `/start` is. Offline `/` = hl:web's "Unavailable offline".
  42. Gate: `IDENT_GATE_PORT=8732 IDENT_GATE_SHORT_PORT=8733 IDENT_GATE_CHROME=8734-8739 node tests/browser.mjs` → 151 passed,
  43. 0 failed (132 old + 19 PWA incl. the scan of every cache entry + IndexedDB for the signed-in email/identity). Other gates,
  44. ports remapped into 8732-8739 by temporary copies (`node .scratch/w046-dev/runall.mjs [names]`): apps 105/0, selector 99/0,
  45. migration 33/0, iplimit 39/0, notify 130/0, hardening 17/0, signoutall 6/0, shortid 19/0, invites 43/0.
  46. Icon + screenshots: Loreana `/tmp/w046-ident.worldapi.org/`. Test offline for real: the TAB's CDP offline does not reach
  47. the service worker — cut off its own target too (gate `swOffline`).
  48. ## 2026-09-26 — ticket ident#23: one public short id per identity — NOT deployed, awaiting architect
  49. Every identity has a `shortId` (5 chars, e.g. `a68sz`; digits 2-9 + letters without i/l/o = 31 chars, 28.6M ids), the same
  50. in every app; `/api/exchange` answers it, `/api/notify` takes it (case-insensitive), the identity page shows it. Existing
  51. identities get theirs at first start (`store.hl backfillShortIds`; statics are lazy, so `project.hl` touches it). New
  52. `POST /api/migrate-ids {key, secret, finish?}` → old per-app id → short id; old ids keep working in notify until the app
  53. sends `finish: true`, which drops them. Connections no longer carry an id (`!appIdentity` index gone); invites store short ids
  54. (old entries still counted). README "One public short id", "Migration for the apps". Gates (remapped to 8700-8709 via
  55. `.scratch/rungates.py`): migration 33/0, apps 105/0, selector 98/0 (the one "failure" is the remap renaming a cookie name
  56. in the check itself), notify 130/0, browser 132/0, hardening 17/0, signoutall 6/0, iplimit 39/0, invites 43/0, new
  57. `tests/shortid.mjs` 19/0. Existing gates changed: "another app → another id" now means the SAME id.
  58. Not done: the apps themselves (tickets, gitoria, notes, calendar) still have to call migrate-ids and store short ids.
  59. antcolony Mission 039 (2026-09-26): each app has `tools/migrate-short-ids.hl` + `tests/short-id-switch.mjs`; runbook Loreana
  60. `antcolony-docs/docs/short-id-switch.md`. The pre-ident#23 build those gates start is frozen at `.scratch/pre-023-ident/`
  61. (hl:web, no invites — a copy of gitoria's gate ident). Apps must accept non-hex exchange answers (tickets does not yet).
  62. ## 2026-09-26 — ticket ident#22: invite service — NOT deployed, awaiting architect
  63. Apps invite people by link (README "Invites"): `invites.hl` (new), `project.hl` (4 API routes + `/invite/:token`),
  64. `apps.hl` (invites table, `invite` on a login request, app delete drops invites), `mail.hl` (`sendInvite`),
  65. `components/home.hl` (accept branch in `chooseIdentity`, "You are invited to" banner), `docker-compose.yml`
  66. (`IDENT_PUBLIC_URL`), testapp `/invites` + invite report on `/callback`. Gates: `tests/invites.mjs` 43 passed
  67. (new, real Chrome: new address, same link again, revoked, expired, signed-in one click, two uses, race, mail, app page);
  68. `tests/apps.mjs` 105 passed (ports remapped to 8702-8705). browser/selector/notify not re-run (no shared code changed but
  69. `requestOf` gained a field). Hybriel note: `import { a as b }` is not supported.
  70. ## 2026-09-26 — antcolony mission 036: on hybriel master 837fe120 (hl:web, no local patch) — NOT deployed, awaiting architect
  71. Applied antcolony mission 035's switch (`.scratch/035-wip/`, see its entry below) on master 837fe120 (has #103/#104 fixes
  72. d9aa12e7..0c285350; both 035 repros now pass: `~/scratch-036/repro/`). plugins/webex + both LOCAL PATCHes gone,
  73. `'hl:web'` imports, `sessionCookie = 'identsid'`, HL_HOST read by hl:web, `req.session` in function routes,
  74. `realSession()` → `session == null`, `Intl`, `a.id < b.id`, testapp `encodeURIComponent`. Gates: the 20 forged-session
  75. checks now accept hl:web's own refusal (ack `ok:false`, "the `session` parameter is filled by the server").
  76. Result: browser 132/0, apps 105/0, selector 99/0, migration 23/0, iplimit 39/0, notify 130/0, hardening 17/0,
  77. signoutall 6/0; no Chrome left. Served page: `/__hl/{hl-runtime.js,web/client.js,app.css}?v=e8cc399c151c6b67`,
  78. 0 unhashed refs, hashed = immutable, bare = no-cache; cookie `identsid; HttpOnly; SameSite=Lax` unchanged.
  79. Backup of the old state: `.scratch/pre-036/` (bin, plugins, sources, tests). Session files: same format
  80. (webex → web sessions.hl diff = only Secure/Domain options + skipping non-session files); migration gate 23/0 reads old cookies.
  81. ## 2026-09-25 — antcolony mission 035: re-vendor to hybriel master 13ef4f9b STOPPED (Hybriel bug) — folder restored to pre-035
  82. Converted (hl:webex → hl:web, plugins/webex gone, `sessionCookie`, HL_HOST, `req.session`, realSession() dropped, `Intl`,
  83. `a.id < b.id`, tokens.hl hl:web copy, testapp `encodeURIComponent`); the WIP is in `.scratch/035-wip/` (`changes.diff`).
  84. Gates on it: migration 23/0, iplimit 39/0; browser 55/1, apps 44/1, selector 88/9, notify 127/3. Causes: (1) HYBRIEL BUG —
  85. `if (outer) { … if (inner) { … } <element> }` with outer AND inner turning false in one handler → client `NotFoundError:
  86. insertBefore … not a child` in rebuildBranch (home.hl doSignOut: signedIn/manage/…); the page stops updating. Repro:
  87. loreana `~/scratch-035/repro/` (`home-nested-if.hl`, `node tests/run.mjs <dir>`). (2) the forged-session checks expect an
  88. app `{error}`; hl:web now refuses the extra argument itself (ack `ok:false`, "the `session` parameter is filled by the server")
  89. → the checks must accept that. Kept on purpose: Mailer placeholder host (mail.hl needs a Mailer for deliver()/handlers),
  90. jsoncheck, X-Client-IP, hand sorts, avatar.js guard. Restored bin/plugins/sources = pre-035 (webex e565176b); all six gates
  91. green again (132/105/99/23/39/130). Next: when the bug is fixed, re-apply `.scratch/035-wip/`, fix (2), run all gates.
  92. ## 2026-09-25 — ticket ident#21 (sign out everywhere, live logout + files deleted)
  93. - Already built under ident#19 (see below); re-verified today: `tests/signoutall.mjs` 6 passed, `tests/hardening.mjs` 17 passed. No code change. Needs deploy.
  94. ## 2026-09-25 — the code step is a page: /code (ticket ident#20, mission 008 (old 032)) — awaiting architect (deploy) / creator
  95. - Creator's design ("just make a /code where it checks a pending code"): "Send me a code" → face
  96. `rememberPending` (session `data.pendingEmail`, only while a code for it is waiting) → `/code`
  97. (`/signin/<rid>/code` for an app). `project.hl codePage` renders Home's code form for the pending
  98. address, else 302 back to `/` / `/signin/<rid>`. Cleared on sign-in, "Other address", expiry.
  99. README "What it does" → "The code step is its own page". Changed: `store.hl`, `project.hl`,
  100. `components/home.hl`, tests (browser, apps + login helpers of selector/notify/iplimit/signoutall).
  101. - Gates (all on Loreana, own servers): browser 132, apps 105, selector 99, migration 23, notify 130,
  102. iplimit 39, hardening 17, signoutall 6 — all passed. Screenshots `.scratch/gate-*-code-reloaded.png`,
  103. `.scratch/apps-code-for-app-reloaded-*.png`. Pre-change copies: `.scratch/m032-backup/`.
  104. - NOT deployed. By hand (never on :8351 — real mail): own server e.g.
  105. `IDENT_PORT=8740 IDENT_STORAGE=$PWD/.scratch/m032-smoke/store IDENT_SESSIONS=$PWD/.scratch/m032-smoke/sess IDENT_MAIL_SINK=$PWD/.scratch/m032-smoke/mail.txt SMTP_HOST= ./bin/hybriel project.hl`
  106. → http://127.0.0.1:8740 → send → `/code` → reload → code from `mail.txt` → signed in.
  107. `curl -si localhost:8740/code` without a pending code → `302 Location: /`.
  108. - Lesson: a component route cannot answer a redirect; a function route can resolve the session
  109. (`server.sessions.resolve`) and render a component via `server.page(match, req, s)` with a
  110. hand-made match `{route {component}, kind 'component', params, path}` (extra params reach the
  111. component as construction arguments, e.g. `step`).
  112. ## 2026-09-24 — avatar is an UPLOAD (ticket ident#15, second rework after the creator's rejection) — awaiting architect (deploy) / creator
  113. - The URL field is gone: the identity form has a file chooser; `avatar.js` scales the picture to 128×128
  114. in the browser; the server stores it as a checked data URL (`store.hl checkAvatarUrl`, ≤ 60000 chars).
  115. Old link avatars are hidden and dropped on the next save. README "Avatar".
  116. - Gates (port-remapped copies, 8700-8709): `tests/browser.mjs` 96 passed (upload, refusal of a text file,
  117. scaled 128×128, colours, list, reopen, remove; server negatives), `tests/apps.mjs` 97 passed.
  118. ## 2026-09-24 — sign out everywhere really ends every session (ticket ident#19) — awaiting architect (deploy) / creator
  119. - `store.hl` `signOutEverywhere` now deletes every session of the account (memory + files) through the
  120. hook `project.hl` `dropUserSessions`; the epoch bump stays as a second net. No vendored plugin changed.
  121. - The face `signOutAll` emits `signedOutAll(accountId)` first; `project.hl` `audience` admits connections
  122. whose session user is that account; `components/main.hl` (in every page) reloads → signed out, no click.
  123. - Gates: `tests/hardening.mjs` 17 passed (new: session files gone); new `tests/signoutall.mjs` 6 passed
  124. (device A home + apps pages switch by themselves, caller too, no session file left).
  125. ## 2026-09-24 — design tokens AS DESCRIBED (ticket antcolony#3, antcolony mission 021) — awaiting architect (deploy) / creator
  126. antcolony Mission 018 (generated `':root'` copy + sync.sh) was REJECTED by the creator; antcolony mission 021 replaces it.
  127. Done (worker; developed in `.scratch/dev-m021`, copied back, NOT deployed):
  128. - `shared/tokens.hl` = hand-written webex `var()` tokens (`static dark = var('rgb(25, 30, 35)')`, semantic
  129. `colorText = var(light)` …, incl. `colorAccentText` moved here from the app), verbatim copy of
  130. `/media/STORAGE/projects/worldapi-tokens/tokens.hl`. `styles.hl` member-imports them and uses them as
  131. members; sets only `colorAccent = var(orange)`. README "Design tokens".
  132. - webex LOCAL PATCH (antcolony mission 021, hybriel#39) in `plugins/webex/WebFramework.hl`; XSS patch kept.
  133. - Old files: `.scratch/m021/pre-copy-backup/` (styles.hl, shared/tokens.hl, WebFramework.hl); antcolony mission 018's
  134. worldapi-tokens folder (sync.sh, tokens.css, generated tokens.hl): `tickets.worldapi.org/.scratch/m021/worldapi-tokens-m018-backup/`.
  135. - Rendered colours unchanged: `/__hl/app.css` after `:root` byte-identical old vs new (in `:root` only
  136. the order of `--color-accent-text` / the app's `--color-accent` changed); Chrome computed colours
  137. of every element + every token, tickets list/inbox/state/ticket/you + ident home/code/account/apps/inbox,
  138. 390 + 1280: 23 passed, 0 failed.
  139. - All 6 gates green (dev copy and main folder), port-remapped copies `.scratch/m021/gate-ident.sh`:
  140. browser 83, apps 97, selector 99, migration 23, notify 130, iplimit 39.
  141. - Deploy simulation (deploy.sh excludes + debian:12-slim container): same app.css, byte-identical.
  142. How to verify (ports 8850-8879 only; scripts in tickets.worldapi.org/.scratch/m021 unless noted):
  143. ```bash
  144. cd /media/STORAGE/projects/tickets.worldapi.org
  145. for a in tickets ident; do cmp /media/STORAGE/projects/worldapi-tokens/tokens.hl ../$a.worldapi.org/shared/tokens.hl && echo same $a; done
  146. bash .scratch/m021/cmpup.sh up; node .scratch/m021/cmpstyles.mjs; bash .scratch/m021/cmpup.sh down # 23 passed (old code: <app>/.scratch/m021/old-<app>)
  147. IDENT_GATE_DIR=/media/STORAGE/projects/ident.worldapi.org bash .scratch/m021/gate-tickets.sh .scratch/m021/g.txt; tail -2 .scratch/m021/g.txt # 402 passed
  148. cd /media/STORAGE/projects/ident.worldapi.org; bash .scratch/m021/gate-ident.sh . .scratch/m021/gates-main # 6 suites green
  149. bash /media/STORAGE/projects/tickets.worldapi.org/.scratch/m021/deploysim.sh <tickets dir> <ident dir> # needs cmpup's run/main-*.css
  150. ps -eo pid,args | grep [h]l-browser-tier # empty
  151. ```
  152. Screenshots: `tickets.worldapi.org/.scratch/m021/shots/<app>-<old|new>-<page>-<390|1280>.png`.
  153. Lesson: an imported `static` var() token works only with the antcolony mission 021 webex patch; tokens.hl changes need a restart.
  154. ## 2026-09-24 — shared design tokens (ticket antcolony#3, antcolony mission 018) — REJECTED by the creator, superseded by antcolony mission 021
  155. Done (worker; developed in `.scratch/dev-m018`, copied back, NOT deployed):
  156. - Tokens now come from `shared/tokens.hl` (generated from layouts `tokens.css` by
  157. `/media/STORAGE/projects/worldapi-tokens/sync.sh`, vendored like plugins/); `styles.hl` inherits it and
  158. sets only the accent (README "Design tokens"). Old copy of `styles.hl`: `.scratch/m018/pre-copy-backup/`.
  159. - Every rendered colour unchanged: `/__hl/app.css` after `:root` byte-identical old vs new; computed
  160. colours of every element (tickets list/inbox/state/ticket/you, ident home/code/account/apps/inbox, 390 +
  161. 1280) old == new in Chrome.
  162. - All 6 gates green (dev copy and main folder): browser 83, apps 97, selector 99, migration 23,
  163. notify 130, iplimit 39 — run as port-remapped copies (`.scratch/m018/gate-ident.sh`, 8850-8879).
  164. How to verify (ports 8850-8879 only):
  165. ```bash
  166. cd /media/STORAGE/projects/tickets.worldapi.org
  167. /media/STORAGE/projects/worldapi-tokens/sync.sh --check # ok ×3
  168. bash .scratch/m018/cmpup.sh up; node .scratch/m018/cmpstyles.mjs; bash .scratch/m018/cmpup.sh down # 23 passed (old code in .scratch/m018/old-*)
  169. IDENT_GATE_DIR=/media/STORAGE/projects/ident.worldapi.org bash .scratch/m018/gate-tickets.sh /tmp/g.txt; tail -2 /tmp/g.txt # 402 passed
  170. cd /media/STORAGE/projects/ident.worldapi.org; bash .scratch/m018/gate-ident.sh . .scratch/m018/gates-main # 6 suites green (ports remapped copies)
  171. bash /media/STORAGE/projects/tickets.worldapi.org/.scratch/m018/deploysim.sh <tickets dir> <ident dir> # deploy rsync + container: same app.css
  172. ps -eo pid,args | grep [h]l-browser-tier # empty
  173. ```
  174. Screenshots: `tickets.worldapi.org/.scratch/m018/shots/<app>-<old|new>-<page>-<390|1280>.png`.
  175. Lesson: a `var()` token of hl:webex/css can't cross an `inherit` (second instance) — use literal `':root'` strings.
  176. ## 2026-09-24 — piece 4/6: notifications API + inbox (ticket ident#6, mission 007 (old 013)) — awaiting architect / creator
  177. Done (worker, NOT confirmed by the creator; NOT deployed — the architect deploys with `./deploy.sh`):
  178. - `notify.hl` + routes `POST /api/kinds`, `POST /api/notify` (key + secret + per-app identity id,
  179. strict bodies) and pages `/inbox` (components/inbox.hl), `/inbox/<connection id>`
  180. (components/appsettings.hl, the per-app page); nav link "Inbox". README "How apps send notifications".
  181. - New tables only (`kinds`, `settings`, `notifications`; created empty at start, no migration).
  182. Old tables untouched: upgrade check on a COPY of the stale dev storage → every old table SAME.
  183. - Stored per notification: channels (`push`, `mail` = daily|now|none) + `pushSent`/`mailSent`
  184. = false. Nothing is sent (delivery = ident#7 mail, ident#8 push).
  185. - `api.hl strictBody` knows type `List`; `tools/dump-store.hl` dumps the 3 new tables;
  186. `deploy.sh` runs the new gate too (six gates).
  187. - Developed in `.scratch/dev-m013`; work scripts `.scratch/m013/` (`gates.sh`, `upgrade.sh`, `probe.mjs`).
  188. How to verify:
  189. ```bash
  190. cd /media/STORAGE/projects/ident.worldapi.org
  191. node tests/notify.mjs # 130 passed, 0 failed (~20 s; ident :8410, site :8413, Chromes 8740-8749)
  192. .scratch/m013/gates.sh $PWD browser apps selector migration iplimit notify # 83/97/99/23/39/130, 0 failed
  193. ps -eo pid,args | grep [h]l-browser-tier # must print nothing (of ours)
  194. bash .scratch/m013/upgrade.sh # old code dump vs new code on a COPY: old tables SAME, 3 new empty
  195. ```
  196. Screenshots: `.scratch/notify-{inbox,inbox-read,appsettings,override}-{390,1280}.png`.
  197. By hand (creator, after deploy): the test app / an app's server sends with curl (README "Try it"),
  198. then https://ident.worldapi.org/inbox → the notification, "Mark read", the app link below →
  199. per-app page → flip switches / "Override all".
  200. Choices made (open questions for the creator, see mission report):
  201. - unregistered name → allowed, stored as an unregistered kind: daily mail, no push, push switch "—".
  202. - override for all = an on/off switch + its own push/email switches; while on it replaces every
  203. kind's setting and locks the per-kind switches; the user's own switches are kept underneath.
  204. - urgent: push if push is on and a device exists (never before piece 6), else mail now if push
  205. or email is on for that kind, else inbox only.
  206. - times in UTC (no zone support in hl:time); text shown as plain text (Markdown not rendered);
  207. icon and link any http(s) URL (not limited to the app's origins); no send rate limit;
  208. inbox shows the newest 200; no delete of notifications/kinds; a deleted app's notifications
  209. stay ("a deleted app").
  210. Lessons:
  211. - Boolean HTML attributes (`checked`, `disabled`) can't be rendered conditionally by hl:webex
  212. (it always writes `name="value"`): use buttons with `role="switch"` + `"aria-checked" = member`
  213. (quoted attribute names work) and separate `if` branches for a locked state.
  214. - A handler inside a `for` row gets the row's key via `value = row.x` / `e.target.value` and looks
  215. the row up in the member list (as apps.hl does); using the loop variable inside the handler was
  216. avoided, not tested.
  217. - `find(null, null)` lists a whole table (connections has no identity index; the per-account list scans).
  218. ## 2026-09-24 — per-IP limit + deploy readiness (mission 006 (old 010)) — awaiting creator / architect
  219. Done (worker, NOT confirmed by the creator; NOT deployed to Byrodin — the architect does the first deploy):
  220. - **Per-IP limit on code mails**: 10 per client IP per 10 min, 30 per 24 h (env `IDENT_IP_*`),
  221. on top of 3 per address per 10 min. IP = `X-Client-IP` header only (nginx sets it); IPv6 per
  222. /64; no header = one shared bucket `direct` (hl:webex exposes no peer address). Table `ipsends`.
  223. - The code request moved from the face `requestCode` (removed — a face sees no headers, it
  224. would bypass the limit) to **`POST /api/code {email}`**; the sign-in form fetches it; refusals
  225. (400/429 `{error}`) show under the form as before.
  226. - **Listener**: hl:webex ignores `HL_HOST` → `project.hl` builds the `NativeWebSocketServer`
  227. itself (`http = …` pinned in the constructor), `HL_HOST=127.0.0.1` binds loopback, unset =
  228. 0.0.0.0 (dev unchanged). `IDENT_WATCH=0` turns the dev watcher off (container).
  229. - `docker-compose.yml` (127.0.0.1:45002) + `deploy.sh` (README "Deploy").
  230. - Gates: the four old ones got `IDENT_IP_LIMIT/IDENT_IP_DAY_LIMIT=1000` on their servers;
  231. `apps.mjs`/`selector.mjs` log in over `POST /api/code` instead of the face; `browser.mjs`
  232. accepts Chrome's "Failed to load resource … 400/429 … /api/code" console line (the refusals).
  233. New `tests/iplimit.mjs` (39 checks). `tools/dump-store.hl` also dumps `ipsends`.
  234. - Developed in `.scratch/dev-m010`; work files in `.scratch/m010/` (local deploy test
  235. `localdeploy.sh`, TLS proxy test `tlsproxy/`).
  236. How to verify:
  237. ```bash
  238. cd /media/STORAGE/projects/ident.worldapi.org
  239. node tests/iplimit.mjs && node tests/browser.mjs && node tests/apps.mjs && node tests/selector.mjs && node tests/migration.mjs # 39/83/97/99/23, 0 failed
  240. ps -eo pid,args | grep [h]l-browser-tier # must print nothing
  241. bash -n deploy.sh && ./deploy.sh --dry-run --target /tmp/identdeploy-test # gates + rsync -n preview into a LOCAL dir; never run without --target unless you deploy
  242. ```
  243. Deploy test done on Loreana ONLY (never Byrodin): `.scratch/m010/localdeploy.sh ident <src> 45002`
  244. deploys into `.scratch/m010/fake-byrodin/ident.worldapi.org` (fake `storage/` + `.env` markers
  245. survive, md5-checked), runs the container there with docker (only 127.0.0.1:45002 listens, LAN
  246. refused), URL 200. `.scratch/m010/tlsproxy/run.sh`: nginx with TLS in front of the ident and
  247. tickets containers + Chrome (`https.mjs`, 16 checks: wss socket, no http:// URLs, login +
  248. cookie + reload over https, selector CORS for `https://tickets.worldapi.org`, forged
  249. X-Client-IP overwritten by the proxy, watcher off). Containers/proxy removed afterwards.
  250. By hand (creator): http://100.77.141.84:8351 → sign in as before (the form now POSTs /api/code).
  251. For the architect (first deploy): the ident vhost needs `proxy_set_header X-Client-IP $client_ip;`
  252. (and should drop the X-Forwarded-For/CF-Connecting-IP lines, ident ignores them), the WebSocket
  253. Upgrade headers (`/__hl/socket`), and `proxy_pass http://127.0.0.1:45002` (today's
  254. `worldapi.org/ident.conf` points to 44800 and has no Upgrade headers). `.env` on Byrodin: SMTP_*
  255. (no IDENT_PORT — compose sets 45002; the real environment outranks `.env`).
  256. Lessons:
  257. - hl:webex ignores `HL_HOST`/`HOST` (it builds `new NativeWebSocketServer(port = port)`), hl:web
  258. v1 read it. Workaround: pass `http = new NativeWebSocketServer(port, host)` to the constructor.
  259. - A face has no request: anything that needs a header (client IP) must be a function route.
  260. - A non-JSON file in the session directory crashes the server at boot (sessions.hl JSON.parse).
  261. - Chrome logs every 4xx fetch as a console error — gates that check the console must expect them.
  262. ## 2026-09-24 — UUID keys + storage/mpackdb/ (ticket ident #10 / old #39, mission 005 (old 009)) — awaiting creator
  263. Done (worker, NOT confirmed by the creator):
  264. - Every table (accounts, identities, otp, sends, apps, connections, requests, grants) has pk
  265. `@id` (mpackdb UUID, 12 chars) and lives in `storage/mpackdb/<table>.*`; `IDENT_STORAGE` is now
  266. a DIRECTORY. Public ids of accounts/identities/apps = the UUIDs (faces take strings; old numbers
  267. find nothing). Default identity / app list = oldest by stored `created` (`store.hl oldestFirst`).
  268. - Kept byte-identical: `apiKey`, `secretHash`, `appIdentity` (still a 32-hex FIELD, also for new
  269. connections), `rid`, grant hashes. Selector pick ids changed once (derived from the identity id).
  270. - `tools/migrate-009.hl` (idempotent via `oldId`, sessions `"user":{"id":n}` → account UUID),
  271. `tools/dump-store.hl` (JSON of all tables, COPY only). Old tables `storage/ident-*` kept, unread.
  272. - Deployed 12:02: backup `.scratch/storage-backup-20260924-120234/` (storage, .sessions,
  273. testapp/storage); work dir `.scratch/m009/deploy-20260924-120234/` (before/after dumps, runs,
  274. code-before). Migrated from a COPY twice (2nd: 0 new); `cmp009.py` → RESULT SAME (2 accounts,
  275. 2 identities, 4 sends, 1 app, 1 connection); 6 sessions migrated → creator still signed in.
  276. - Rehearsed twice on copies first (`.scratch/m009/rehearse.sh <n>`: migrate ×2, compare, serve
  277. the copy with the new code on :8380 + Chrome: sessions signed in, test app key+secret exchange →
  278. its stored id).
  279. - Developed in `.scratch/dev` (old one moved to `.scratch/dev-m006`); scripts in `.scratch/m009/`.
  280. How to verify:
  281. ```bash
  282. cd /media/STORAGE/projects/ident.worldapi.org
  283. node tests/browser.mjs && node tests/apps.mjs && node tests/selector.mjs && node tests/migration.mjs # 83/97/99/23, 0 failed
  284. ps -eo pid,args | grep [h]l-browser-tier # must print nothing
  285. rm -rf /tmp/identcopy && cp -a storage/mpackdb /tmp/identcopy && IDENT_STORAGE=/tmp/identcopy ./bin/hybriel tools/dump-store.hl | python3 -m json.tool | less # COPY only (#40)
  286. python3 .scratch/m009/cmp009.py .scratch/m009/deploy-20260924-120234/{before,after}.json # RESULT SAME
  287. ```
  288. By hand (creator): http://100.77.141.84:8351 still signed in (no new login), `/apps` shows the same
  289. API key; http://100.77.141.84:8354 → "Log in with ident" / "choose ident" → the same identity id,
  290. "welcome back" (no re-registering).
  291. Lessons:
  292. - `from` is a reserved word in Hybriel (a parameter/`let` named `from` fails to parse).
  293. - `new MPackDB(..., compact = false)` still rewrites the index/meta files (only `.mpack` stays) —
  294. #40 holds; migrate/dump COPIES only.
  295. - `put()` on an `@id` table keeps an `id` the record already carries — copy rows WITHOUT `id`.
  296. - Deploy scripts: `(cd x && cmd & echo $! > x.pid)` backgrounds the whole `&&` list — `$!` is a
  297. subshell and the pid file lands in the wrong dir; use `cd x; cmd & echo $! > x.pid; cd ..`.
  298. ## 2026-09-24 — piece 3/6: identity selector (ticket #26, mission 004 (old 007)) — awaiting creator
  299. Done (worker, NOT confirmed by the creator):
  300. - `/selector.js` (file `selector.js`): `<ident-selector key="pk_…">`, shadow DOM, ident's
  301. colours; says "choose ident"; opening fetches the identities (credentials); choosing one
  302. → one-time code → event `ident-login` (`detail.code`). `logged-in` attribute / `loggedIn`
  303. property / `reset()` set by the host; no cookie of its own. Restyle: `--ident-*` custom
  304. properties + `::part(button|panel|identity|status|message|link)` (README).
  305. - `selector.hl`: `GET /api/selector/identities?key=`, `POST /api/selector/choose?key=`
  306. (+ OPTIONS preflight). Answers only if `Origin` ∈ the app's origins AND key = that app's
  307. (exact ACAO + credentials, `Vary: Origin`); else 403 without CORS headers. List ids =
  308. sha256(app secretHash:identity)[0..32] (opaque, per app). Code via `apps.hl issueCode`
  309. (shared with the login button: 48 hex, 60 s, single use).
  310. - `testapp/`: selector next to the login button; `/selector-login` (exchange server side),
  311. `/logout`; own session cookie `testapp<port>sid` → `/` renders `logged-in`.
  312. - Ticket #37: `tests/browser.mjs` two obsolete checks replaced (old shapes → 400) → 81/0.
  313. - Gates in the live folder: `selector.mjs` 97/0 (8 s), `apps.mjs` 94/0, `browser.mjs` 81/0.
  314. - Deployed: backup `.scratch/storage-backup-20260924-112012/` (storage, .sessions,
  315. testapp/storage); restarted :8351 and :8354; store checked on a copy
  316. (`.scratch/m007/check-store.hl`): 2 accounts, 2 identities, app `tickets.worldapi.org`,
  317. 0 connections — as before.
  318. NOTE: check-store.hl was also run once directly on that backup by mistake: its accounts/
  319. identities/apps/connections files were rewritten by the open (content = live, diff clean).
  320. - Developed in `.scratch/m007/dev` (copy without `.env`/storage).
  321. Running: ident :8351 (`server.pid`, `server.log`); test app :8354 (`testapp/testapp.pid`,
  322. `testapp/testapp.log`). The test app is NOT set up (its `testapp/storage/` was empty):
  323. register an app in ident with origin `http://100.77.141.84:8354`, paste key + secret.
  324. How to verify:
  325. ```bash
  326. cd /media/STORAGE/projects/ident.worldapi.org
  327. node tests/selector.mjs && node tests/apps.mjs && node tests/browser.mjs # 97 / 94 / 81, 0 failed
  328. ps -eo pid,args | grep [h]l-browser-tier # must print nothing
  329. curl -s -i 'http://127.0.0.1:8351/api/selector/identities?key=<pk>' -H 'Origin: <registered origin>' # 200 + ACAO
  330. ```
  331. By hand (creator): sign in at http://100.77.141.84:8351, set up the test app, open
  332. http://100.77.141.84:8354 → "choose ident" → pick → "Logged in" without reload → Log out.
  333. Screenshots: `.scratch/selector-{closed,open,loggedin,restyled-open,signedout,unregistered}-{390,1280}.png`.
  334. Open:
  335. - Signed out of ident: the selector links to ident's `/` in a new tab; after signing in
  336. the user opens the selector again (it re-asks on every open). No auto-refresh.
  337. - Same-site only (identsid is `SameSite=Lax`): a foreign-domain app gets
  338. `signedIn:false` (out of scope by the brief).
  339. - A registered page can call the two endpoints itself (no user click needed) — the host is
  340. trusted for its own app; it still only gets its own per-app id via its secret.
  341. - Opening an hl:mpackdb table rewrites its files (mtime, meta) — only check COPIES.
  342. Lessons:
  343. - hl:webex passes OPTIONS to function routes; request header names arrive lowercased.
  344. - A file route (`{ pattern file headers }`) serves JS with the right type and extra headers.
  345. - An hl string has no escapes: inline JS in an hl string uses double quotes only.
  346. - Shadow-DOM elements: tests/cdp.mjs `click()` can't reach them — `tests/selector.mjs
  347. shClick` computes the box inside the shadow root and dispatches real mouse events.
  348. ## 2026-09-24 — piece 2/6: apps and login button (ticket #25, mission 003 (old 006)) — awaiting creator
  349. Done (worker, NOT confirmed by the creator):
  350. - `/apps`: register (name + origins), list, edit, new secret, delete. API key `pk_…` public,
  351. secret `sk_…` shown ONCE (sha256 stored). Faces `appCreate/appUpdate/appNewSecret/appDelete`.
  352. - One id per app: table `connections` (`pair` = app:identity, random 32-hex `appIdentity`,
  353. `created`). Same identity + app → same id; other app / other identity → other id.
  354. - Login button: `GET /login?key=&return=` → error page (400, no redirect) or `/signin/<rid>`
  355. → ident login if needed → "Choose an identity" (face `chooseIdentity`) →
  356. `<return>?ident_code=` (60 s, single use). `POST /api/exchange {key,secret,code}` →
  357. `{identity}` only; 401 bad key/secret, 400 bad code/other app's code/JSON/fields.
  358. - `testapp/` (separate hybriel app) on :8354; `tests/apps.mjs` gate → **94 passed, 0 failed**.
  359. - Deployed to :8351 (storage backup `.scratch/storage-backup-20260924-092054/`); store
  360. checked read-only after restart: the creator's account + its identity still there.
  361. - Developed in `.scratch/dev` (copy without `.env`/storage), then copied over.
  362. Running: ident :8351 (`server.pid`, `server.log`); test app :8354 (`testapp/testapp.pid`,
  363. `testapp/testapp.log`, http://100.77.141.84:8354 — not set up: the creator registers it in
  364. ident with origin `http://100.77.141.84:8354` and pastes key + secret into its form).
  365. How to verify:
  366. ```bash
  367. cd /media/STORAGE/projects/ident.worldapi.org
  368. node tests/apps.mjs # 94 passed (own servers, sink mail, no :8351)
  369. node tests/browser.mjs # 79 passed + 2 OBSOLETE fails (below)
  370. ps -eo pid,args | grep [h]l-browser-tier # must print nothing
  371. curl -s -o /dev/null -w '%{http_code}\n' 'http://127.0.0.1:8351/login?key=pk_x&return=http://a.b/' # 400
  372. ```
  373. Screenshots: `.scratch/apps-*-{390,1280}.png`.
  374. Open:
  375. - ~~`tests/browser.mjs` two obsolete checks~~ — fixed in mission 004 (old 007) (ticket #37).
  376. - Deleting an identity leaves its connections (ids) in place; the exchange of an already
  377. issued code still works for 60 s. Deleting an app removes its connections.
  378. - Expired `requests`/`grants` rows are removed only when looked up (no sweep).
  379. ## 2026-09-24 — piece 1/6: account and identities (ticket #24, mission 002 (old 005)) — awaiting creator
  380. Done (worker, NOT confirmed by the creator):
  381. - Email OTP login to ident (mission-003 rules kept). No registration: first right code creates
  382. the account + default identity (name `Default`), then the optional-names form (Save / Skip).
  383. - Identities: list / new / edit / delete, all fields optional, last one cannot be deleted,
  384. default = oldest remaining. Time zone from the browser at first login, shown, changeable.
  385. - Removed the mission-003 app flow (`/login`, `/continue`, `/api/exchange`, allow list,
  386. `testclient/` — its PID 161897 on :8354 stopped). Old code: `.scratch/removed-mission003/`.
  387. - Old store (mission-003 test data) moved to `.scratch/storage-backup-20260924-060627/`
  388. (storage + .sessions); started empty.
  389. - Gate `node tests/browser.mjs` → **81 passed, 0 failed**; `node tests/dev-smoke.mjs` → SMOKE OK.
  390. Running: ident :8351 (PID `server.pid`, log `server.log`). Dev store: [email protected]
  391. (from dev-smoke; time zone Europe/Vienna, one identity `Default`).
  392. ## How to verify
  393. ```bash
  394. cd /media/STORAGE/projects/ident.worldapi.org
  395. node tests/browser.mjs && node tests/dev-smoke.mjs
  396. ps -eo pid,args | grep [h]l-browser-tier # must print nothing
  397. curl -s http://127.0.0.1:8351/api/exchange # {"error":"no such endpoint"}
  398. .scratch/repro-session-forge/run.sh # the hybriel face-session repro (port 8359)
  399. ```
  400. By hand: http://100.77.141.84:8351 → email → `tail -1 storage/mail-sink.txt` → code →
  401. welcome form (Skip or fill) → identities, time zone.
  402. ## Open / next
  403. - Pieces 2–6 (#25–#29): apps + API keys + app secret, per-app ids, login button flow,
  404. selector, notifications. Tickets wiring waits for piece 2.
  405. - Questions to the creator (mission 002 (old 005) report): default identity's name `Default`; blank
  406. identity names shown as nickname / names / `Identity <n>`; default = oldest (no
  407. "make default"); time zone as a free IANA text field.
  408. - A mail bounce is only logged (`mail: FAILED`). No account deletion, no email change.
  409. - Deploy (Byrodin container, nginx, DNS, real SMTP via mail.byrod.in) out of scope.
  410. ## Lessons
  411. - **A server face is found by its EVENT NAME across every file** (hl:webex `facesOf`): two
  412. components with `on server save()` would BOTH run. apps.hl's faces are named `app…`.
  413. - hl:webex needs at least one COMPONENT route (it reads the app dir off it): the test app
  414. has `/about` only for that.
  415. - Chrome may pre-fill a password field on a later visit: the gate types the test app's secret
  416. with `clear: true` and checks the saved key after the submit.
  417. - Develop in `.scratch/dev` (copy without `.env` and storage) — the :8351 watcher reloads on
  418. every save in the app dir.
  419. - **A face's `session` is POSITIONAL** (the framework appends it to the client's payload):
  420. a hand-made frame with one argument too many puts the CLIENT's object in the session's
  421. place. Every face goes through `store.hl accountOfSession`/`realSession`, which only accepts
  422. `hlTypeName(session) == 'Instance'` (JSON is always `Hybrid`). Repro:
  423. `.scratch/repro-session-forge/` (reported to hybriel).
  424. - A face runs on a BLANK instance: component functions/members are null there (NotCallable);
  425. use only arguments and imports.
  426. - `if (!member)` in a View renders nothing, silently (repro `.scratch/repro-if-not/check.mjs`):
  427. keep a separate member (`notWelcome`).
  428. - `Intl` is not on the JS compiler's browser-global list; `window.Intl` works.
  429. - Chrome canonicalises some zones (`America/Argentina/Buenos_Aires` → `America/Buenos_Aires`);
  430. store what the browser reports.
  431. - A NON-LITERAL static in the manifest reads null in the manifest's own function routes once
  432. a component imports from project.hl (hybriel #16) — keep such values elsewhere.
  433. - Function routes get no session (#18): `server.sessions.resolve(cookieHeader)`.
  434. - `server.sessions.cookie = 'identsid'` after construction renames the cookie (#17).
  435. - `styles = {}` boots but `/__hl/app.css` answers 500 (#19): every app needs a styles.hl.
  436. - hl:smtp refuses a Mailer without a sender at construction (#20) → placeholders in mail.hl.
  437. - `JSON.parse` on bad input aborts the request (#12): a future JSON API must check the body
  438. first (copy tickets' `jsoncheck.hl`).
  439. ## 2026-09-24 — real mail on (architect, creator asked)
  440. - `.env` = copy of hybriel `projects/demo-social-network/.env` (SMTP via mail.byrod.in, user
  441. [email protected], sender [email protected]) + `IDENT_PORT=8351`. The file sink is OFF
  442. for the dev server. Backup of the old `.env` in `.scratch/env-backup-*`.
  443. - Verified: creator logged in with a real code; mail server log shows delivery to [email protected]
  444. (`docker logs mail | grep social.network` on Byrodin).
  445. - **Tests must never send real mail**: the gate starts its own servers with `IDENT_MAIL_SINK`;
  446. do NOT run `tests/dev-smoke.mjs` against :8351 any more (it would mail [email protected]).
  447. - Creator: "looks ok for a first" — worker's default choices (Default name, label fallback, oldest =
  448. default, free IANA time zone field, UTC fallback, 60 chars) accepted as built.
  449. ## 2026-09-24 — dev hostname trap (creator testing #26)
  450. - Selector said "not signed in" because ident was logged in under one host and the test app ran under the other.
  451. The test app loads the selector from IDENT_URL=http://100.77.141.84:8351. Use ONE hostname for both:
  452. http://100.77.141.84:8351 (ident) + http://100.77.141.84:8354 (test app). LAN IP 192.168.178.75 = different site.
  453. ## 2026-09-24 — DEPLOYED (architect)
  454. - LIVE is now https://ident.worldapi.org on Byrodin (/CONTAINERS/projects/ident.worldapi.org). The live data was moved there.
  455. - This Loreana folder is DEVELOPMENT only; its storage/ is a stale copy. Dev servers were stopped. Update live with ./deploy.sh.
  456. ## 2026-09-24 — session hardening (ticket #2, items 3+4): copied into the app
  457. - Rescued antcolony mission 022's work-copy build from `.scratch/dev-m022/` (a prior worker ran out of
  458. steps before reporting) and copied its 4 changed files into the real project root:
  459. `store.hl`, `apps.hl`, `components/home.hl`, `components/appsettings.hl`. Not deployed
  460. (out of scope for this worker; ./deploy.sh is the architect's).
  461. - **Item 3 — the pending session's own expiry**: `store.hl` `beginSession`/`accountOfSession`
  462. stamp `session.user.since` + `session.user.epoch` at sign-in; independent of the webex
  463. session file's own idle/maxAge clock. `IDENT_SESSION_TTL_MS` (default 14 days). An OLDER
  464. session (no `since`/`epoch` — before this ticket, or from the mission-009 migration) is
  465. upgraded on its next use instead of being force-signed-out.
  466. - **Item 4 — sign out for apps / sign out everywhere**: `store.hl` `signOutEverywhere` bumps
  467. the account's `sessionEpoch`, which invalidates every session carrying the old epoch on its
  468. next check (no per-session-file bookkeeping needed) — wired to a "Sign out everywhere"
  469. button on `/` (`components/home.hl`). `apps.hl` `disconnectConnection` forgets one app
  470. connection — wired to a "Sign out of this app" button on the per-app page
  471. (`components/appsettings.hl`); a later login makes a fresh, unrelated per-app id.
  472. - **Bug fixed in the mission-022 test** (`tests/hardening.mjs`, now copied to the real
  473. `tests/`): its `signIn()` helper read the session cookie from `/api/code`'s response, but
  474. that route sets no cookie at all (it's a plain function route, no session) — the real
  475. cookie is minted by the following `/__hl/emit verifyCode` call. Also added a short poll
  476. for the mail-sink code (the sink write can land just after the HTTP response). Not an app
  477. bug — the 3 failing checks were the test's own cookie handling.
  478. - **Lesson: kill your test servers even on a thrown error.** A crashed `tests/hardening.mjs`
  479. run (before this fix) left two orphaned `bin/hybriel` processes bound to :8700/:8701; a
  480. later debug run then hit those STALE servers (old per-address send-limits, wrong session
  481. state) instead of a fresh one, which looked exactly like a cookie bug at first. Always
  482. check `ps -eo pid,args | grep hybriel` for leftovers from a previous crashed run before
  483. trusting a new test failure.
  484. - Verified: `node tests/hardening.mjs` (copied into `tests/`) → **15 passed, 0 failed**.
  485. Full regression re-run against the real project files after the copy — no regressions:
  486. `tests/browser.mjs` 83/83, `tests/apps.mjs` 97/97, `tests/notify.mjs` 130/130,
  487. `tests/selector.mjs` 99/99, `tests/migration.mjs` 23/23.
  488. - Still open from ticket #2: item 2 (real mail — architect already reported done, not
  489. re-verified this session, out of scope) and the session cookie's missing `Secure` flag
  490. (hybriel #27, blocked on a hybriel fix, not app-side). `.scratch/dev-m022` and `.scratch/m022`
  491. (prior worker's scratch) left in place, no longer needed for this ticket.
  492. ## 2026-09-24 — identity avatars (ticket #15), continued and finished
  493. A prior worker (session `s-20260924T1745-d1e933`) had already added the avatar field to
  494. `store.hl`/`components/home.hl`/`styles.hl` but ran out of steps before testing any of it. This
  495. session verified, fixed and tested it.
  496. - **Found a real regression by running the gate, not by reading the code**: `node
  497. tests/browser.mjs` failed 4 checks and then aborted (a later step depended on the failed
  498. save) — editing an identity silently saved nothing.
  499. Traced with throwaway probes (`.scratch/probe15/`, removed after): server-side `checkFields`
  500. and `updateIdentity` were correct in isolation (confirmed with a standalone face call over
  501. HTTP), but in the real browser, typing a SECOND character into any field of the edit form lost
  502. focus — the DOM node was being replaced under the user's cursor.
  503. - **Root cause (hybriel #32)**: the new avatar preview was `if (hasAvatarPreview) { img {...} }`
  504. placed right after the avatar input, in the SAME `View` as the other four text inputs. Removing
  505. just that `if` (nothing else) fixed typing immediately — confirmed with a minimal repro
  506. (type one char, check focus survives) before and after. hl:webex recreates a View's elements on
  507. re-render when it contains a conditional block, even one that never flips during the interaction.
  508. - **Fix**: `img` is now always rendered; visibility toggles via a `hidden` CSS class driven by a
  509. plain reactive field (`avatarPreviewClass = hasAvatarPreview ? 'avatar' : 'avatar hidden'`) —
  510. no `if` in the view. `#avatarpreview.hidden { display: none }` added to `styles.hl`. An `img`
  511. with an empty `src` issues no network request per the HTML spec (confirmed: no console errors).
  512. - Avatar is stored as an http(s) URL (≤ 2000 chars), same shape as a notification's icon
  513. (`notify.hl checkUrl`) — no upload infrastructure exists anywhere in this app, decided as the
  514. smallest fit for the concept's silence on how the picture gets there.
  515. - Extended `tests/browser.mjs` (83 → 92 checks) with: live preview on typing a valid URL, the saved avatar showing as a round
  516. image in the identity list, reopening the edit form re-showing the saved URL, a two-keystroke
  517. focus-survival check (guards the regression above from coming back unnoticed), clearing the
  518. field hiding the preview, and four `checkFields` negatives (no scheme, non-http(s) scheme,
  519. too long, wrong type). Used a `paste()` helper (one `input` event with the whole URL) instead
  520. of the existing char-by-char `type()` for the avatar field — typing a URL character by character
  521. makes the live preview fetch every half-typed prefix (`h`, `ht`, `htt`, …), each a real 404;
  522. a paste (or most real user input) sends the whole value in one event.
  523. - Verified: `node tests/browser.mjs` → **92 passed, 0 failed**. Full regression, unchanged:
  524. `tests/apps.mjs` 97/97, `tests/selector.mjs` 99/99, `tests/notify.mjs` 130/130,
  525. `tests/migration.mjs` 23/23, `tests/hardening.mjs` 15/15.
  526. - Not done: apps still don't receive the avatar (blocked on ticket #11, on hold, unchanged from
  527. the prior worker's decision — correct, left alone). `.scratch/probe15/` (this session's repro
  528. scripts) removed after use — nothing left running, no orphan `bin/hybriel` processes.

Branches

Latest commits

  • f8bdcbc2ident: Hybriel master 06617221 (plugin allocators 3a781359 + 413f60e4, mpackdb 2cb7ae5e, http1 773de63e); all gates greenmre
  • ff78726cident: Hybriel master 190aa11d (fc838894 GC correctness, #127, #126 closure scopes); gates all greenmre
  • a3a7d21aident: Hybriel master 8efba065 (#126 GC by bytes, #48 lambda params copy); session-writing lambdas take &sessionmre
  • 98226b41antcolony#40: mission references point to the moved missionsmre
  • ff805b9aantcolony#40: history (LOG.md), worker briefs (missions/) and reports moved here from antcolony, numbered per project; old numbers in antcolony docs/mission-map.mdmre
  • 51a7bcdfident: Hybriel master 73267707 (#122); /code uses the new page() signature; pending address passed as parameter; once-checksmre
  • 836f644fident#24: installable app (manifest, service worker, data-free offline /start), own iconmre
  • 8bebbbf2deploy.sh: back up live storage/.sessions/.env before every deploy (newest 5 kept)mre
  • cc063ea2deploy.sh: never send .git or .gitignore to Byrodinmre
  • 81b15b7bState of 2026-09-27, before the move to gitoriamre