Skip to content

Session notes — claude-2026-09-23 ​

One person's notes, never rewritten by anyone else. docs/state/HANDOFF.md §18e holds what is NOT done, and STATE.md holds what is true now. This file is WHY each thing was done, and exactly how it was checked.

▶ HANDOFF — shop pictures, the shop's Discord totals, and the Discord bot (nicknames + an admin panel). Released as v4.8.0. ​

Migrations 0206, 0207, 0208 — each applied to samo-dev first, then production, each with a rolled-back live proof in tools/. Every deploy was <== exit 0 — ran to the end; the sha's only home is the ✅ DEPLOYED line in STATE.md. v4.8.0 is tagged on the release commit and pushed.

Read these before touching the same code ​

If you touch…Read first
any shop picture URLdocs/SHOP-GALLERY.md §5 + memory shop-pictures-img-cache — only 4 widths exist
server/nginx-samo.confit is NOT installed by deploy.sh; /img/ must keep admitting PICTURE_WIDTHS (data.test.js)
parsing stored HTMLsrc/js/inert-parse.test.js — never innerHTML into a detached element
image-resize.jsSafari returns PNG, not null, for an unsupported type
server/discord-sync*.mjsdocs/mistakes/integrations.md (the 2026-09-23 entry) + skills/discord-role-sync.md (Nicknames, The admin panel)
the bot's channel messagespeople are NAMED in text; never <@id> (it rendered @unknown-user)
cutting a releasedocs/VERSIONING.md §3 — tag AFTER the release commit, never --write --tag

1. "Pictures on samoshop take so long" — three causes, the biggest outside the shop ​

Measured on production with headless Chrome (390 px, DPR 3, cache off), by phase: opening /shop ~47 MB, a product popup ~16 MB, the zoom ~6.5 MB.

  • 45 MB was news pictures. announcements.js pickCover/extractSnippet put every post's HTML into a detached <div> — which downloads every <img>. Control run: the same build with only that fix reverted downloaded 14.8 MB in 9 s. Fixed with DOMParser + a sweeping guard.
  • The product masters are 2 MB PNGs (Safari's WebP fallback kept the original). lh3 serves the master's format; -rw on PNG is lossless. JPEG (-rj, lh3 default quality 90) = 40.6 dB PSNR, no visible difference at 2× on the pattern's edges; the zoom asks quality 95.
  • Cards asked for the 1200 px cover.
  • The owner then asked for full quality AND speed: lh3's own first byte is 0.5–2.6 s, so nginx now caches /img/d/<id>=w…-rj on the VM (hit ~0.02 s). All 28 current picture sizes were pre-warmed.

After (production): grid 212 KB, popup 1.1 MB, zoom ~1 MB, every picture a cache HIT.

Owner's follow-ups, same day, all fixed and measured per frame:

  • the popup showed the cover, then scrolled to the pre-selected colour — now placed by a ResizeObserver before the first painted frame (old code in the same measurement: positions 0 → 684; new: 684 only);
  • a blank blue stage while loading — every slide has a placeholder;
  • the admin รายละเอียด box cut text off — .shop-autogrow on six shop-admin textareas (CSS field-sizing, a JS fallback for old Safari — NOT seen on a real iPad).

2. The shop's Discord order message gains the shop's totals (0206) ​

Slips awaiting review, orders (not cancelled) and their total, checked revenue — the admin dashboard's own rules, one constant (NOT_YET_REVENUE_STATUSES) held to the SQL by a test. The buyer's session cannot see other orders, so the notify service reads shop_order_totals() with the service key it already held — ONE pinned RPC, called only after the buyer's session proved a new order (functions/notify.test.js, mutation-checked). Verified on the VM with the real key: 11 orders, ฿2,926, ฿1,045 checked, 6 awaiting. Not yet seen in a real order's message.

3. The Discord bot — the "fetch failed" alert, nicknames, and a panel ​

  • The 13:57 alert was one transient network failure (the only one in a week of journal), healed in ~100 s. Unrecoverable which host: the code dropped e.cause. Fixed: host + cause in every error; the channel hears only after 3 consecutive failures and hears the recovery. Also fixed while tracing: queue rows deleted by id <= max (ids commit out of order), one refused write aborting the whole pass.
  • Nicknames (0207) ชื่อเล่น_#ชั้นปี_XXX-X for linked members. Rolled out in plan mode first. The first plan said 29; 25 of those already SHOWED the right text (no server nick) — fixed to compare what the member reads as, plan became 4. The owner approved; applied; the next full pass renamed 0. The owner was never renamed (a bot cannot) and that skip is logged only.
  • The announcement (updated 19 Sep text + a names section) was sent as a normal message; the 19 Sep role list was NOT resent (owner's choice).
  • @unknown-user: all 172 linked accounts are members. Mention parsing is off (no pings), so the message carries no user data. People are now named in text — markdown-ESCAPED: a first version stripped _ and rewrote every name. The rename message posted before that fix shows names without _.
  • The panel (0208), /admin/ → บอท Discord, permission discord_bot. No client grant on the tables; three definer RPCs. Proof 14/14 dev + prod. The panel was driven in a browser with stubbed responses (not signed in as a real admin); the bot side against the stub guild, pause branch mutation-checked; the live heartbeat read back from production.

Working docs. STATE.md is the status file and lives at the repo root, not here.