gitoriaLog in with ident

tickets

All repositories: gitoria

ReadmeCodePull requestsReleasesTicketsSettings
Commitbb64d57cbb64d57cmission 012 (ticket #25 API part): every /api/projects/<x>/… route takes the project's id (12-char record id) as well as its slug, same answers byte for byte; tickets stay under their project, no global /api/tickets/<x>. gate 315/0 (old code: the 13 id checks fail), connect 60/0, live-data copy old vs new 1,484 identical, slug vs id 772 identicalmrebb64d57c/STATUS.md

54.7 KB

  1. ## 2026-10-04 — mission 012: ticket #25 API part — a project by its id (`/api/projects/<id>/…`) — NOT deployed by me
  2. Every `/api/projects/<x>/…` route takes the slug (also an old one) or the project's id (its 12-char record id, e.g.
  3. `0mufbw18nsuj` = tickets.worldapi.org; `GET /api/projects` → `details[].id`). Tickets stay under their project; no new global
  4. `/api/tickets/<x>`. All existing paths answer as before. Pages unchanged. README "Short URLs (ticket #25)".
  5. - Files: lib/projects.hl `slugOfRef` (id → current slug, slug first), lib/api.hl `slugOf(route)` in `byNumber` + the 7 route functions
  6. under /api/projects/:slug (all 16 routes), tests/browser.mjs (+22 "ids (#25)" checks; PWA list count +1), new tests/projectids-realdata.py.
  7. - Tests (ports 8760–8764, 8767–8769): gate **315 passed, 0 failed** (`.scratch/w091/gate.sh <out>`); control = the same gate
  8. on the old code: 286 passed, 13 failed (exactly the id checks) then stops; connect.mjs (`.scratch/w091/connect.sh <out>`)
  9. **60 passed, 0 failed** ×2 (a first run timed out once at the connect page's new-project field — UI, untouched; flake).
  10. Real-data copy (Byrodin 2026-10-04 ~05:00, deleted after): `tests/projectids-realdata.py` old :8763 vs new :8764 →
  11. 1,484 answers identical, 772 slug-vs-id answers identical, unknown id 404, **0 FAILED** (README "Project ids on real data").
  12. letcount: never-reassigned `let` 0.
  13. - Open: deploy (`./deploy.sh`, architect); no data change. t.py / watch.py may switch to ids when wanted.
  14. ## 2026-10-03 — mission 011: ticket #25 short URLs (web part) — NOT deployed by me, API part waits for the creator
  15. Pages: project `/<slug>`, ticket `/<slug>/<number>`; `/projects/<slug>`, `/projects/<slug>/<n>`, `/projects/<slug>/tickets/<n>` and
  16. `/tickets/<old n|uuid>` answer 301 to them (current slug; nothing named → 404). API paths unchanged; the page links in its rows
  17. (`href`, `projectHref`, Markdown URL lines) are the short ones. Reserved slugs (`lib/projects.hl reservedSlugs`) refused at
  18. creation and slug change. Details + why the slug routes are moved behind hl:web's own urls: README "Short URLs (ticket #25)".
  19. - Files: project.hl (routes, the reorder after `new WebFramework`), lib/util.hl (hrefs), lib/api.hl + api-helpers.hl (301s),
  20. lib/projects.hl (reserved), components settings / ticket / ticket_list (links, comments), tests/browser.mjs (+44 checks),
  21. tests/connect.mjs (2 page URLs), new tests/shorturls-realdata.mjs.
  22. - Tests (ports 8760–8764, 8767–8769): gate **293 passed, 0 failed** (249 before; `.scratch/w083/gate.sh <out>`); connect.mjs
  23. (temp copy, app URL :8762, Chrome 8767–8769, `.scratch/w083/connect.sh <out>`) **60 passed, 0 failed**; control without the
  24. reorder: 35 passed, 15 failed. Live-data copy (Byrodin 2026-10-03 ~20:20, deleted after): `tests/shorturls-realdata.mjs`
  25. **187 passed, 0 failed** (11 projects, 20 tickets, every old form + followed); the ticket's own test (`/tracker.worldapi.org`,
  26. `/tracker.worldapi.org/31`, both old forms 301) by curl; API old vs new: 1,348 answers, 0 different beyond the page links.
  27. Collisions with reserved names on live data: none (no old slugs either). letcount: never-reassigned `let` 0.
  28. - Open: the API part (`/api/tickets/<x>`, `/api/projects/<id>`) — the creator must say whether `<x>` is a global id.
  29. After the deploy: links in old messages keep working by 301; t.py / watch.py use the API, unaffected.
  30. ## 2026-10-03 — mission 010: code order (antcolony docs/code-order.md), no behaviour change — NOT deployed by me
  31. Commits on main 97e269b (moves), e9d5c61 (one lib/ file per topic, project.hl = map, thin routes/faces), a75e027 (`let`), + docs.
  32. - Root: 14 `.hl` → only `project.hl` (621 → 144 lines: index comment, config, routes, audience). Layout + import order: README "Files".
  33. - `store.hl` split: `lib/projects.hl` (projects, members, roles), `lib/tickets.hl` (tickets + relations, all their writes,
  34. `removeMember` incl. the unassign, `ticketRights`), `lib/events.hl` (history; `putEvent`), `lib/tickets-helpers.hl` (state names,
  35. ref / relation view / filter links). `lib/util.hl` = old localtime.hl + the copies of env / storage dir / countOf / first /
  36. merged / sorts / text checks that were in store.hl and users.hl. The API routes moved from project.hl to `lib/api.hl`
  37. (auth, filters, Accept, HTML page in `lib/api-helpers.hl`); invite checks (`mayInvite`) and the invite join (`joinByInvite`)
  38. moved out of project.hl / the settings face into `lib/invites.hl`. `import.hl` → `tools/import.hl`, `styles.hl` → `components/`.
  39. - `let`: 680 → 219 (136 reassigned + 83 re-bound in a loop body); 456 → plain declarations (`tests/letcount.py .` → never-reassigned 0).
  40. - Tests (ports 8750–8759): gate **249 passed, 0 failed**; connect.mjs (temp copy, app URL :8752, Chrome 8753–8759) **60 passed,
  41. 0 failed**; live-data copy (README "Test" → "Same output", copy of Byrodin storage 2026-10-03 19:3x): 2,172 reads (every page
  42. signed in as az5b2 + signed out, every API read JSON + Markdown, refusals) **0 different** (1,460 byte-identical, 712 differ
  43. only by source positions / hashes / seed time), 72 write answers (API + faces) **0 different**; tools/fix-import-summaries on
  44. a copy = same output as the old tree (10 fixed, 40 fine); tools/import.hl reads `import/tickets` by default (gate).
  45. Default storage path checked: a server started WITHOUT TICKETS_STORAGE reads `storage/mpackdb` of the app (11 projects).
  46. - Found on the way (Hybriel, not app bugs): see Lessons (loop-body declarations, instance inside a copied hybrid, import cycles).
  47. ## 2026-10-03 — antcolony mission 074: re-vendored to hybriel master 06617221 (plugin allocators 3a781359 + 413f60e4) — NOT deployed by me
  48. bin sha256 21059cc7…d77bdb, same plugin set (old copy `.scratch/pre-074/` = 190aa11d). No app code change. Gate (ports in README
  49. "Vendored Hybriel") → **249 passed, 0 failed**; connect.mjs (temp copy, 8760–8769) → **60 passed, 0 failed**. Real-data memory
  50. (`.scratch/w074/mem.mjs`, LONG=1, 69 URLs): new 315 → 335 → 312 → 315 MiB after 5700 pairs, swap 0, ~31–38 ms per pair —
  51. **flat now** (072's slow growth is gone); old 190aa11d 319 → 743 MiB, 40–47 ms per pair.
  52. ## 2026-10-02 — antcolony mission 072: re-vendored to hybriel master 190aa11d (GC correctness fc838894, #126 closure scopes, #127) — NOT deployed by me
  53. bin sha256 860f5e61…0a23626, same plugin set (old copy `.scratch/pre-072/` = 7eea0d32). No app code change. Gate (same ports as below)
  54. → **249 passed, 0 failed**; `tests/connect.mjs` (temp copy, app URL :8722, Chrome 8723–8729, ports 8720/8721) → **60 passed, 0 failed**.
  55. Real-data memory (`.scratch/w072/mem.mjs`, SSR page + API read pairs over 69 URLs): new 315 MiB boot → 355 (200) → 418 (1200) →
  56. 706 MiB (5700), ~29 ms per pair, flat timing; old 697 → 1365 → 1719 MiB, ~79 ms per pair. Still slow growth (~0.065 MiB/pair,
  57. was 0.26 on 7eea0d32) — watch live RSS after the deploy.
  58. ## 2026-10-02 — antcolony mission 069: re-vendored to hybriel master 7eea0d32 (#126 memory, #48 lambda copy) — NOT deployed
  59. bin sha256 f0d3019f…fcc4bc734a0, plugins core crypto data fetch fs http http1 mpackdb proc time web (old copy `.scratch/pre-069/`).
  60. #48 fix: `migrate.hl` migrate* lambdas take `&log` (first gate run without it: 248/1 "migration: … log says what was migrated").
  61. Gate (ports 8720–8729): `TICKETS_GATE_PORT=8720 TICKETS_GATE_IDENT_PORT=8721 TICKETS_GATE_EXCHANGE_PORT=8722 TICKETS_GATE_CHROME_A=8723-8724
  62. TICKETS_GATE_CHROME_B=8725-8726 TICKETS_GATE_CHROME_C=8727-8729 node tests/browser.mjs` → **249 passed, 0 failed** (ident's current folder works again).
  63. `tests/connect.mjs` (temp copy, app URL :8722, Chrome 8723–8729, `TICKETS_CONNECT_PORT=8720 TICKETS_CONNECT_IDENT_PORT=8721`) → 60/60.
  64. Memory (copy of live storage, `.scratch/w069/mem.mjs <tag> <tree> <port>`, LONG=1 for more rounds; one round = N SSR pages + N API reads):
  65. RSS old 73267707 / new 7eea0d32 — boot 1541 / 701 MiB, after 200: 2654 / 1114, after 1200: 2874 / 1679, after 2700: 3173 / 2445,
  66. after 4200: 2403 / 2840 MiB. Boot and the first 2700 clearly lower, but new still grows ~0.26 MiB per load pair at 4200 (no plateau seen) →
  67. watch live RSS after a deploy (`docker stats --no-stream | grep tickets` on Byrodin; live was 8.76 GiB on 2026-10-02 before).
  68. ## 2026-10-01 — antcolony mission 048 (2nd try): re-vendored to hybriel master 73267707 (≥ 4850d798, #122 fixed) — NOT deployed
  69. bin sha256 9707e0cc…a81be534f, plugins core crypto data fetch fs http http1 mpackdb proc time web (old copy `.scratch/pre-048/`).
  70. #122 repro `.scratch/w048/repro/` (`node repro.mjs bin/hybriel`, port 8732, Chrome 8735–8739) → stays "you may edit" after the face.
  71. Gate (ports 8732–8739, ident snapshot — README "Test" → "Ident for the gate"):
  72. `TICKETS_GATE_IDENT_DIR=$PWD/.scratch/w048/ident-snap TICKETS_GATE_PORT=8732 TICKETS_GATE_IDENT_PORT=8733 TICKETS_GATE_EXCHANGE_PORT=8734
  73. TICKETS_GATE_CHROME_A=8735-8735 TICKETS_GATE_CHROME_B=8736-8736 TICKETS_GATE_CHROME_C=8737-8739 node tests/browser.mjs` → **249 passed, 0 failed**
  74. (3 runs; all 4 "once:" checks green incl. bob once in settings). `tests/connect.mjs` (temp copy, Chrome 8735–8739, app URL :8734) → 60/60.
  75. With ident's CURRENT folder (ident itself on master) the gate stops at "ident /code page" — an ident issue, not tickets.
  76. Removed: `migrate.hl` `compactNow()` block (#110) — master 249/0 without it; 837fe120 without it fails (control). Nothing else to remove
  77. (no #115/#116 statics, no #118 detours); no client-built SVG. Duplicate @id: counter `.scratch/w048/dupcount.py <storage dir>` (validated on
  78. an injected dup); this try's live tar was spent on a framing bug in the first version — numbers from the first try this morning: 0 everywhere.
  79. ## 2026-10-01 — antcolony mission 048: re-vendor to hybriel master ff51cf46 STOPPED (Hybriel bug) — restored to 837fe120
  80. Tried master ff51cf46 (ReleaseFast, read-only archive): gate 212 passed, 1 failed — settings "Save" turns the admin page into
  81. "Only an admin of the project can open its settings". CAUSE (hl:web, 0b17f65b + 64527baa): after ANY face that takes `session`,
  82. WebFramework.hl answers with `ack.sync` built from `mount(key, {}, s, …)` — the component re-mounted WITHOUT its route params, so
  83. every server reference site reading session (`isAdmin = … isAdminOf(project.id, me)`) is computed with `projectSlug = null` →
  84. false, and the browser takes it. Minimal repro: `.scratch/w048/repro/` (`node repro.mjs <bin>` in a tree with master's plugins:
  85. "you may edit" → "you may not edit" after a face; 837fe120 keeps it). Restored `bin/` + `plugins/` from `.scratch/pre-048/`;
  86. gate on the restored copy (ports 8732–8739, A/B one port each) → 245 passed, 0 failed. Logs: `.scratch/w048/`.
  87. Live data (copy, deleted): no duplicate @id in any table (#113). migrate.hl compactNow() workaround (hybriel #110, master has it)
  88. KEPT — not provable without the new binary. Not deployed.
  89. Follow-up (lead: since 64527baa a session face also syncs session-derived members → a handler that appends the face's row shows
  90. it twice, as in gitoria): tickets never appends a face's answer to a synced list (comments/state events arrive by push into `events`,
  91. which reads no session; list/inbox upsert by id; tokens/members/choices are replaced from the answer). Gate +4 "once:" checks
  92. (comment once in A and B, state change once, bob once in the settings members). 837fe120: 249 passed, 0 failed. Master copy
  93. (throwaway): the 3 comment/state ones green, members unreachable (stops at the settings bug) — `.scratch/w048/gate-once-*.txt`.
  94. ## 2026-10-01 — antcolony mission 046: installable app (PWA) + own icons — NOT deployed, awaiting architect / creator
  95. Done: `project.hl` (appIcons, appTouchIcon, appFavicon, theme = header darker rgb(15, 20, 25), `offline = [ TicketList ]`, icon routes,
  96. real `/favicon.ico`), `components/main.hl` + `styles.hl` (the "You are offline" note via the net-probe animation tick),
  97. `icons/` (ticket stub, svg source + PNGs + ico), gate part "PWA" in `tests/browser.mjs` (README "PWA", "Test").
  98. Gate (ports 8750–8759, command in README "Test") → **245 passed, 0 failed** (was 233); `tests/connect.mjs` 60/60 (run on a
  99. temp copy with Chrome 8757–8759). Looked at: icon 48/192/512 + maskable crop, 390 px online/offline shots — /tmp/w046-tickets/ on Loreana.
  100. Gate race fixed: "inbox follows the state change live" failed ~1/5 (API write overtook the tab's hello; the worker's extra
  101. requests widened the window) → `connected()` now waits for a pong after the hello. 12 runs green (6 on 8740–8749, 6 on 8750–8759).
  102. Open: offline `/` shows the list as last loaded (stale by design); verify install on a real phone after deploy.
  103. ## 2026-09-26 — ticket #23: connect protocol documented for app workers
  104. New `docs/connect-apps.md` (steps, exchange, key headers, create-ticket / comment / state calls); linked from the README. No code change.
  105. # STATUS
  106. ## 2026-09-26 — ticket #21: connect an app to a project (gitoria's tickets) — NOT deployed
  107. Done: `connections.hl` + `components/connect.hl` + routes (`/connect`, `/connect/:nonce`, `/api/connect/exchange`, `…/connections`, `…/connections/remove`), the
  108. connections line + Disconnect on the project page, per-project key `tktc_…` (create / comment / state only, roles of the NAMED person via
  109. `X-Tickets-Identity`). README section "Connecting an app to a project". `node tests/connect.mjs` → 60 passed, 0 failed. NOT run: the old gate
  110. `tests/browser.mjs` / `short-id-switch.mjs` (they need a copy of ident's code, `ident.worldapi.org` is not on this machine); the only shared-code
  111. change is `apiAuth` in `project.hl` (token users behave as before — checked in the new gate). Gitoria's side (button "Connect tickets", storing the key,
  112. sending the events) is gitoria's ticket. On Byrodin set `TICKETS_CONNECT_ORIGINS` only if a non-worldapi.org app must connect.
  113. ## 2026-09-26 — ticket #22: every event in the API carries `userId` — NOT deployed
  114. `store.hl eventRowOf` adds `userId` (the users @id of the author; `''` for events from before the login). `author` (display name) unchanged. Checked with a real write on a fresh store (`.scratch/m22/t.hl`: user, project, ticket, comment): both events show `userId` = the author's users @id. Not covered by the gate.
  115. ## 2026-09-26 — ticket #20: projects as own data, members, roles, new states — NOT deployed (architect: together with the scheduler update)
  116. Took over the unfinished worker session s-20260926T1044-278e20 (store/projects/members/settings/invites/inbox code was written, the gate not run).
  117. Fixed: the migration's replaced records left tombstones and the page realm's second open compacted the files under the first handle (every slug
  118. lookup 404'd after the first page render) → `migrate.hl` compacts the changed tables. Gate: `tests/browser.mjs` → 233 passed, 0 failed (roles,
  119. states + aliases, inbox, project create / settings / slug change / members / invite accepted through ident, layouts of the new pages);
  120. `tests/short-id-switch.mjs` (ports 8706/8707) → 19 passed, ported to the roles. The migration was also run on a copy of the dev store (5 projects,
  121. 47 tickets: 38 open, 9 done). Parked: the old browser checks (`tests/browser-pre20-parked.txt`). Not done: live push of a role change to an open page
  122. (a page needs a reload after an admin changes its role), the "answered" state of #19 is gone (replaced by these states).
  123. ## 2026-09-26 — antcolony mission 036: on hybriel master 837fe120 (hl:web, no local patch) — NOT deployed, awaiting architect
  124. Still carries the UNDEPLOYED #19 state (see Byrodin STATUS: never deploy tickets before #19 is decided).
  125. Switched: plugins/webex + both LOCAL PATCHes gone → master's plugins/web; `'hl:web'` / `'hl:web/css'` imports;
  126. NativeWebSocketServer/bindHost gone (hl:web reads HL_HOST); /login/callback uses `req.session` (still mints +
  127. Set-Cookie when the browser has none); `realSession()` gone (`session == null`); `encode` = `encodeURIComponent`;
  128. import.hl `after` = `a > b`; jsoncheck accepts a VALID \u surrogate pair (lone ones still 400 — JSON.parse still
  129. aborts on them, checked with the new binary); shared/tokens.hl = ident's/gitoria's hl:web copy. Gate: the 12 forged
  130. checks accept hl:web's own refusal (ack `ok:false`, "the `session` parameter is filled by the server"); surrogate cases
  131. updated (+2 checks: pair passes, high+non-low refused). `node tests/browser.mjs` → **423 passed, 0 failed** (incl.
  132. the #104 text-order check "the form says which ticket it opened"), no Chrome left. Served page:
  133. `/__hl/{hl-runtime.js,web/client.js,app.css}?v=ead0a6f2dfc83077`, 0 unhashed refs, hashed = immutable, bare = no-cache.
  134. Backup of the old state: `.scratch/pre-036/` (bin, plugins, src, tests). Kept: jsoncheck, localtime, hand sorts,
  135. login.js guard (JS untouched).
  136. ## 2026-09-25 — antcolony mission 035: re-vendor to hybriel master 13ef4f9b STOPPED (Hybriel bug) — code NOT touched (only this note)
  137. Probe in a copy (loreana `~/scratch-035/tickets-probe` deleted; recipe: rsync without .env/storage, bin+plugins from master,
  138. `'hl:webex'`→`'hl:web'`, `'hl:webex/css'`→`'hl:web/css'`, gitoria's `shared/tokens.hl`): gate 408 passed, 13 failed. (1) HYBRIEL
  139. BUG: an element built in the browser when its `if` turns true puts a child element BEFORE the bound text: `p { notice " " a {…} }`
  140. in ticket_list.hl renders `<a>open it</a>Opened hybriel #1. ` (server render is right). Repro: loreana
  141. `~/scratch-035/repro/home-textorder.hl`. (2) 12 forged-session checks expect an app `{error}`; hl:web now refuses the extra
  142. argument itself (ack `ok:false`) → the checks must accept that. Workarounds to drop at the real re-vendor: webex LOCAL PATCHes,
  143. NativeWebSocketServer listener (HL_HOST), realSession(), charCodeAt ordering, jsoncheck surrogate refusal (#15), hand URL encoders.
  144. ## 2026-09-25 — ticket #19 "answered" state — awaiting architect (deploy) / creator
  145. New state `answered`. When the creator comments on a ticket in `awaiting creator`, the store (`addComment`)
  146. also writes a state event → `answered` (goes back to whoever asked, leaves the inbox). Other users' comments
  147. change nothing; "confirmed" stays "tested, works". The asker sets `awaiting creator` again as before.
  148. API POST comments and the page face push both events; state badge colour + filter added. Gate: new #19 checks.
  149. DECISIONS.md not touched (architect/librarian).
  150. ## 2026-09-24 — ticket #12 Markdown source view (antcolony mission 025) — awaiting architect (deploy) / creator
  151. Follow-up to mission 009 (old 024): the creator asked (ticket #12, after "awaiting creator") for "a switch
  152. source / markdown at the bottom" to copy/paste or hand-edit the raw Markdown.
  153. Done (worker; edited `/media/STORAGE/projects/worldapi-components/md-editor.js` directly — a plain
  154. JS asset, no `.hl` dev-server-reload risk — then `cp` into `shared/md-editor.js` here; no tickets
  155. `.hl`/faces/API/data changed, so nothing else needed a dev-copy round-trip):
  156. - `<md-editor>` gets a footer row at the bottom with a "Markdown source" button: swaps the visual
  157. editor for a plain textarea with the raw Markdown (copy/paste or hand-editing), "Visual editor"
  158. swaps back (re-parses the text). Same `input`/`change` events, Ctrl+Enter still sends the form.
  159. Hidden together with the toolbar when `toolbar="none"`. Details: worldapi-components README
  160. "Source view" / STATUS antcolony mission 025.
  161. - Component test (worldapi-components) → **81 passed, 0 failed** (72 before + 9 new checks), 83
  162. with `ORACLE_APP`. Screenshot `worldapi-components/.scratch/test/md-editor-source-view.png`.
  163. - This app's gate `node tests/browser.mjs` (ports 8700-8729, this session's range) → **418 passed,
  164. 0 failed**, unchanged from mission 009 (old 024) (no new tickets-side checks added — the feature lives
  165. entirely in the vendored component; the existing `#12` checks already cover the enhanced/standalone
  166. editors and keep passing with the new footer present).
  167. How to verify (ports 8700-8749 only):
  168. ```bash
  169. cmp /media/STORAGE/projects/worldapi-components/md-editor.js shared/md-editor.js && echo same
  170. cd /media/STORAGE/projects/worldapi-components && HL_CHROME=/opt/google/chrome/chrome node test/run.mjs # 81 passed
  171. cd /media/STORAGE/projects/tickets.worldapi.org
  172. TICKETS_GATE_PORT=8700 TICKETS_GATE_IDENT_PORT=8701 TICKETS_GATE_EXCHANGE_PORT=8702 \
  173. TICKETS_GATE_CHROME_A=8710-8719 TICKETS_GATE_CHROME_B=8720-8729 node tests/browser.mjs # 418 passed
  174. ```
  175. Manual (creator): open a ticket → Comment field → click "Markdown source" at the bottom → the box
  176. becomes a plain textarea with the Markdown → edit it → click "Visual editor" → it renders again.
  177. Not deployed (Byrodin container untouched, live app on tickets.worldapi.org still the pre-mission-025
  178. version); the architect deploys with `./deploy.sh` after the creator confirms.
  179. ## 2026-09-24 — ticket #12 Markdown editor (mission 009 (old 024)) — awaiting architect (deploy) / creator
  180. Done (worker; developed in `.scratch/dev-m024`, copied back, NOT deployed):
  181. - New project `/media/STORAGE/projects/worldapi-components/` with `<md-editor>` (its README/STATUS/test).
  182. - Vendored as `shared/md-editor.js`, route `/md-editor.js`, script in the shell; new-ticket summary, edit
  183. summary, comment and state note (input → textarea rows 1) wrapped in `mdEditor { textarea {…} }`;
  184. `styles.hl` sets the `--md-*` tokens. Faces/API/data unchanged. README "Markdown editor".
  185. - Gate `node tests/browser.mjs` → **418 passed, 0 failed** (402 old + 16 new `#12` checks; dev copy and main folder).
  186. - Component test (in worldapi-components, with this app's markdown.hl as oracle) → 72 passed, 0 failed.
  187. - Old files before the copy-back: `.scratch/m024/pre-copy-backup/`.
  188. How to verify (ports 8800-8849 only):
  189. ```bash
  190. cd /media/STORAGE/projects/tickets.worldapi.org
  191. cmp shared/md-editor.js ../worldapi-components/md-editor.js && echo same
  192. bash .scratch/m024/gate-tickets.sh .scratch/m024/g.txt; tail -2 .scratch/m024/g.txt # 418 passed (ports 8810-8812, Chromes 8835-8844)
  193. cd ../worldapi-components && ORACLE_APP=../tickets.worldapi.org HL_CHROME=/opt/google/chrome/chrome node test/run.mjs # 72 passed
  194. ps -eo pid,args | grep [h]l-browser-tier | grep remote-debugging-port=88 # empty
  195. ```
  196. Screenshots: `.scratch/gate-{phone,desktop}-mdeditor.png`, `.scratch/gate-phone-newticket-mdeditor.png`,
  197. `../worldapi-components/.scratch/test/md-editor-{phone,desktop}.png`.
  198. Manual (creator): open a ticket → Comment: type `**bold**` → it turns bold; tap B / list on the phone;
  199. Ctrl+Enter sends; Edit → the summary shows formatted, Save.
  200. Lessons: `<md-editor>` is form-associated, so a wrapping `<label>` activates IT (not the textarea);
  201. Hybriel's JSON.stringify sorts object keys (compare key-sorted); a `<textarea>` turns CR LF into LF
  202. when its value is set by script.
  203. ## 2026-09-24 — design tokens AS DESCRIBED (ticket antcolony#3, antcolony mission 021) — awaiting architect (deploy) / creator
  204. antcolony Mission 018 (generated `':root'` copy + sync.sh) was REJECTED by the creator; antcolony mission 021 replaces it.
  205. Done (worker; developed in `.scratch/dev-m021`, copied back, NOT deployed):
  206. - `shared/tokens.hl` = hand-written webex `var()` tokens (`static dark = var('rgb(25, 30, 35)')`, semantic
  207. `colorText = var(light)` …, incl. `colorAccentText` moved here from the app), verbatim copy of
  208. `/media/STORAGE/projects/worldapi-tokens/tokens.hl`. `styles.hl` member-imports them and uses them as
  209. members; sets only `colorAccent = var(purple)`. README "Design tokens".
  210. - webex LOCAL PATCH (antcolony mission 021, hybriel#39) in `plugins/webex/WebFramework.hl`; XSS patch kept.
  211. - Old files: `.scratch/m021/pre-copy-backup/` (styles.hl, shared/tokens.hl, WebFramework.hl); antcolony mission 018's
  212. worldapi-tokens folder (sync.sh, tokens.css, generated tokens.hl): `tickets.worldapi.org/.scratch/m021/worldapi-tokens-m018-backup/`.
  213. - Rendered colours unchanged: `/__hl/app.css` after `:root` byte-identical old vs new (in `:root` only
  214. the order of `--color-accent-text` / the app's `--color-accent` changed); Chrome computed colours
  215. of every element + every token, tickets list/inbox/state/ticket/you + ident home/code/account/apps/inbox,
  216. 390 + 1280: 23 passed, 0 failed.
  217. - Gate `node tests/browser.mjs` (ports remapped: `.scratch/m021/gate-tickets.sh`) → **402 passed, 0 failed** (dev copy and main folder).
  218. - Deploy simulation (deploy.sh excludes + debian:12-slim container): same app.css, byte-identical.
  219. How to verify (ports 8850-8879 only; scripts in tickets.worldapi.org/.scratch/m021 unless noted):
  220. ```bash
  221. cd /media/STORAGE/projects/tickets.worldapi.org
  222. for a in tickets ident; do cmp /media/STORAGE/projects/worldapi-tokens/tokens.hl ../$a.worldapi.org/shared/tokens.hl && echo same $a; done
  223. 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>)
  224. 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
  225. cd /media/STORAGE/projects/ident.worldapi.org; bash .scratch/m021/gate-ident.sh . .scratch/m021/gates-main # 6 suites green
  226. bash /media/STORAGE/projects/tickets.worldapi.org/.scratch/m021/deploysim.sh <tickets dir> <ident dir> # needs cmpup's run/main-*.css
  227. ps -eo pid,args | grep [h]l-browser-tier # empty
  228. ```
  229. Screenshots: `tickets.worldapi.org/.scratch/m021/shots/<app>-<old|new>-<page>-<390|1280>.png`.
  230. Lesson: an imported `static` var() token works only with the antcolony mission 021 webex patch; tokens.hl changes need a restart.
  231. ## 2026-09-24 — shared design tokens (ticket antcolony#3, antcolony mission 018) — REJECTED by the creator, superseded by antcolony mission 021
  232. Done (worker; developed in `.scratch/dev-m018`, copied back, NOT deployed):
  233. - Tokens now come from `shared/tokens.hl` (generated from layouts `tokens.css` by
  234. `/media/STORAGE/projects/worldapi-tokens/sync.sh`, vendored like plugins/); `styles.hl` inherits it and
  235. sets only the accent (README "Design tokens"). Old copy of `styles.hl`: `.scratch/m018/pre-copy-backup/`.
  236. - Every rendered colour unchanged: `/__hl/app.css` after `:root` byte-identical old vs new; computed
  237. colours of every element (tickets list/inbox/state/ticket/you, ident home/code/account/apps/inbox, 390 +
  238. 1280) old == new in Chrome.
  239. - Gate `node tests/browser.mjs` → **402 passed, 0 failed** (dev copy and main folder).
  240. How to verify (ports 8850-8879 only):
  241. ```bash
  242. cd /media/STORAGE/projects/tickets.worldapi.org
  243. /media/STORAGE/projects/worldapi-tokens/sync.sh --check # ok ×3
  244. 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-*)
  245. 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
  246. cd /media/STORAGE/projects/ident.worldapi.org; bash .scratch/m018/gate-ident.sh . .scratch/m018/gates-main # 6 suites green (ports remapped copies)
  247. bash /media/STORAGE/projects/tickets.worldapi.org/.scratch/m018/deploysim.sh <tickets dir> <ident dir> # deploy rsync + container: same app.css
  248. ps -eo pid,args | grep [h]l-browser-tier # empty
  249. ```
  250. Screenshots: `tickets.worldapi.org/.scratch/m018/shots/<app>-<old|new>-<page>-<390|1280>.png`.
  251. Lesson: a `var()` token of hl:webex/css can't cross an `inherit` (second instance) — use literal `':root'` strings.
  252. ## 2026-09-24 — ticket #4 (mission 008 (old 017)): parent / child + blocked-by — awaiting architect (deploy) / creator
  253. Done (worker; developed in `.scratch/dev-m017`, copied back, NOT deployed to Byrodin):
  254. - **Relations** (README "Relations"): a ticket has ≤ 1 parent and any number of blockers, across
  255. projects; parent page lists children + states, child shows "Part of …"; "Blocked by" / "Blocks" on
  256. both sides. New table `storage/mpackdb/links.*` (created empty — no migration); every change is a
  257. history event `kind:"link"` on the child / blocked ticket.
  258. - **Who may**: the author of either ticket or the creator (legacy tickets without author: only the
  259. creator or the other ticket's author). No self-links, no loops (also transitive), no duplicates.
  260. - **Parent state**: `allChildrenConfirmed` (children exist and all `confirmed`) → page "all children
  261. confirmed (n)" (green) / "k of n children confirmed"; the parent's state is never changed.
  262. - **API**: `POST …/parent {parent}` (`""` removes), `POST …/blocked-by {add}|{remove}`, both URL
  263. forms; rows (list + single) carry `parent`, `children`, `blockedBy`, `blocks` (refs with states),
  264. `allChildrenConfirmed`. Markdown view: Parent / Children / Blocked by / Blocks blocks + list-line tails.
  265. - **Web**: forms "Parent ticket" (Set / Remove) and "Blocked by" (Add, Remove per row) for logged-in
  266. users; live push `ticketsRelated` after relation writes, state changes and edits.
  267. - Gate ports configurable (`TICKETS_GATE_CHROME_A/B` new); a `connected()` wait added before the
  268. pre-existing token "Revoke" click (it flaked once on the bare SSR button).
  269. - Gate `node tests/browser.mjs` → **402 passed, 0 failed** (dev copy 3× in a row, main folder 3×).
  270. How to verify:
  271. ```bash
  272. cd /media/STORAGE/projects/tickets.worldapi.org
  273. bash .scratch/m017/gate.sh .scratch/m017/g.txt; tail -2 .scratch/m017/g.txt # gate on ports 8800-8819 (or plain node tests/browser.mjs)
  274. ps -eo pid,args | grep [h]l-browser-tier # empty
  275. bash .scratch/m017/compat.sh # old vs new code on store copies: JSON equal minus the 5 new keys, Markdown identical, .mpack unchanged
  276. node .scratch/m017/identmigrate.mjs # rehearsal on a store COPY: ident #3–#8 under ident #1 (creator token 201 ×6, non-creator 403)
  277. ```
  278. Look at `.scratch/gate-{phone,desktop}-{parent,blocked}.png`.
  279. For the architect (after the deploy): link the ident pieces —
  280. `for n in 3 4 5 6 7 8; do curl -s -XPOST -H "Authorization: Bearer $TOKEN" -d '{"parent":"ident.worldapi.org#1"}' https://tickets.worldapi.org/api/projects/ident.worldapi.org/tickets/$n/parent; done`
  281. — ident #1 and #3–#8 were opened BEFORE the login (authors "import" / "architect" as text), so only
  282. the CREATOR's token may (a non-creator gets 403, rehearsed). Check: `curl -s -H 'Accept: text/markdown' https://tickets.worldapi.org/api/projects/ident.worldapi.org/tickets/1 | head -20`.
  283. Manual (creator): open a ticket you opened → "Parent ticket" field → `ident.worldapi.org#1` → Set parent;
  284. the parent page lists it under "Children".
  285. Lessons (mission 008 (old 017)):
  286. - `from` is reserved in Hybriel (parse error "Expected parameter name") — known (hybriel#22), bit again.
  287. - A gate click right after `goto` + `waitForSelector` can hit the SSR button before hydration → wait for
  288. the socket (`connected()`) before any click.
  289. - Loreana ports 8821 and 8830 are taken by other software (llama-server) — inside the 8800-8849 range.
  290. ## 2026-09-24 — tickets #8 (edit) + #6 (Markdown) + #11 (gate under load), mission 007 (old 014) — awaiting architect (deploy) / creator
  291. Done (worker; developed in `.scratch/dev-m014`, copied back, NOT deployed to Byrodin):
  292. - **SECURITY, found on the way (pre-existing, live too)**: hl:webex writes the page state (user
  293. text) into an inline `<script>` unescaped → a summary / comment containing
  294. `</script><img src=x onerror=…>` executes in every viewer's browser. Reproduced on the pre-m014
  295. code (`.scratch/m014/old-app`, probe ticket via `.scratch/m014/xssprobe.hl` on a store COPY →
  296. `xsscheck.mjs` `__pwned = 1`); fixed by a one-line LOCAL PATCH in `plugins/webex/WebFramework.hl`
  297. (`<` → `\u003c` in the seed) → `__pwned = undefined`, page hydrates. Deploy soon; ident and
  298. every other hl:webex app vendor the same code (hybriel issue to file).
  299. - **#8 editing** (README "Markdown, editing"): the author edits subject + summary (web "Edit",
  300. API `POST …/edit`); legacy tickets (no user on the created event): only the creator; else 403.
  301. Edit = history event `kind:"edit"` with old + new values, pushed live.
  302. - **#6 Markdown**: `markdown.hl` + `components/markdown.hl` render summary / comments / state
  303. notes safely (text nodes only, `safeHref`); `mdview.hl` = `Accept: text/markdown` on the ticket
  304. GETs. JSON unchanged (old vs new code on copies of the store: every GET byte-identical).
  305. - **#11 gate**: page waits × `TICKETS_GATE_SLOW` (default 3); console check before ident stops,
  306. browsers parked on `about:blank` during "ident down".
  307. - Gate `node tests/browser.mjs` → **335 passed, 0 failed** (dev copy and main folder).
  308. How to verify:
  309. ```bash
  310. cd /media/STORAGE/projects/tickets.worldapi.org
  311. node tests/browser.mjs # 335 passed; look at .scratch/gate-{phone,desktop}-{edit,markdown}.png
  312. ps -eo pid,args | grep [h]l-browser-tier # empty
  313. bash .scratch/m014/compat.sh # JSON DUMPS IDENTICAL, 47 tickets md+page 200, .mpack unchanged
  314. # #11 under load: a dev server on a store COPY (:8374), then
  315. node .scratch/m014/load.mjs http://127.0.0.1:8374 4 6 & # 4 Chromes x 6 tabs; kill %1 afterwards
  316. node tests/browser.mjs # 3x green (logs .scratch/m014/gate-load-*.txt, gate-heavy-*.txt)
  317. # old code under the same load: .scratch/m014/old-app → 274/1 (ERR_CONNECTION_REFUSED selector.js), 3 of 3 runs
  318. ```
  319. Manual (creator): open a ticket you opened after the login → "Edit" (top, under the dates) →
  320. change subject / summary (Markdown) → Save: the history shows "edited the ticket" with the
  321. previous version folded. `curl -H 'Accept: text/markdown' https://tickets.worldapi.org/api/projects/tickets.worldapi.org/tickets/6`.
  322. Old files before the copy-back: `.scratch/m014/pre-copy-backup/` (+ `.scratch/m014/old-app/plugins`).
  323. Lessons (mission 007 (old 014)):
  324. - A `#events li` selector (CSS or test) also hits the `<li>` of a Markdown list inside an event:
  325. use `#events > li`.
  326. - Hybriel string literals DO decode `\uXXXX` but not `\\`: a literal backslash is `'\u005c'`.
  327. - Never embed user text in an inline `<script>` without escaping `<` (webex did — see above).
  328. ## 2026-09-24 — tickets #9 + #10 (mission 006 (old 012)): login banner after logout, button returns to the page — awaiting architect (deploy) / creator
  329. Done (worker; developed in `.scratch/dev-m012`, copied back, NOT deployed to Byrodin):
  330. - **#9 cause (reproduced in headless Chrome, own ident + tickets, `.scratch/m012/repro.mjs`)**:
  331. hl:webex re-creates the shell header — `<ident-selector>` AND the `<script>` elements — every
  332. time the login state flips (login, logout); the browser runs the re-inserted `/login.js` again,
  333. and every run added one more `ident-login` listener to the (new) selector. After a logout the
  334. selector carried 2 listeners → one choice = 2 `change`s on `#identcode` = 2 `identLogin` faces =
  335. 2 exchanges of the SAME code at ident (200, then 400 "unknown or already used code" → banner).
  336. The first exchange had logged the session in as the chosen identity (B). Not identity-specific:
  337. A → Log out → A again showed the banner too; the first selector login after a page load never
  338. does (one listener) — that is why "with A alone" it never appeared. Navigation is NOT the cause
  339. (after an in-app click list → ticket, login.js had not run again); list, ticket page reached by
  340. click and ticket page loaded directly all showed it (old code: exchanges `[200, 400]`).
  341. - **Fix** `login.js`: installs once per document, `ident-login` listener on `document`, current
  342. `#selector` looked up each time (MutationObserver on the whole document), each code handed on
  343. once. After: exactly one exchange, no banner, logged in as B (all 3 scenarios).
  344. - **#10** the button returns to the page: `login.js` adds `?next=<path+query>` to the button's
  345. return URL at the click; `project.hl` `safeNext` + `/login/callback` → 302 there (same-origin
  346. path only, else `/`). ident keeps the return URL's query and appends `&ident_code=`.
  347. - Gate `node tests/browser.mjs` → **275 passed, 0 failed** (+25: #9 path with an exchange-counting
  348. proxy, #10 table + browser). With the OLD `login.js` the new #9/#10 checks FAIL (negative control).
  349. How to verify:
  350. ```bash
  351. cd /media/STORAGE/projects/tickets.worldapi.org
  352. node tests/browser.mjs # 275 passed; grep '#9\|#10' in the output
  353. ps -eo pid,args | grep [h]l-browser-tier # empty
  354. SCEN=ticket-nav node .scratch/m012/repro.mjs # "B login: exchanges=1 ..." ; banner null
  355. SCEN=list node .scratch/m012/repro.mjs .scratch/m012/old-app # the OLD login.js: "B login: exchanges=2", banner
  356. ```
  357. Old files before the copy-back: `.scratch/m012/pre-copy-backup/`. Gate logs `.scratch/m012/gate-*.txt`.
  358. Manual (creator): log in with the selector, Log out, choose another identity → no red banner,
  359. top right the other identity; log out on a ticket page, "Log in with ident" → back on that page.
  360. ## 2026-09-24 — ticket #7 (mission 005 (old 011)): login via ident — awaiting architect (deploy) / creator
  361. Done (worker; developed in `.scratch/dev-m011`, NOT deployed to Byrodin):
  362. - **Login** (README "Login (ident)"): ident's selector top right + "Log in with ident" button;
  363. the server exchanges the one-time code (`users.hl` exchangeCode, IDENT_API_KEY/SECRET) for the
  364. per-app identity id; `login.js` bridges the selector's `ident-login` event into the shell's
  365. face `identLogin` (no reload); `/login/callback` for the button. Logout resets the selector.
  366. - **Users** `storage/mpackdb/users.*` (identity id → display name, asked once at the first login,
  367. cannot be changed afterwards), **tokens** `storage/mpackdb/tokens.*` (sha256 only; `/you`:
  368. create / list / revoke; `Authorization: Bearer` on every API write, else 401).
  369. - **Reading public, writing needs a login** (web faces check the REAL session, #31; API 401).
  370. No author field (API `author` → 400 naming it); events store `user`, old events keep their
  371. author. **Confirm/reject only by `TICKETS_CREATOR_IDENTITY`** (unset = nobody; API 403).
  372. - `/you` shows the user's own per-app id (for TICKETS_CREATOR_IDENTITY) + tokens.
  373. - `import.hl` needs `TICKETS_TOKEN`. `docker-compose.yml`: `IDENT_EXCHANGE_URL=http://127.0.0.1:45002`
  374. (key/secret/creator come from `.env`, loaded by the runtime).
  375. - Gate `node tests/browser.mjs` → **250 passed, 0 failed** (own ident on :8353 from a copy of
  376. ident's code without `.env`, mail sink; see README "Test").
  377. - Live-store compatibility: old code (`.scratch/dev-m010`) and new code each on a COPY of
  378. `storage/mpackdb`, every GET dumped → byte-identical (`.scratch/m011/compat.sh`); old
  379. `.mpack` files unchanged; `users.*`/`tokens.*` appear empty. No migration step.
  380. For the architect (deploy): `.env` on Byrodin must have `IDENT_API_KEY` / `IDENT_API_SECRET`
  381. (creator set them). After the deploy: the creator logs in, opens `/you`, the architect sets
  382. `TICKETS_CREATOR_IDENTITY=<id>` in `.env`, restarts; the creator (identity "Architect") creates
  383. the architect's token on `/you`. Until then NOBODY can confirm/reject, and every API client
  384. (the architect's scripts, import) needs a token — writes without one are 401 from the deploy on.
  385. How to verify:
  386. ```bash
  387. cd /media/STORAGE/projects/tickets.worldapi.org
  388. node tests/browser.mjs # 250 passed; screenshots .scratch/gate-*-{signedout,name,loggedin,tokens}.png
  389. ps -eo pid,args | grep [h]l-browser-tier # empty
  390. bash .scratch/m011/compat.sh # DUMPS IDENTICAL (old vs new code on copies of storage/mpackdb)
  391. ```
  392. Manual (dev): own ident + tickets: `bash .scratch/m011/devup.sh` (ident :8358 from
  393. `.scratch/m011/ident` = copy WITHOUT .env, mail sink `.scratch/m011/run/mail.txt`; tickets :8359
  394. with `.scratch/m011/run/app.env`; `node .scratch/m011/devsetup.mjs` registers the app first).
  395. Query users/tokens: only on a COPY (#40): `cp -a storage/mpackdb /tmp/c` + a small hl script
  396. (`MPackDB(file = '/tmp/c/users.db', primaryKey = '@id', indexes = ['!identity'])`).
  397. ## 2026-09-24 — deploy readiness (ident mission 006 (old 010)) — awaiting architect (first deploy)
  398. Done (worker; NOT deployed to Byrodin):
  399. - `project.hl`: builds the listener itself so `HL_HOST=127.0.0.1` binds loopback (hl:webex
  400. ignores HL_HOST); `TICKETS_WATCH=0` = no dev watcher. Defaults unchanged (0.0.0.0, watcher on).
  401. - `docker-compose.yml` (container `tickets.worldapi.org`, 127.0.0.1:45003) + `deploy.sh`
  402. (README "Deploy").
  403. - Gate `node tests/browser.mjs` → 164 passed, 0 failed (dev copy `.scratch/dev-m010` and live folder).
  404. - Tested on Loreana only: local deploy into a directory + the container there (only
  405. 127.0.0.1:45003 listens, URL 200, fake storage marker untouched), and behind a local TLS
  406. nginx in real Chrome: page + wss socket, no http:// URLs (ident `.scratch/m010/`).
  407. How to verify:
  408. ```bash
  409. cd /media/STORAGE/projects/tickets.worldapi.org
  410. node tests/browser.mjs # 164 passed
  411. bash -n deploy.sh && ./deploy.sh --dry-run --target /tmp/ticketsdeploy-test # LOCAL dir only
  412. ```
  413. For the architect: the vhost needs WebSocket Upgrade headers and `proxy_pass http://127.0.0.1:45003`.
  414. ## 2026-09-24 — ticket #38 (mission 004 (old 008)): per-project numbers, UUID keys, storage/mpackdb/ — awaiting creator
  415. Done (worker, not yet confirmed by the creator):
  416. - **UUID keys** (`@id`) in all three tables, files in `storage/mpackdb/{projects,tickets,events}.*`.
  417. Ordering from stored times: lists by `updated`, history by (`created`, `seq`), projects by `created`.
  418. - **Per-project numbers** from 1; URL `/projects/<project>/<number>`; slug = project name as is
  419. (new names: letters, digits, `. _ -` only). List, inbox, ticket page, pushes, notice link use it.
  420. - **Old global numbers** kept as `oldNumber`: page shows "formerly #n", `/tickets/<n>` → 301,
  421. `/api/tickets/<n>[/comments|/state]` still work (also with the UUID). New tickets: no old number.
  422. - **API** per-project form `/api/projects/<slug>/tickets[/<number>[/comments|/state]]` (GET/POST),
  423. rows carry `project`, `number`, `href`, `apiHref`, `oldNumber`. `ticket.id` is now the UUID.
  424. - **Migration** `tools/migrate-008.hl` (idempotent) of the live data, deployed 2026-09-24:
  425. old tables stay in `storage/tickets-*` (untouched, md5-checked), full backup
  426. `.scratch/storage-backup-<ts>`, logs + before/after dumps in `.scratch/deploy-008-<ts>/`
  427. (`deploy-008.sh`, compare `cmp008.py`). Rehearsed on a copy first (`.scratch/rehearse.sh`).
  428. - Gate: `node tests/browser.mjs` → **164 passed, 0 failed** (in `.scratch/dev` before deploy).
  429. Test: gate (see README "Test"), then
  430. `python3 .scratch/cmp008.py .scratch/deploy-008-*/before.jsonl .scratch/deploy-008-*/after.jsonl`
  431. and open http://192.168.178.75:8350/tickets/38 (→ /projects/tickets.worldapi.org/5).
  432. Known gaps: new tickets have no global number, so `/api/tickets/<n>` works only for the
  433. migrated #1–#40 (use the UUID or the per-project form). Numbers are max+1 (a deleted ticket's
  434. number would be reused — there is no delete). Project names stay case-sensitive.
  435. ## 2026-09-24 — ticket #15 (mission 003 (old 004)): required author, 400 on invalid JSON, list lines — awaiting creator
  436. Done (worker, not yet confirmed by the creator):
  437. - **`author` required** on `POST /api/tickets`, `/comments`, `/state` → 400 `{error, field:"author"}`
  438. when missing/empty (`api.hl` required lists + `store.hl` `authorMissing`, so web faces too).
  439. Web forms: name fields removed, they write as `creator` until ident exists. Stored data unchanged.
  440. - **Invalid JSON → 400 `{error:"invalid JSON at character N"}`** (no source path) via
  441. `jsoncheck.hl` before `JSON.parse` — WORKAROUND for hybriel #12, remove when fixed. It also
  442. refuses `\uD800`–`\uDFFF` escapes (hl JSON.parse refuses even valid pairs → would be 500).
  443. - **Import list lines**: `ticketfile.hl` keeps `- ` / `* ` / `1. ` lines as own lines (`\n`),
  444. wrapped lines still joined; page shows them as separate lines (pre-wrap). #1–#5 have no lists
  445. → nothing to migrate.
  446. - Gate: `node tests/browser.mjs` → **119 passed, 0 failed** (in `.scratch/dev` before deploy).
  447. - Deploy 2026-09-24: backup + before/after API dumps in `.scratch/` (see `deploy-004.sh`).
  448. Known gaps: a body that passes jsoncheck but that hl JSON.parse still refuses would 500
  449. (none known besides surrogates). Nested list indentation is dropped. A wrapped line that
  450. happens to start with `12. ` is taken as a list item (same as Markdown).
  451. ## 2026-09-24 — ticket #10 (mission 002): strict API, Vienna time, paragraph import — awaiting creator
  452. Done (worker, not yet confirmed by the creator):
  453. - **Strict API** (`bodyError` in `api.hl`): every POST body must be a JSON object; unknown
  454. field / missing or empty required field / non-string value → 400 `{ error, field }`,
  455. nothing written. Semantic refusals (state, project name) carry `field` too. `POST /api/projects` → 405.
  456. - **Local time**: all shown times (pages + API `created`/`updated`/`when`) are Europe/Vienna
  457. (`localtime.hl`, userland EU DST rule — hl:time has no time zones). Storage stays epoch ms.
  458. - **Import**: `ticketfile.hl` joins hard-wrapped lines, blank line = new paragraph (`\n\n`).
  459. - **One-off data fix** 2026-09-24 00:11: `tools/fix-import-summaries.hl` rewrote only `summary`
  460. of #1–#5 (server stopped, backup `.scratch/storage-backup-20260924-001142`). Ids, states,
  461. `updatedMs`, events unchanged — compared API dumps `.scratch/{before,after}-full-20260924-001142.json`.
  462. - Gate: `node tests/browser.mjs` → **93 passed, 0 failed** (2026-09-24, run in a copy before deploy).
  463. Known gaps: invalid JSON → 500 (no way to catch `JSON.parse` in Hybriel; nothing written).
  464. `author` stays optional (defaults to `anonymous`). Vienna rule is correct from 1996 on only.
  465. Running: dev server on :8350, PID in `server.pid`, log `server.log`, data `storage/mpackdb/`.
  466. ## 2026-09-23 — MVP (AntColony ticket 0001, mission 001)
  467. Done (worker, not yet confirmed by the creator):
  468. - Data: projects, tickets (id, project, subject, summary, state, source, created, updated),
  469. append-only events (created / comment / state, free-text `author`). Six states.
  470. - Pages `/`, filters `/project/:p`, `/state/:s`, both; `/tickets/:id`; `/inbox`; new-ticket form.
  471. - Live push of comments / state changes / new tickets to every open browser (web AND API writes).
  472. - JSON API (list/get/create/comment/state/projects), import command (idempotent by file name).
  473. - Dark WorldAPI tokens, accent #c586c0, danger #f44747; works at 390px and 1280px.
  474. - Gate then: 57 passed.
  475. ## How to verify
  476. ```bash
  477. cd /media/STORAGE/projects/tickets.worldapi.org
  478. node tests/browser.mjs # the gate (see README "Test")
  479. node tests/connect.mjs # the connect gate (ports: README "Vendored Hybriel" / a temp copy)
  480. # a refactor: tests/realdata-baseline.mjs old vs new + tests/realdata-compare*.py (README "Test" → "Same output")
  481. python3 tests/letcount.py . # code order: root .hl files, project.hl lines, misused let
  482. ps -eo pid,args | grep [h]l-browser-tier # must print nothing afterwards
  483. curl -s http://127.0.0.1:8350/api/tickets | jq '.tickets[] | {project, ref, oldNumber, state, subject}'
  484. curl -s -XPOST -d '{"text":"x","bogus":"1"}' http://127.0.0.1:8350/api/tickets/1/comments # → 400 naming bogus (nothing written)
  485. curl -s -XPOST -d '{bad' http://127.0.0.1:8350/api/tickets/1/comments # → 400 invalid JSON at character 1
  486. curl -si http://127.0.0.1:8350/tickets/38 | grep Location # → /projects/tickets.worldapi.org/5
  487. node .scratch/checklive.mjs # read-only real-Chrome look at :8350: list/history times == Intl Vienna, #1/#2 summaries one paragraph
  488. ```
  489. Then open http://192.168.178.75:8350 in two browsers, comment in one, watch the other.
  490. ## Open / next
  491. - ~~No users~~ — done by ticket #7 (mission 005 (old 011)): creator-only confirm/reject via TICKETS_CREATOR_IDENTITY.
  492. - Not deployed (Byrodin container, nginx vhost, DNS) — out of MVP scope.
  493. - Project filter chips / datalist of a list page do not grow live when a NEW project appears
  494. (a reload shows it); ticket rows do.
  495. - No delete of tickets or comments; comments cannot be edited (only subject + summary, ticket #8).
  496. - Keywords in comments (`/confirm`, `/reject`, …) are the scheduler's job, not implemented here.
  497. - Hybriel limits met (worked around): no time zones in hl:time (localtime.hl), no catch for
  498. JSON.parse (jsoncheck.hl → 400), JSON.parse refuses \u surrogate escapes, no list `concat()` (NotCallable), no string escapes ("\r" is 2 chars).
  499. - Older Hybriel limits met (worked around): no string ordering / no list `sort()` (import sorts by
  500. char codes). (mpackdb `*id` from 0 no longer matters: UUID keys since #38.)
  501. ## Lessons
  502. - (mission 010) A plain declaration (`x = 1`, no `let`) is a CONSTANT: inside a for/while body Hybriel refuses it on the
  503. 2nd pass ("Cannot reassign immutable variable") — a variable declared in a loop body keeps `let`. A constant can still be
  504. mutated (`out.push`, `o.k = v`). Inside a component, `x = …` in a handler assigns the MEMBER `x` if there is one.
  505. - (mission 010) A hybrid that holds an instance (the request with `req.session`) keeps the handle through ONE call, but
  506. copied into a SECOND call the instance is copied too: writes to the session were lost (the gate's logins failed).
  507. `/login/callback` therefore gets `&req` and `&server.sessions` from project.hl. Instances as call arguments are copied
  508. anyway — pass `&` when the callee must change the original.
  509. - (mission 010) Imports must not form a cycle ("Cyclic member import"): lib/ imports only downwards (README "Files").
  510. - (mission 011) hl:web APPENDS its own routes (`/__hl/…`, `/components/<file>.hl` modules) to the app's table during
  511. `new WebFramework(…)`, and the first match answers: a catch-all like `/:slug/:number` anywhere in the app's table eats
  512. them (the page answered /__hl/app.css with HTML). Fix in project.hl: move such routes to the end of `server.router.routes`
  513. after the construction; keep them IN the table, or hl:web serves no module for their component (a component only
  514. mounted by a route added later had none: `/components/ticket.hl` → HTML). Upstream: hl:web should match its own urls first.
  515. - (mission 011) `setsid cmd &` started from a `( … )` subshell forks: `$!` is not the server's PID (a stale server kept
  516. the port and the next run silently talked to it). Start test servers as `(… exec ./bin/hybriel …) &` and check the
  517. port is free afterwards (`ss -ltn`).
  518. - (mission 010) An HTML page's seed carries source positions (`site`, `Markdown@135:54` mount keys): moving lines changes
  519. the bytes without changing the output — tests/realdata-compare.py masks them.
  520. - (mission 006 (old 012)) hl:webex re-creates elements of a View when an `if` block before/around them
  521. flips — including `script { src }`: the browser EXECUTES a re-inserted script again, and custom
  522. elements lose their internal state (the selector forgets the chosen name). A plain script in a
  523. View must be idempotent (guard per document) and must not hold element references.
  524. - (mission 005 (old 011)) A called function's PARAMETER NAMED LIKE AN ENTRY of the object literal it is
  525. called from reads that entry: `{ id = e.id author = nameOfUser(e.user) }` with
  526. `nameOfUser = (id) => …` got the EVENT's id. Compute such calls before the literal, avoid
  527. parameter names like `id` (Hybriel issue reported in mission 005 (old 011)).
  528. - (mission 005 (old 011)) hl:mpackdb `update(key, rec)` on an `@id` table with a record WITHOUT `id`
  529. stores it without the key: `fetch(key)` is null afterwards (the unique index still finds it)
  530. and the next update fails `CorruptRecord`. Always pass the whole record with `id`.
  531. - (mission 005 (old 011)) hl:webex cannot listen to a custom DOM event (`ident-login`): a plain script
  532. (`login.js`) writes the value into a hidden input and fires `change`.
  533. - (mission 005 (old 011)) A failed hl:fetch (connection refused) is an `Error` event; only a root
  534. `on Error(e)` in the ENTRY file (project.hl) absorbs it — one in the imported users.hl did not.
  535. - (mission 005 (old 011)) hl:webex ships every component member AND `session.user` to the browser:
  536. keep the identity id out of members and out of the session (only on /you, on purpose).
  537. - (mission 005 (old 011)) `rsync --exclude 'server.*'` also drops `plugins/*/server.hl` — anchor it (`/server.*`).
  538. - Hybriel resolves RELATIVE file paths (mpackdb too) against the ENTRY SCRIPT's directory,
  539. not the cwd: a tool in `tools/` must get an absolute `TICKETS_STORAGE`.
  540. - Saving a `.hl` file reloads the running dev server (watcher): develop in a copy
  541. (`.scratch/dev`, gate there), stop the server, then copy files in.
  542. - `JSON.parse` of a JSON array gives a Hybrid with `.length`; of an object `.length` is null.
  543. - A function route's request has `req.query` (parsed object); `req.path` carries NO query string.
  544. - `emit client …` inside a function route (API) works: with an `audience` entry it fans out to
  545. every admitted connection that has a listener mounted.
  546. - A selector group in `styles.hl` must be ONE quoted key (`'input,\ntextarea' { … }`); a bare
  547. `input, textarea` is a parse error.
  548. - Function PARAMETERS are immutable in Hybriel (`i = i + 1` on a param is a runtime error):
  549. copy into a `let` first (jsoncheck.hl).
  550. - `google-chrome` is not on PATH on Loreana; the gate uses `/opt/google/chrome/chrome`.
  551. - Opening an hl:mpackdb table REWRITES its index/meta files (`*.txt`, `*.meta.json`,
  552. `*.idxstate.json`) even with `compact = false`; only `.mpack` stays. Migrate from a COPY
  553. when the originals must stay byte-identical (deploy-008.sh does).
  554. - mpackdb `@id` keys are 12-char strings (`0mufbphip3w7`); a missing indexed field is fine
  555. (`*oldNumber` on new tickets); `!path` unique index refuses duplicate numbers.
  556. - A non-literal `static` in project.hl reads null in function routes (hybriel #16): API helpers
  557. in project.hl are plain members, not statics.
  558. ## 2026-09-24 — DEPLOYED (architect)
  559. - LIVE is now https://tickets.worldapi.org on Byrodin (/CONTAINERS/projects/tickets.worldapi.org). The live data was moved there.
  560. - This Loreana folder is DEVELOPMENT only; its storage/ is a stale copy. Dev servers were stopped. Update live with ./deploy.sh.
  561. ## 2026-09-24 — ticket #16: tidy relations block out of the edit handlers — awaiting creator
  562. Done (worker, not yet confirmed by the creator):
  563. - `components/ticket.hl`: the relations init block (`rel`, `hasRelations`, `hasParent`, …,
  564. `parentDraft`, `blockerDraft`, `linkMessage` — 16 lines) had been pasted verbatim into
  565. `startEdit`, `cancelEdit` and `saveEdit` too (ticket #4 code landing inside the ticket #8 edit
  566. handlers). Removed the three copies; the component's own init block (still there) is the only
  567. place that sets them. No behaviour change intended — clicking Edit/Cancel/Save no longer resets
  568. `parentDraft`/`blockerDraft`/`linkMessage` mid-edit, which was a side effect of the paste, not a
  569. feature. 489 → 441 lines.
  570. - Gate: `node tests/browser.mjs` → **418 passed, 0 failed**, run on this folder with
  571. `TICKETS_GATE_PORT=8700 TICKETS_GATE_IDENT_PORT=8701 TICKETS_GATE_EXCHANGE_PORT=8702
  572. TICKETS_GATE_CHROME_A=8703-8704 TICKETS_GATE_CHROME_B=8705-8706` (own storage in
  573. `.scratch/gate-store`, own ident, no live data touched). Includes the full #4 relations suite
  574. and #8 edit suite, both still green.
  575. - Not deployed (deploy.sh is the architect's job).
  576. Test: gate above, then open a ticket with relations on the live site and click Edit — the
  577. relations panel keeps showing normally, editing subject/summary still works.

Branches

Latest commits

  • bb64d57cmission 012 (ticket #25 API part): every /api/projects/<x>/… route takes the project's id (12-char record id) as well as its slug, same answers byte for byte; tickets stay under their project, no global /api/tickets/<x>. gate 315/0 (old code: the 13 id checks fail), connect 60/0, live-data copy old vs new 1,484 identical, slug vs id 772 identicalmre
  • 7538b034mission 011 (ticket #25 web part): short page URLs /<slug> and /<slug>/<number>; /projects/<slug>, /projects/<slug>/<n>, /projects/<slug>/tickets/<n> and /tickets/<ref> answer 301 (current slug); slug pages moved behind hl:web's own routes after construction (they answered /__hl/* and /components/*.hl); reserved slugs refused; API paths unchanged. gate 293/0, connect 60/0, live-data copy 187/0, API old vs new: only page links differmre
  • 0369106emission 010 (code order) 4/4: README file map + import order + 'Same output' test, STATUS (entry, lessons, how to verify), LOG, report; tests/realdata-baseline.mjs + realdata-compare*.py (a cleanup answers the same on live data), tests/letcount.pymre
  • a75e0279mission 010 (code order) 3/4: let only where a variable is reassigned or re-bound in a loop body (456 lets → plain declarations; Hybriel refuses a plain declaration inside a loop on its 2nd pass). gate 249/0, connect 60/0, real-data reads identical, a 50-step write sequence (API + faces) identical to the old codemre
  • e9d5c618mission 010 (code order) 2/4: one lib/ file per topic — store.hl split into projects / tickets (+ relations) / events / tickets-helpers, util.hl shared helpers (env, storage dir, URLs, sorts, Vienna time), the function routes out of project.hl into lib/api.hl (thin; auth/filters/Accept in api-helpers.hl), invite + member-removal logic out of the faces/routes into invites.hl / tickets.hl; project.hl is the map. /login/callback gets req + the session store by reference. gate 249/0, connect 60/0, real-data reads identicalmre
  • 97e269b5mission 010 (code order) 1/4: .hl files out of the root — lib/ (store, users, connections, invites, migrate, markdown, mdview, import = ticketfile, util = localtime, jsoncheck, api-helpers = api), tools/import.hl, components/styles.hl; import paths only. gate 249/0, connect 60/0, real-data reads identicalmre
  • 38f9d10ftickets: Hybriel master 06617221 (plugin allocators 3a781359 + 413f60e4, mpackdb 2cb7ae5e, http1 773de63e); gate 249/0, connect 60/0mre
  • d3db6139tickets: Hybriel master 190aa11d (fc838894 GC correctness, #126 closure scopes, #127); gate 249/0, connect 60/0mre
  • bce182e3tickets: Hybriel master 7eea0d32 (#126 memory, #48 lambda copies its argument); migrate.hl lambdas take &logmre
  • 4137be0fantcolony#40: mission references point to the moved missionsmre
  • 9bfba36aantcolony#40: history (LOG.md), worker briefs (missions/) and reports moved here from antcolony, numbered per project; old numbers in antcolony docs/mission-map.mdmre
  • c7bd2645tickets: Hybriel master 73267707 (#122 fixed); compactNow workaround removed (#110 covered)mre
  • 2ab91ee9tickets: gate checks rows appear once (session sync); re-vendor to ff51cf46 stopped on hybriel#122, stays 837fe120mre
  • e01c2b1dtickets#24: installable app (manifest, service worker, offline list), own icon; gate waits for the hello's pongmre
  • 752fbb7fdeploy.sh: back up live storage/.sessions/.env before every deploy (newest 5 kept)mre
  • 38bdd5e4deploy.sh: never send .git or .gitignore to Byrodinmre
  • f12fa1bcState of 2026-09-27, before the move to gitoriamre