ธีม
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 URL | docs/SHOP-GALLERY.md §5 + memory shop-pictures-img-cache — only 4 widths exist |
server/nginx-samo.conf | it is NOT installed by deploy.sh; /img/ must keep admitting PICTURE_WIDTHS (data.test.js) |
| parsing stored HTML | src/js/inert-parse.test.js — never innerHTML into a detached element |
image-resize.js | Safari returns PNG, not null, for an unsupported type |
server/discord-sync*.mjs | docs/mistakes/integrations.md (the 2026-09-23 entry) + skills/discord-role-sync.md (Nicknames, The admin panel) |
| the bot's channel messages | people are NAMED in text; never <@id> (it rendered @unknown-user) |
| cutting a release | docs/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.jspickCover/extractSnippetput 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 withDOMParser+ a sweeping guard. - The product masters are 2 MB PNGs (Safari's WebP fallback kept the original). lh3 serves the master's format;
-rwon 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…-rjon 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-autogrowon six shop-admin textareas (CSSfield-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 byid <= max(ids commit out of order), one refused write aborting the whole pass. - Nicknames (0207)
ชื่อเล่น_#ชั้นปี_XXX-Xfor linked members. Rolled out inplanmode 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.