gitoriaLog in with ident

ident

All repositories: gitoria

ReadmeCodePull requestsReleasesTicketsSettings
Commita3a7d21aa3a7d21aident: Hybriel master 8efba065 (#126 GC by bytes, #48 lambda params copy); session-writing lambdas take &sessionmrea3a7d21a/STATUS.md

44.9 KB

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

Branches

Latest commits

  • 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