notes
All repositories: gitoria
- Address
- https://notes.gitoria.worldapi.org/
- Owner
- Caramboleyo
- Created
notes.worldapi.org
A notes app in Hybriel (hl:web). Log in with ident; the sidebar lists your notes by subject, last edited first;
a click opens the note on the right, edited with the Markdown editor <md-editor>; New note and Delete note.
The notes are also kept in the browser (IndexedDB): they open and can be edited offline, and sync with the server
(and other devices) when the network is back. Concept: CONCEPT.md.
How it works
- Subject = the first non-empty line of the note without leading
#(at most 80 characters); an empty note is "Untitled note". No separate title field. - Order =
edited(ms), last first. Only a changed text moves a note to the top. - Pages:
/(the list; on a phone the list alone) and/note/:id(the note; on a phone it replaces the list, "All notes" leads back; from 50rem wide both show side by side). Signed out: only "Log in with ident". - Offline + sync (ticket notes#2):
notes-offline.jsfills the notes area from the browser's own copy (IndexedDBnotes-<user>), every change is written there first and sent to/api/notesabout 0.6 s later (and every 10 s a pull looks for other devices' changes; also on "online", on returning to the tab; other tabs follow through BroadcastChannel).sw.js(service worker, network first) keeps the page and assets, so a reload works with no network; the page carries no note data. A note made offline gets a local id (l…) and its server id at the first push (the address follows). Conflict (changed here and elsewhere): the later edit wins, the other version is kept as a copy note ("kept as a copy …" line at its end). A delete of a note changed elsewhere meanwhile is refused (the changed note stays). Deleted notes stay on the server as tombstones (gone) so other devices learn of it. Log out first sends what is unsent, then this browser deletes its copy of the notes. - Private: every function in
lib/notes.hltakes the owner's user id and only reaches that user's notes; another user's note id answers "does not exist"; saving/deleting it is refused. A note is at most 200 000 characters (refused, never cut). - Login: ident's button flow (
/login/callback→?ident_code=→ server-side exchange) plus the identity selector, as in gitoria; the session cookie isnotessid, idle time 14 days. The session carries the users@idonly; the ident identity id never leaves the server. - Short ids (ident#23, antcolony mission 039):
users.identityholds what ident's exchange answers — since ident#23 the identity's public 5-character short id (a68sz), before that the per-app id (32 hex);lib/util.hl isIdentIdaccepts both (the oldisHexcheck refused short ids). The switch: one-offtools/migrate-short-ids.hl(old → short id, idempotent, neverfinish), gatenode tests/short-id-switch.mjs(ports 8720/8721, no browser), runbookantcolony-docs/docs/short-id-switch.md(Byrodin:/CONTAINERS/projects/antcolony/docs/short-id-switch.mdonce synced).
Files
Code order (antcolony docs/code-order.md, notes mission 002): project.hl is the map (config, routes, audience, an
index of which file handles what); one lib/ file per topic holds its central logic, lib/<topic>-helpers.hl what it
looks up; the function routes are thin wrappers. let only for a variable that is reassigned (or re-bound in a loop
body). Imports go one way: util ← users, notes-helpers ← notes ← api-helpers ← api ← project.hl.
| file | what |
|---|---|
project.hl | the map: index comment, PWA config, routes, audience, server |
lib/notes.hl | notes table (@id, index owner; { owner, text, created, edited, gone, local }), every write to it: sync push (pushNote) / delete (removeNote) / conflict rules, metaRows, noteView |
lib/notes-helpers.hl | size limit (textError), isAlive (tombstones), stampOf (version stamps), copyMark |
lib/users.hl | users table (@id, !identity), ident config + exchange, userOfLoginCode (both logins), userIdOfSession, tagOf |
lib/api.hl | the function routes: /login/callback (+ loginFailed; take &req, &sessions), /api/me, /api/notes |
lib/api-helpers.hl | JSON reply, isText / isTime, safePath (the login's ?next=) |
lib/jsoncheck.hl | JSON syntax pre-check (copy from tickets; workaround for hybriel#6/#12 — delete when fixed) |
lib/util.hl | envOr, storageDir, countOfList, firstOf, isHex, isIdentId |
components/main.hl | the shell: header, login/logout, faces notesLogin / notesLogOut |
components/notes.hl | the page (/, /note/:id): only <note-book id="notesapp"> |
components/loginfailed.hl | /login/failed: the reason parked in the session |
components/styles.hl | all CSS (mobile first; accent = yellow); shared/tokens.hl = copy of worldapi-tokens |
notes-offline.js | the notes area: sidebar, editor, New / Delete, IndexedDB, sync (served at /notes-offline.js) |
sw.js | service worker: keeps the page + assets for offline use |
icons/ | app icons: icon.svg (source), icon-192/512.png, apple-touch-icon.png, favicon.svg, favicon.ico |
shared/md-editor.js | vendored copy of worldapi-components md-editor.js (served at /md-editor.js) |
login.js | ident selector ↔ shell bridge |
tools/migrate-short-ids.hl | one-off ident#23 migration (README "Short ids") |
tests/browser.mjs | the gate (own server, own ident copy, real Chrome); deploy.sh runs it |
tests/short-id-switch.mjs | gate of the short-id switch (fixed ports 8720/8721) |
tests/realdata-baseline.mjs, tests/realdata-compare.py | same-output check of two code trees on a live-data copy (below) |
tests/letcount.py | code-order counts: root .hl files, project.hl lines, lets (-v lists any that should be plain) |
Data: storage/mpackdb/{users,notes}.db (never sent by deploy.sh). Only ever open COPIES of live data.
PWA (installable app, antcolony mission 046)
project.hlappIcons/appTouchIcon/appFavicon/appThemeColor(tokendarker, the header) /appBackgroundColor(dark) → hl:web serves/__hl/manifest.webmanifest(namenotes, start_url/scope/, standalone) and links it, the apple-touch-icon and theme-color from every head. Each icon has its ownfileroute inproject.hl.- Offline is notes' own, not hl:web's: no
offline = [...]inproject.hl, because hl:web would then register/__hl/sw.jsfor scope/and replace/sw.js(or be replaced by it)./sw.jsis registered bynotes-offline.jsonce signed in; it is the only worker (the gate checks that, and that/__hl/sw.jsis 404). Moving to hl:web's offline = ticket notes#4. - Icons (
icons/):icon.svg= source (512; a note page with a folded corner and a pencil, yellow on rgb(25,30,35); everything inside the maskable safe circle r=204, so one image servesanyandmaskable);favicon.svg= the same drawing cropped tight, thicker strokes. Render:
rsvg-convert -w 192 -h 192 icons/icon.svg -o icons/icon-192.png
rsvg-convert -w 512 -h 512 icons/icon.svg -o icons/icon-512.png
rsvg-convert -w 180 -h 180 icons/icon.svg -o icons/apple-touch-icon.png
for s in 16 32 48; do rsvg-convert -w $s -h $s icons/favicon.svg -o /tmp/fav-$s.png; done
magick /tmp/fav-16.png /tmp/fav-32.png /tmp/fav-48.png icons/favicon.icoThe sync API (lib/api.hl; JSON, the session cookie is the login; 401 signed out, 400 for bad JSON/fields)
GET /api/me→{ user }·GET /api/notes→{ notes: [{ id, edited, gone }] }·GET /api/notes?id=→{ id, text, edited, created }POST /api/notes{ op:'save', id, local, text, base, at }→{ id, edited, text, copy? }·{ op:'delete', id, base }→{ ok }|{ kept, id, edited, text }.base= theeditedthe device last saw,at= when it edited.
Run and test
NOTES_PORT=8710 NOTES_WATCH=0 ./bin/hybriel project.hl # dev; needs IDENT_API_KEY/SECRET for a login
node tests/browser.mjs # the gate: ports 8701 (ident), 8702 (app), Chrome 8703–8709
# (other ports: NOTES_GATE_PORT / _IDENT_PORT / _CHROME=8722-8726)
# includes the PWA checks; screenshots in .scratch/gate-*.png
./deploy.sh [--dry-run] # gate → backup → rsync to Byrodin → restart → URL 200 (architect)
python3 tests/letcount.py . [-v] # code order: must say `never reassigned 0`, root .hl files 1Same output (for a cleanup that must not change behaviour; notes mission 002): a copy of the live tables, then the
old tree and the new tree each answer every page (signed in as az5b2 = users 0muh6o5a3kkd, and signed out), every
API read, the POST refusals, the login routes/faces against a fake ident, and a fixed write sequence (save, retry,
conflict both ways, delete, tombstone, logout); then compare:
tar -C /CONTAINERS/projects/notes.worldapi.org -cf - storage/mpackdb | ssh loreana tar -C <repo>/.scratch/realdata -xf - # on Byrodin
git archive <old commit> | tar -C .scratch/old -xf - && cp -a bin plugins .scratch/old/
node tests/realdata-baseline.mjs .scratch/old 8762 .scratch/base-old # app on 8762, fake ident on 8767 (port + 5)
node tests/realdata-baseline.mjs . 8762 .scratch/base-new
python3 tests/realdata-compare.py .scratch/base-old .scratch/base-new -v # exit 0 = the sameIt masks only source positions, ?v= hashes, the seed time and (writes) new ids, edited stamps and login tags; two
runs of the same tree compare clean. Delete the live copy and the outputs afterwards (they hold live notes).
Environment: NOTES_PORT (dev 8710, container 45007), NOTES_STORAGE, NOTES_SESSIONS, NOTES_WATCH=0,
NOTES_PUBLIC_URL (default https://notes.worldapi.org), IDENT_URL, IDENT_EXCHANGE_URL, IDENT_API_KEY,
IDENT_API_SECRET (.env beside project.hl, never read by workers).
First deploy (architect)
Container notes.worldapi.org on 127.0.0.1:45007 replaces the demo-notes at the same address: back the demo up,
stop it, point the vhost to 45007 (WebSocket Upgrade headers needed), register the app in ident with the origin
https://notes.worldapi.org, put the key and secret into .env.
Hybriel notes
bin/hybriel+plugins/(core crypto data fetch fs http http1 mpackdb proc time web) are vendored = hybriel master 06617221 (2026-10-03, antcolony mission 074: plugin allocators 3a781359 + 413f60e4 (#126, plugins allocate with malloc via plugin_api.zig), mpackdb 2cb7ae5e, http1 773de63e, f0ac2d2d (plugin ABI field — bin and .so must match); no lambda semantics change), sha25621059cc7…d77bdb, built the same way fromgit archive 06617221(~/scratch-074/src, removed); previous copy (190aa11d) in.scratch/pre-074/. Gates: browser 50/0 (NOTES_GATE_PORT=8760 NOTES_GATE_IDENT_PORT=8761 NOTES_GATE_CHROME=8762-8769), short-id-switch 18/0 (temp copy with ports 8760/8761). Memory:LOADS=N ROUNDS=N [MODE=pull|push] node .scratch/w074/memtest.mjs <app dir> <mpackdb copy> <label> <users @id>(port 8760, Chrome 8761–8769; loadMarks/apiMarks = RSS every 250). Pages and pulls are flat now; saves still grow ~0.09 MB per save on old and new alike (mpackdb update path). Before: master 190aa11d (2026-10-02, antcolony mission 072: fc838894 GC correctness (string index / plugin error read freed memory), 038d84b3 #126 returned closure scopes, #127 d98926c6/04df4428; no lambda semantics change since 8efba065), sha256860f5e61…0a23626, built read-only fromgit archive 190aa11d(~/scratch-072/src, removed); previous copy (8efba065) in.scratch/pre-072/. Gates: browser 50/0 (NOTES_GATE_PORT=8720 NOTES_GATE_IDENT_PORT=8721 NOTES_GATE_CHROME=8722-8729), short-id-switch 18/0. Memory:node .scratch/w072/memtest.mjs <app dir> <mpackdb copy> <label> <users @id>(port 8720, Chrome 8721-8728; LOADS / ROUNDS env): 200 loads + 200 sync rounds new 114 → 186 → 230 MB, old (8efba065) 124 → 222 → 277 MB; 1000 + 1000: 213 MB after the loads (pages plateau), 486 MB after the sync rounds (~0.27 MB per round, still grows). Before: master 8efba065 (2026-10-02, antcolony mission 069: #126 = f685f240 the interpreter GC also collects by bytes, #48 = 4371b7aa a lambda parameter COPIES its argument (&p= reference), #112 #117 #119 #120 opt-in, not used), sha256ebf31876…5c0ad2a2, same build command as below; previous copy (ff51cf46) in.scratch/pre-069/. #48 audit: no lambda in notes writes a parameter the caller relies on (project.hlfailed/loginCallback aliasreq.sessionand save it themselves; faceson server …(session)are not lambdas) → no&needed. WRONG forfailed(req, why)with a cookie: its write was lost ("the login did not finish" instead of the reason) — fixed in notes mission 002 by&req/&sessions(lib/api.hl). Audit:python3 .scratch/w069/lambdas.py(writes to params) +lambdas2.py(params handed on/aliased). Memory (#126):node .scratch/w069/memtest.mjs <app copy without .env> <mpackdb copy> <label> <users @id>(port 8730, Chrome 8722-8729; LOADS / ROUNDS env) — live-data copy, 200 Chrome loads + 200 sync rounds: new 124 → 287 MB, old 97 → 1282 MB. Before that: master ff51cf46 (2026-10-01, antcolony mission 048: #113 mpackdb update() duplicate ids, #115/#116 lambda members on faces, #118forover a literal list, client-built SVG namespace, hl:markdown #111 — notes uses none of these directly; before: e6720b1f, antcolony mission 037: #103/#104 client fixes, #105headers, #106letper loop pass, #94 files in emit; earlier #83 hashed/__hl/…?v=URLs + immutable cache, #82 socket reconnect, #95–#97 offline/PWA from project.hl — the manifest (appIcons) is used since antcolony mission 046,offlineis NOT: notes keeps its own sw.js / notes-offline.js). Built from a read-onlygit archive master(zig build -Doptimize=ReleaseFast -Dtarget=x86_64-linux-gnu.2.39innative/), sha256e70631f0…eff3471b. No local patch: re-vendor = copy binary + plugins, run the gate. Copy before 048 (e6720b1f):.scratch/pre-048/.- The notes area is plain JS on purpose: IndexedDB, a service worker and offline edits cannot go through hl:web's
faces (they need the socket; it never reopens, hybriel#82). The framework's link click and
popstatewould navigate through the socket, so the area claims its own link clicks and useshistory.replaceState(Back leaves the app). - Gate needs a secure origin for the service worker: Chrome runs with
--unsafely-treat-insecure-origin-as-secure. - Server restart closed open tabs' sockets for good (hybriel#82) — fixed in the vendored master since 035 (reconnect); not checked by the notes gate. The notes area still uses its own JS (candidate for hl:web offline, #95–#97).
History and worker briefs
LOG.md— append-only history, one dated line per step (moved here from the antcolony LOG on 2026-10-01).missions/NNN-*.md— worker briefs for this app;reports/NNN-*.md— their reports (same name). Numbered per project since 2026-10-01 (antcolony#40); older text, code comments and commits use the old antcolony numbers → map:/media/STORAGE/projects/antcolony-docs/docs/mission-map.md(Byrodin:/CONTAINERS/projects/antcolony/docs/mission-map.md).