Skip to content

Session notes — claude-2026-09-22 ​

One person's notes, never rewritten by anyone else. docs/state/HANDOFF.md §18b/§18c 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.

A student reported "อัปโหลดสลิป กดยืนยันแล้ว แต่ขึ้นว่า สั่งซื้อไม่สำเร็จ: [object ProgressEvent]". It became three things:

  1. the bug;
  2. a sweep for the same bug shape across every file picker;
  3. at the owner's request, a thorough bug hunt of the whole shop.

It ended with the product gallery BUILT, reviewed and fixed. Migrations 0202, 0203 and 0204 are applied to samo-dev first, then production. The deploys are all <== exit 0 — ran to the end; the sha's only home is the ✅ DEPLOYED line in STATE.md.

Read these before touching the same code ​

If you touch…Read first
any picked File that is uploaded latersrc/js/read-file.js header + docs/mistakes/frontend-ui.md ("[object ProgressEvent]")
checkout, placeOrder, place_shop_order§2 below + docs/mistakes/frontend-ui.md ("could place the order twice", "a recovery path placed AFTER the check")
safeUrl or any URL into innerHTMLdocs/mistakes/frontend-ui.md ("safeUrl said pair with escHtml()")
shop admin editors / order modaldocs/mistakes/app-state.md ("a save that awaits, then reads the current thing")
the buyer's own-order write pathdocs/mistakes/authz-rls.md ("the column guard allowed the columns, not the values")
product picturesdocs/SHOP-GALLERY.md (built — its header says where each piece lives) + src/js/shop/pictures-readers.test.js

1. [object ProgressEvent] — the slip bug ​

  • Cause. A picked File is a handle. The checkout read it for the preview at pick time, then again at submit. Phones revoke the handle in between (typically while the buyer is in the bank app), and r.onerror = reject printed the raw event.
  • Reproduced in headless Chrome by changing the file on disk after the preview read. That gave the exact string, with NotReadableError underneath.
  • Fixed with holdInMemory (copy the bytes at pick time) and one shared readAsDataURL that always rejects with Thai.
  • The sweep found six modules that parked a pick and read it later. All six now hold the bytes at pick. read-file.test.js keeps a registry of every module with a .files reference, so a new one is red until someone classifies it.

2. The shop sweep — how it was run and checked ​

  • Four areas: storefront + checkout, orders / API / uploads / QR, the admin panel (three reviewers, read-only), and the database (me: live policies and pg_get_functiondef bodies).
  • About 40 findings, every one re-traced before it was fixed. A few PLAUSIBLE ones were fixed defensively and never reproduced; HANDOFF §18b names them.
  • Database checks: shop0202-stock-rule is 22/22 on dev and production. All 14 of its refusals are red on the pre-0202 bodies. That ritual caught one case that was green for the wrong reason (tooling-proofs.md).
  • Client checks: every new guard test was mutation-checked. After the deploy, a headless load of /#shop showed that the LOADED core-* chunk carries the new strings, with no console errors.
  • Not checked: the checkout and admin flows were NOT clicked signed in. The first real order is the first real check.

3. The second pass ​

A fresh reviewer read the whole day's diff cold while I re-checked my own suspects. The one real new bug was the retry lookup, which sat after the stock check its own lost order trips. The rest were small; HANDOFF §18b lists them.

docs/SHOP-GALLERY.md. It rests on facts measured that day:

  • 1 product, 1 picture;
  • the stored master is 1200 px, so =w2400 and =s0 return the same bytes, and zoom needs larger masters;
  • every picture reader is in src/js/shop/.

The build order and the three owner defaults are in §9 and §10.

Open, owner-only ​

The shop's Apps Script upload limits need a production GAS redeploy (HANDOFF §18b, kept vague on purpose because docs/ is public).

  • 0203 (images + a trigger-derived cover + a shape check): 16/16 at the time; 20/20 after 0204 and the RLS allow case (§6). On dev and production. It errors before the migration, as documented. The stale-tab branch is asserted.

  • Headless Chrome against npm run dev at 390 px and 1280 px:

    • the pictures load, the colour jump and swipe tracking work;
    • the viewer opens inside the modal;
    • Esc and back close only the viewer;
    • no console errors.

    It first found every picture broken on localhost, from lh3's Referer check (frontend-ui.md), so pictures are now fetched with no referrer.

  • Production, through an ssh tunnel, because KKU-internal access to the public address broke mid-session (HANDOFF §18c): the real product in the gallery, and the lightbox.

  • Not driven: the admin picture strip, signed in. → DONE in §6 (tools/browser/shop-admin-strip.mjs).

  • The third-pass shop bug scan was started and stopped when the owner asked for the gallery. Its plan (a JS↔SQL differential of cartLineProblems against place_shop_order) is not done.

6. Final pass — bug scan, the admin strip signed in, docs ​

  • A second cold reviewer read the gallery commit; the admin strip had NO CSS on /admin/. Every finding and its fix is in the docs/SHOP-GALLERY.md header.
  • 0204 fixes the stale-tab trigger. Its two new proof rows are red on the 0203 body and green after.
  • shop0203-gallery gained the RLS ALLOW beside the anon DENY: a real shop admin, as authenticated, changes the same row. Now 20/20 on dev and production.
  • The admin strip was driven signed in: tools/browser/shop-admin-strip.mjs runs the whole cycle with Apps Script intercepted, then cleans samo-dev back to 0. How-to: skills/drive-the-browser.md §11.
  • Apps Script: the live endpoint runs the repo's code (verified with deploy:gas --verify). No .gs changed today.
  • docs/CONTEXT.md: the shop section gained images and 0202/0204. It had a STALE claim that buyers may INSERT orders (0199 removed that), now corrected. README.md "Key features" has the gallery.

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