This commit is contained in:
Your Name 2026-08-08 16:28:12 -04:00
parent dc947d16b9
commit 465cc74796

View file

@ -402,6 +402,142 @@ then log results in Section 10 before moving on.
- _Next:_ `styles` + `message` template + `GET /message/:id` (single-message
primitive).
- **2026-08-06 — Phases 17 built & backend verified on brb.city.**
- **All 16 templates + 10 routes written** (ids in the maps above). Structure
`aim` #44 is live at `https://brb.city/aim/`.
- **Design pivot (recorded so §46 match reality): identity is fully
client-side.** localStorage holds `aim:handle` + `aim:client_id` (a stable
per-browser UUID). The socket carries them on an `identify` event (presence)
and each `send` (authorship + push exclusion). The server *trusts* the
client's declared identity — correct for a fun playground, and it kills the
"forgets who you are" + session-churn class of bugs outright. No server-side
alias/session juggling remains.
- **The four reported bugs, resolved:**
- *Forgets you* → localStorage handle + one stable alias per handle
(`getOrCreateAlias`), auto-seeded from the logged-in Bliss user.
- *Self-notify push* → subscriptions keyed by `endpoint`, owned by stable
`client_id`; fan-out is `subsExcludingClient(senderClientId)`. Your own
device is structurally excluded regardless of name/session changes.
- *Noisy presence* → counts kept **per person, not per socket**, with a 4s
debounce on sign-off, so tabs/reconnects don't spam. (`arrive`/`depart` in
the WS handler.)
- *Broken single-message*`getMessageById` rewritten with the correct join;
it's now the backbone of the socket-as-pointer-bus design.
- **Socket-as-bus confirmed live** via a Node `ws` client: `identify`
presence-line broadcast; `send` → persisted (`message #4`) + a `msg-pointer`
broadcast of exactly
`<div hx-get="/aim/message/4?zone=z9" hx-trigger="load" hx-swap="outerHTML">`
— no message markup crosses the socket. `GET /aim/message/1` renders the AIM
line (red bold name, gray timestamp) with **unescaped** `<b>`/`<marquee>`
intact. First-render null-slice bug (Eta ASI: an eval block must not start
with `(`) found & fixed in `transcript`.
- **`req.currentUser` added** (minimal `{id, username}`) in **local**
`index.js` + `getUserById` in `db.js` (syntax-checked). ⚠️ This repo is the
*local* copy; brb.city runs from `/home/nodejs/bliss2` on another host — **the
index.js/db.js change needs deploy + restart there to take effect.** Until
then templates fall back to `"Guest"` (graceful; nothing breaks).
- Seeded one welcome message; cleared all test rows.
### Verified ✅ vs. remaining ⏳
- ✅ Fresh schema + library (alias stability, verbatim HTML, range/catchup/push helpers)
- ✅ `GET /message/:id` single-message primitive (unescaped content)
- ✅ `WS /room`: identify→presence, send→persist→pointer broadcast, catchup
- ✅ Full page, `/live`, `/compose`, `/frozen` render without error
- ✅ Socket ships pointers only; message route is the single render source
- ⏳ **Browser check** (Playwright MCP wasn't available this session): the
client paths — compose→`chat:send`→ws-bridge ship, `chat:message` emit on
arrival, localStorage default handle, rename, autoscroll, push subscribe —
are written but not yet driven in a real browser. Do this next session.
- ⏳ **Deploy** local `index.js`/`db.js` to brb.city + restart (pm2 `foo`) to
enable the logged-in-user default.
- ⏳ **Phase 8**: an `aimgpt`-style demo page nesting `/live` + `/compose` (+ the
sheep) to show the parts compose.
- **2026-08-06 — Browser verification + the send-path bug (found via Playwright).**
- Got Playwright MCP working (it wanted chromium build 1237; symlinked the
installed `chromium-1234``chromium-1237` in `~/.cache/ms-playwright`).
- **Sending was broken — real bug, now fixed.** Eta's `<%= %>` HTML-escapes, so
`var dflt = <%= JSON.stringify('Guest') %>;` rendered as
`var dflt = &quot;Guest&quot;;` **inside the `<script>`** → "Unexpected token
'&'" → the entire `ws-bridge` script threw → `window.aimId` undefined → no
`chat:send` listener → messages dispatched into the void (textarea cleared,
nothing shipped). Fix: use `<%~ %>` (raw) for JSON in every script context
(`ws-bridge`, `identity-boot`, `push-setup`).
- Also fixed: `_hyperscript` has **no ternary** (`?:`) — the screen-name span
`set my innerHTML to (window.aimId ? … : …)` threw. Replaced with a plain-JS
`.aim-screenname` painter in `identity-boot`.
- **Verified in a real browser (0 console errors):** typed a message, pressed
Enter → full round trip (compose → `chat:send` → ws-bridge ship → persist →
`msg-pointer` broadcast → `hx-get /message/2` → rendered). Presence line
"Guest signed on." shows. AIM aesthetic confirmed via screenshot
(`aim-working.png`): blue title bar, silver bevel, small serif transcript,
color-coded bold names, gray timestamps, toolbar, compose. `<marquee>` runs.
- Note: the Bliss inspector 🔍 button floats over the Send button (Enter still
sends). Handle still defaults to "Guest" until the `req.currentUser` change
is deployed to brb.city.
- **2026-08-06 — Mobile pass.**
- Root cause of "text too small on mobile": **no `<meta viewport>`** — page
rendered at desktop width and scaled down. Added a mobile head-injection on
structure 44 (viewport with `user-scalable=no` + `interactive-widget=resizes-content`,
theme-color, apple PWA metas, `touch-action:manipulation` to kill double-tap
zoom, favicon + apple-touch-icon links).
- Bumped fonts (16px base / 13px timestamps / 13px titlebar), enlarged compose
(3 rows, min-height 84px, 16px to avoid iOS focus-zoom), safe-area insets on
titlebar/compose.
- **AIM favicon:** wget'd the real AIM running-man logo (user OK'd for the test
instance), generated 32/180/192/512 + `.ico` via ImageMagick, uploaded to
structure 44 (served `brb.city/44/*`), wired manifest icons + head links.
- **Keyboard resize (user chose PURE CSS, zero JS):** layout already shrinks
via the flex column (`.aim-transcript { flex:1; min-height:0; overflow }`) +
`height:100dvh`; `interactive-widget=resizes-content` makes Android shrink
the viewport with no JS. iOS Safari has no CSS-only fix, so it falls back to
the browser's shift-up. (Briefly tried a `visualViewport` script — verified
it shrank 844→544px — then removed it per the clean-CSS decision.)
- **2026-08-06 — File upload (frontend compression).**
- **Architecture:** compression is 100% frontend (the handler sandbox exposes
no fs/sharp/ffmpeg). Images: canvas downscale + JPEG quality loop to <1MB
(verified: crushed a 21MB PNG). Videos: **ffmpeg.wasm** transcode to H.264/AAC
mp4 targeting a bitrate from clip duration (verified: **4.6MB → 986KB**,
playable 640×360). Other types: uploaded as-is → inline `<a>` link.
Images/videos embed as `<img>`/`<video>`. Toolbar `attach` control.
- **Upload path = disk.** `POST /upload``require('files').saveFile`
`public/<sid>/<name>`, served at `/<sid>/<name>`; returns same-origin path.
Found + fixed a framework bug: `saveFile` did `path.join(__dirname,"public",
structureId)` with a **numeric** id (only worked from the workshop route
which passes a string) → `String()` coercion in `index.js` (reproduced
before/after). ⚠️ **Needs the index.js deploy to brb.city** for uploads to
persist live (same deploy as `req.currentUser`).
- **ffmpeg.wasm hosting:** the 5 assets (ffmpeg.js, 814.ffmpeg.js worker,
util, core.js, 32MB core.wasm) are uploaded to the structure's files
(`/44/*`, same-origin) and lazy-loaded on first video; the service worker
caches them so they load once. Key gotcha (cost several iterations): in
ffmpeg.js 0.12, passing `classWorkerURL` forces a **module** worker, but the
UMD worker chunk is **classic** (`importScripts`) → "failed to import
ffmpeg-core.js". Fix: load `ffmpeg.js` same-origin and **do NOT pass
classWorkerURL** — webpack auto publicPath resolves its default (classic,
same-origin) worker to `/44/814.ffmpeg.js`. core/wasm handed in as
`toBlobURL` blobs.
- iOS caveat: ffmpeg.wasm is memory-bound in WKWebView; short clips fine, very
large/long ones may fail. Documented, acceptable for the playground.
### How to embed the pieces (the payoff)
```html
<!-- live transcript, no compose -->
<div hx-get="/aim/live?zone=abc" hx-trigger="load"></div>
<!-- compose only, no transcript -->
<div hx-get="/aim/compose?zone=abc" hx-trigger="load"></div>
<!-- a frozen slice of history -->
<div hx-get="/aim/frozen?after=0&before=50" hx-trigger="load"></div>
<!-- one message -->
<div hx-get="/aim/message/1?zone=abc" hx-trigger="load"></div>
```
Rule: **one zone == one ws-bridge (socket).** Compose raw pieces together only
with distinct zones, or just use `GET /aim/` (which includes exactly one).
Cross-structure port preserved: dispatch `chat:say`/`chat:send` on `body` and a
bridge ships it (sheepfriend/sheepgpt keep working).
---
## 11. Decisions (resolved 2026-08-06)