ธีม
Mistakes — every entry, by area
GENERATED — do not hand-edit. npm run mistakes:index rewrites this from the ## headings in the files beside it. If a line here reads badly, fix the HEADING in the write-up, not this file.
Scan for a line resembling your symptom, then open that file. Usually faster: grep -rin "<phrase>" docs/mistakes/ — it searches the write-ups themselves, not just their titles. Read near-matches; most of these recurred elsewhere in different clothes.
The recurring CLASSES — the part that generalises to code not yet written — are in .claude/rules/mistakes.md, which every session already has.
supabase-client.md — supabase-js, PostgREST & the session lifecycle (19)
Open when: auth.js · db.js · anything calling supabase-js.
- Supabase Realtime in this app: token goes stale + RLS-gated events silently vanish (autoRefreshToken is OFF), and re-re…
- supabase-js
onAuthStateChangedeadlocks every subsequent call - supabase-js autoRefreshToken can stall, blocking subsequent requests
- supabase-js silent-success on RLS-blocked updates / deletes
- supabase-js gets into a bad state — bypass with
dbRest() - Android Chrome surfaces the supabase-js "bad state" hang on the FIRST call
onAuthChangefires on every refresh- PostgREST 400s on unknown URL query params
PGRST303 JWT expiredmid-modal when the 25-min proactive refresh misses- Synchronous first
onAuthChangefire flashes the sign-in gate before the session is restored (looks like "logged out o… - Hardcoded reserved-username lists rot when new staff accounts are added
- Synthetic email domain must be a real public TLD
- Email confirmation must be OFF in Supabase for synthetic emails
- Supabase
unlinkIdentityrequires ≥2 identities - supabase-js
updateUser({password})doesn't create anemailidentity - Account-switcher: capturing the OUTGOING session's tokens fire-and-forget races the session swap → first switch-back fo…
- (Passport repo) Forcing Google OAuth
hd=<workspace-domain>redirects to the domain's SAML IdP - "when i login in the preview, i got {"code":400,…"Unsupported provider: provider is not enabled"}"
- Three fetchers of one API, three error strings, and all three showed the raw JSON body
authz-rls.md — RLS policies, SECURITY DEFINER & read paths (33)
Open when: any policy, current_user_* helper, or definer RPC.
- RLS inline subqueries silently depend on the referenced table's RLS
- RLS row-level policies don't gate per-column writes
INSERT ... RETURNING(a.k.a.Prefer: return=representation) re-applies the SELECT RLS policy to the inserted row- Soft-delete changes the operation from DELETE to UPDATE
null in (...)makes araise-on-unauthorized guard fail OPEN- A per-recipient SELECT RLS policy is DEAD when a
using(true)public-read policy already exists on the same table (pol… - A SECURITY DEFINER RPC over a ROW-SCOPED table leaks the restricted rows unless it re-applies the scope
- GitHub-style "duplicate of #A" cross-references LEAK across a per-submitter visibility boundary
- Sanitizing ONE read path of a confidential column leaves parallel read paths leaking
- Publishing a table-backed directory must be a PROJECTION, never a public SELECT policy
- A row-level UPDATE policy with no column guard let a SUBMITTER self-publish to the public board
- Adding a DELETE to reference data turns every
coalesce(<flag>, false)lookup into a live fail-open - The row-level-UPDATE-without-a-column-guard class, found on a THIRD table
- An
ILIKElookup makes the id a PATTERN, not a capability - A
left joinonto a reference table fails OPEN exactly ascoalesce(flag,false)does - An UPDATE that moves a row OUT of your own SELECT policy fails with the WITH-CHECK error
- A VIEW without
security_invokerreads its base table with the VIEW OWNER's rights revoke all ... from publicdoes NOT remove an explicit grant toanon- Moving a read behind an identity-gated RPC breaks every caller that has NO identity
revoke ... from publicleaves the grant that the schema's DEFAULT PRIVILEGES gaveauthenticated- An anon-readable settings table published a staff member's real email
- An admin's decision was written to a column no student had any read path to
- An RLS policy with no table GRANT denies everyone, and looks exactly like the policy working
- An RLS policy's inline subquery is subject to the referenced table's RLS
- A bypass flag set with
set_config(..., true)stays set for the whole TRANSACTION, not the statement - Every signed-in account could read all 531 rows of
public.users - "someone could just book 16.40-20.00 kick me out"
- A column guard keyed on "is a professor" locked out everyone who is ALSO a professor
- 0181 — a read rule that LOOKS THE ROW UP cannot see the row being created, and a public-site flag had been covering for…
- 0182 — a schema move carried the GRANTS and dropped the ROW SECURITY, and the table it happened to was the one nothing…
docs/CONTEXT.mdsaid "any authenticated user can SELECT all" ofpublic.users- "Make admin samoshop can custom the price for each size"
- A buyer could write status, timeline and slips on their own order
authz-grants.md — The permission / seat / scope channel (22)
Open when: adding an access channel, a scope, or a seat.
- Adding a permission-based access channel leaves every ROLE-ONLY gate as a latent block
- "โมนา got pr permission in teamsamo but she can't delete pr ticket"
- A narrowing "scope" dimension added ALONGSIDE an unconditional full-access permission is DEAD
- The privilege-ESCALATING option must never be a select's default
- A capability key is not a ROLE — granting flat
projectsproduced a tab with no controls, because the app branches on… - When a SCOPED grant deliberately drops its blanket permission key, every reader of that key must learn the second signal
- The permission that manages the grant engine was the one the grant engine didn't honour
- A seat/scope dimension that is UNIONED with what it inherits is not a choice
- A permission channel has TWO halves — writes AND reads.
current_user_is_staff()is a role list, so every read gated o… - Deriving "which department is this admin" from a UI filter is not a permission
- A seat that grants a SHARED role must not be modelled as a new individual
- WEAKENING the meaning of a permission key silently PROMOTES every gate that still treats it as the strong one
masteropened the tab but not the ROLE-gated controls inside it- "when i select permission as master, i cant select sub of the หนังสือโครงการ"
- "I set SAMO Passport for the ฝ่าย and it doesn't show"
- The same bug in three more readers — swept for on purpose, 2026-08-30
- A scope line under
masterunderstates it - A SIXTH scope dimension — what "thread it through every gate" actually cost, itemised
- A "whitelist" that OPENED registration:
SIGNUPS_DOMAINS_WHITELISToverridesSIGNUPS_ALLOWED=false - Unlinking your Discord account was a permanent ฝ่าย role grant
- A claim reached
studentsaround the gate the importer goes through - "Why did มิกซ์มี่ get หัวหน้าฝ่าย PR?"
postgres-schema.md — Migrations, DDL, triggers & constraints (33)
Open when: writing a migration.
- Postgres has no
create or replace policy - A self-update column guard silently bricks EVERY new signup when it blocks a column another trigger legitimately writes
- Service-role seed can't UPDATE
role/permissions create or replace functionCANNOT change the return type- A
NOT NULLcolumn withON DELETE SET NULLis a latent contradiction - Recreating a function from the migration that FIRST defined it silently reverts every later one
- Hard-deleting a row referenced by an
ON DELETE RESTRICTFK fails 23503 - Check constraint must be dropped BEFORE updating to a new enum value
- (Passport) An
AFTER INSERT-on-auth.usersre-key trigger only fires for accounts that have NEVER logged into the pro… - A PL/pgSQL
RETURNS TABLE(... col ...)function silently ignoresORDER BY col - A self-update column guard must exempt the definer FUNCTION that writes on login
- A UNIQUE EXPRESSION index cannot serve
ON CONFLICT (col) - Seeding an OBSERVED range as if it were reference data
- Applying "create the parent on demand" at ONE call site instead of on the table
- "เปลี่ยนรหัสนักศึกษาเป็น 59… หรือ 64… แล้วรุ่นไม่เปลี่ยนตาม"
- A bidirectional mirror without an
is distinct fromguard is an infinite recursion - "เปลี่ยนชื่อเล่นในทีม SAMO แล้วระบบบ้านไม่เปลี่ยน"
- "when i change ชั้นปี in the main web, nothing happens"
- "why 18 august has rail show green 100% shouldn't it be 10%"
- "i can even book at 06.00 which shouldn't be"
- "it shouldnt show the rail as 100% in that 25%"
- 11 students' passport totals were higher than their own scans, and nothing could ever subtract
- "144 students cannot sign in" — a false alarm from reading the function instead of the trigger list
- A carried passport student would have signed in to 0 km
- A migration that applies cleanly on a fresh database and then fails at RUNTIME, far from the change
- I edited a migration after applying it, and samo-dev had been four migrations behind while STATE.md said "in step"
- A new table in
publicis born anon-writable, and nothing says so - A settings flag said
trueand controlled nothing - The test data used keys that turned out to be real
- "The registry wins" was implemented as "the import never speaks"
- "why does that photo and cohort year bug exist"
- The storefront said "sold out" for a size the server would sell
- A tab opened before a migration re-sends what it LOADED
frontend-ui.md — Bootstrap, CSS, DOM & the browser (104)
Open when: markup, modals, layout, touch, icons.
- Ticket renderers interpolate user-text into innerHTML → XSS
- A module shared across two shells carries shell-specific assumptions that silently break in the other shell
- An anon-INSERTable table's text columns are ATTACKER-controlled
- Re-opening an ALREADY-OPEN Bootstrap modal with
new bootstrap.Modal(...).show()stacks a second backdrop - A destructive-direction toggle without a confirm silently dropped a privacy guard (vs_categories.personal flipped to pu…
- A modal that closes on save makes every edit a round trip
- A manager modal opened ON TOP of a form must repaint that form's inputs
- Attribute-driven visibility: check that EVERY value in the markup has a handler, and which way an unhandled one fails
- A directional action whose direction lives ONLY in a label on the other party's row gets read backwards
touch-action: noneon a drag handle makes the page unscrollable THERE- A partial left behind by a restructure is a DECOY
- A shared
render()that repaints a pane another module owns will destroy that module's in-progress input - A
busyflag that RETURNS EARLY silently discards the second action - A Bootstrap icon name from a LATER release renders as nothing
- iOS Safari
100vhhides the bottom of a full-height drawer - Pane-scoped DOM selectors break when the shell is rewritten
- A full-height centered page with
height:100% + overflow:hiddenis unscrollable on mobile when the content is taller t… - Bootstrap tab JS keeps the parent dropdown open
- Bootstrap mobile offcanvas +
data-bs-toggle="pill"race form.reset()clears the file input butfileInput.filesstill references the old Fileform.reset()clears hidden inputs- HTML5
requiredon a hidden field silently blocks form submit - Adding
prefers-color-scheme: darkto ONE component in a light-only app makes just that component go dark on a dark-mo… - A
data-role="x"element with no matching toggle in the JS is visible to EVERYONE - Bootstrap gives EVERY modal the same z-index
- A class in the markup with NO rule in any stylesheet is invisible in review and looks exactly like a broken value
- An indicator that links to a LIST moves the work instead of removing it
- State parked on a REUSED DOM element outlives the record it describes
- Uploading a replacement photo on PICK leaves the previous file in Drive forever
- A filled "danger" style made an UNCHECKED checkbox look ticked
- A second pass over the same controls silently UNLOCKED the checkbox the first pass had locked
- "ลบสมาชิกไม่ได้" — the delete button did nothing at all, and BOTH ways it can do nothing were silent
- "แก้ไขข้อมูล ของระบบบ้าน — ต้องกดหลายครั้งถึงจะขึ้น" — one listener per re-render + a toggle reading its own state
- A chooser that opens as an empty placeholder while its vocabulary loads SUBMITS the empty value
- "ปฏิเสธ ไม่ทำงาน แต่อนุมัติทำงาน" — the same suppressed-dialog bug, on a different button
- "แก้ไขสมาชิก shows ชื่อ นามสกุล as blank, that isn't good"
- Adding an
awaitbefore the modal closes re-opened a double-submit window - "เพิ่มสมาชิก ไม่ทำงาน" + "ค้นหาคนจากระบบ ไม่ขึ้นรายชื่อ"
- The ลบ button on a สาขา row rendered OUTSIDE the modal on a phone
confirm()on a SAVE path, not just a delete/admin/#vsopened the VitalSound workspace for an admin with no VitalSound grant{"code":"23505" … "students_kkumail_key"}in an alert()- "แก้ไขสมาชิก … ค้นหาคนจากระบบ … พู่กัน picture become myself"
- The ยกเลิก button in
askConfirmdid nothing - A VIEW is not a BREAKPOINT — scoping a layout to
@mediaand then making it user-selectable - A markup refactor silently unhooked every
> .org-stationselector justify-content: centermakes the overflow of a scroll container UNREACHABLEflex-wrapdoes nothing insidewidth: max-content- "เข้าสู่ระบบด้วย Google" read as KKU-only
- "เข้าสู่ระบบด้วย Google ... it also gmail.com email etc."
- "when i zoom, it renders some different view then switches back"
- A blank canvas is not a diagnosis — the graph had flown past the far plane
- "the picture render wrong ... zoom also bug"
- "the picture on ipad still bug" —
positionin<foreignObject>drops the transform - A DEPTH NUMBER cannot name a level of a ragged tree
- "It shows 4 lines to อุปนายก, ฝ่าย PR, ComArt, IT"
- "ฝ่ายวิชาการ inside ฝ่ายรังสีเทคนิค shows different color"
- แผนผัง became a staircase — one structure, two different drawings
- จองโควตา Claude rendered unstyled — CSS in the wrong ENTRY
- "on ipad, when touch, it mess up between scroll and adding the booking"
- "in the next week it shows ยังไม่มีตำแหน่งในผังทีม"
- "the rails it got overlap with the booking making it look weird"
- "why there's 50% rails in the period that has people book"
- "พอดีจอ" collapsed the calendar to its minimum row height
- An inline
<b>rendered as a second heading - A confirm dialog offered two buttons that both began with "ยกเลิก"
- "i see something weird in the box booking behind" + "10:00100%"
- A tag positioned where the thing it describes always covers it
- "it shows only 16.00 not 16.00-21:00"
- The 5-hour frame described the window; people were asking what they could put in it
- "ใช้จริง" drew the gauge reading instead of the usage
- "why does it show color weird" / "i still see rail weird"
- "ย้ายปีงบ แล้วโครงการหายไปเลย" — a follow-the-row fix that only fired half the time
- Adding one cell to a flex row collapsed the project name to one character per line
- "it doesn't care about ระดับ that i config in the admin teamsamo"
hiddendid nothing, and only Bootstrap's CDN stylesheet was hiding it- A CSS block lifted out of a deleted commit came back with three rules silently commented out
- "i press สร้างบัญชีและเข้าสู่ระบบด้วย google and button do nothing … but on google app it works"
- "even i press the โหลดใหม่ … it still show it"
- A SyntaxError blamed on the DOCUMENT, in a page whose own scripts all parse
- A second markup site for the same navigation had no handler, so it full-reloaded
- The สถิติ quota panels had 29 tests and had never been LOOKED at
- "The tool renders fine" — but ~160px of dead space under it, and every test was green
- "ไม่พบใครที่ตรงกับ …" stayed on screen with the matching rows listed right beneath it
- "the orange highlight box is not aligned"
- A block styled with Bootstrap looked perfect in the editor and unstyled on the page
- GrapesJS hid its own block panel, and the empty canvas WAS the complaint
- Fixing the transport made a latent bug REACHABLE
- "The columns refuse to stack on a phone"
- "สมาชิกทีม SAMO ที่ไม่ได้อยู่ในไฟล์รายชื่อ"
- "ลิ้งค์จะขึ้นใน discord แต่ไม่ได้ขึ้นใน PR Staff Dashboard"
- "แต่ใน web มันไม่เห็นใช่ไหม" — the SECOND site of the same bug, found by sweeping columns against readers
- ผังตามสาย's own file header warns about hover-only info, then a sibling file did it anyway
- "my ipad has extension of stay, userscripts … why other webs don't have issue but this web does"
- A layout fix scoped to ONE tab hid an overlap that existed on every tab
- "when thumbnail show preorder, after i click into the product … i dont see where it said preorder"
- "อัปโหลดสลิป กดยืนยันแล้ว แต่ขึ้นว่า สั่งซื้อไม่สำเร็จ: [object ProgressEvent]"
- "scan for bugs in samoshop, all thoroughly"
- Checkout: a buyer who came back from the bank app could place the order twice
- A recovery path placed AFTER the check its own failure trips
- Every product picture was broken on localhost and fine in production
- The admin picture strip worked and was UNSTYLED
- "รูปใน samoshop โหลดนานมาก" — the shop tab downloaded ~47 MB of pictures, 45 of them news pictures nobody could see
- "กดสินค้าแล้วเลื่อนไปหน้าปกแล้วเลื่อนกลับมาสีดำ"
app-state.md — Routing, read-state, caches & serialization (26)
Open when: URL state, per-user "seen", import/export.
- "Unread" highlight inside an item vanishes the moment you open it
- Per-user read-state means a newly-granted account INHERITS the whole backlog as unread
- Migrating a SHARED workflow account to a personal one moves the AUTHORIZATION but leaves every uid-bound row behind
- Module-scope caches make an in-place account switch show two accounts at once
- A path-only router silently discards sub-state
- A snapshot table that COPIES a foreign resource id makes the original's delete path destroy history
- An allow-list feeding a BACKUP has the opposite safe default from one feeding a public projection
- A scroll-to-top fix applied in the tab handler misses every link that navigates programmatically
- An upsert that sends EVERY column wipes the ones the file did not have
- An export that carries a GENERATED column re-imports as the real one
- Stripping a คำนำหน้า off a name renames the people whose name STARTS with one
- "แก้ชื่อในหน้าตัวเอง แล้วชื่อ-นามสกุลในระบบบ้านสลับกัน"
- "this person is the same person but it detects wrong because no email"
- The checkout form kept the PREVIOUS account's email after an in-place account switch
- An INSERT is a write path too — the import guard covered UPDATE only
- "in next next week, it still show ใช้ไปแล้วจริง value, which it would be reset by then"
- "I'm looking from เจ้าหน้าที่คณะ and I don't see file highlighting anymore"
- A DELIBERATE omission in the account purge cost 42 of 43 comments their edit button
- "why does the week still say 61% used when nothing has measured it for four days"
- A trailing slash matched no route and landed on the home tab, silently
- computeCensus() and computeGaps() classified the same held row differently on a whitespace-only cell
- sai-grid.js's held-row split was a paraphrase of splitHeld(), not a call to it
- sai-grid.js's numeric-สาย grouping was a third re-typing, and its own comment named the wrong gaps.js group
- The ผังตามสาย legend was a fourth copy of the same rule
- Shop admin: a save that awaits, then reads "the current thing", writes to whatever is current NOW
- A student's ชื่อเล่น could run as a formula in an admin's spreadsheet
integrations.md — Notifications, Apps Script & Google Drive (33)
Open when: notify, GAS handlers, Drive URLs.
- "Email notification doesn't work" = a silent gate, not broken plumbing (verify the channel end-to-end BEFORE rebuilding…
- Discord-notify drops leave NO trace — Pages Function logs aren't retained, so add a durable log before debugging
convertDriveUrl(url, size)silently ignoressizefor an already-lh3 URL- Never append a query string to an
lh3.googleusercontent.comURL - Renaming a folder breaks every guard that matches it BY NAME
- A "validates before touching anything" probe is only side-effect-free on the path that fails FIRST
- A public Apps Script web app is an UNAUTHENTICATED API
- Adding a Google service to an Apps Script web app widens its auto-derived OAuth scopes
navigator.sendBeacondoes not follow HTTP redirects- Fire-and-forget GAS notifications +
muteHttpExceptions:true= invisible drops - GAS Cloud Logs are EMPTY for any browser-fetch call (logs simply not recorded)
- Async click handlers run concurrently → parallel Discord POSTs hit per-webhook rate limit
- Cloudflare 1015 (per-IP rate limit) blocks GAS → Discord webhook traffic, NOT Discord's own webhook bucket
- Notification
notify_*_in_appflags gate the in-app fanout - Awaiting the serialised Discord notify queue blocks the UI re-render (status/comment clicks feel sluggish)
drive.google.com/thumbnail?id=…images 302-redirect → intermittently BLANK on iOS Safari (iPad) while desktop is fine- A vendor manual's field list is not the contract
- A refcount is only as true as its list of referrers
- "เปลี่ยนรูป เปลี่ยนรูปแล้ว แต่ในไดรฟ์ยังมีรูปเก่าอยู่"
- "ลบรูปใน Drive แล้ว แต่เว็บยังขึ้นรูปเดิม"
uploadPRFilehad no counterpart, so every announcement cover ever re-cropped is still in Drive- The crest refcount could not see the crest
- The Discord alert told a human to run the command that CAUSED the error
- Removing
@herefrom two builders left three ways to put it back - A preview deployment could post into the real ฝ่าย Discord channel
- A test notification read as a real incident, because every builder hardcodes its own alarm
- The dev database emailed a REAL staff member, because it is an exact copy of production
- "ทำไมไม่เห็นข้อความใน Discord ของฝ่ายบริหารองค์กร"
- Google answers /exec with an HTML page, and the student gets "Unexpected token '<'" on the file they just picked
- The Discord app id was redacted from
docs/and left in.claude/rules/ - Every Discord role write would have thrown before it was sent
- Provisioning created a duplicate Discord role beside one that held 5 channels
- "Discord sync — error — retrying in 10s: fetch failed" — one network blip posted as an outage, and three quieter defect…
deploy-hosting.md — Deploy, nginx & caching (26)
Open when: deploy.sh, nginx, cache headers.
rsync --deleteon deploy yanks the previous build's chunks out from under OPEN tabs- A deploy script that
git pulls ITSELF and keeps running will execute a garbage fragment - "Login is still there so the cache must be cleared"
- CI
npm testfails on Node 20 — supabase-js throws "Node.js 20 detected without native WebSocket support" at import - nginx subpath app: bare
/passport(no trailing slash) silently serves the wrong SPA - nginx without an
$uri.htmlfallback breaks EXTENSIONLESS deep links that a retired Cloudflare-Pages host used to serv… - Dropping a column while the SERVED bundle still names it
systemctl enable --nowreported success and scheduled nothing- "I grepped the served bundle for the string I just changed and it is not there
- "There is no preview deploy" — the contributor guide denied a pipeline that had been running for weeks
- GitHub was silently DELETING words out of the docs, and nothing could tell us until we rendered them somewhere strict
- A missing docs page answered HTTP 200, so a dead link looked healthy
- The deploy goes silent after "==> docs site"
- "After I login on the preview link, it navigates back to samo.md.kku.ac.th"
- The deploy exited 0, published the app, and silently skipped
/docs - Six runs, four failures, no diagnosis
- The guard said "nothing on pages.dev reaches production"
- A deploy verified green, then the SERVED bundle did not contain the change
- The preview would have bounced ITSELF to production
- "Delete this Cloudflare Pages project" is not one call
- A subpath in
DOMAINre-prefixes the routes INSIDE the container too - Compose v2 ate every
$in the argon2 admin token, and the app downgraded itself to plain text instead of failing .mjsserved asapplication/octet-stream/admin/assets/<bundle>.jsanswers 200 with the SPA fallback, so grepping the wrong path reads as "the code was never…- A missing nginx
locationdoes not 404 install.shexited 3 on a successful install
tooling-proofs.md — Proof scripts & verification discipline (66)
Open when: writing or trusting a tools/*.mjs proof.
- Two implementations of one rule drift silently
- Debugging note:
tools/db-query.mjsCOMMITS - RLS does not RAISE on UPDATE/DELETE — a proof that asks "did it throw?" scores a fully-blocked write as permitted
- A proof script that fails for a CORRECT reason gets ignored
pg_get_functiondefover every function 42809s on aggregates- A proof failed for a CORRECT reason because its subject was hardcoded
- Four guards were reading a MANGLED file
- A proof whose subject was a SHOP ADMIN reported that a buyer could set an order total to ฿1
- Checking the proofs by hand produced TWO false alarms in a row
- A proof that ERRORS is not a proof that fails
- A browser probe measured its coordinates before the page scrolled
- A comment listed four boundaries and the code had three
- A control threshold that assumed the proof runs early in the quota week
- Two proofs ERRORED for six days because their scenario needed a week with room left in it
open(p, "w")truncates before theread()you passed to it- A proof went red fifteen minutes after the app started working again
- STATE.md said a proof was red that had been green for a day
which pg_dumpsaid it was not installed, and it had been installed all along- A
pg_dumprestore made the copy MORE permissive than the original - A refresh script printed "identical to production" while refreshing nothing
npm test | grepreturned success while the suite was failingurllibgot 403 from Discord and I reported the service as DOWN- The verification command in STATE.md named a sha two deploys behind
- "The VM can't do mail" — one probe answered a narrower question than the sentence it was written into
- A dashboard was about to report 83% of a quota that was really at 7%
- Impersonating a user through the Management API works for one statement and silently stops working at the next
npm run proofsagainst dev ran two proofs against PRODUCTION and printed one green summarymain's CI was red for a day because a guard could not see the commit it was checking- A CI gate whose red depended on jsDelivr, and two tests that passed over deleted code
npm run deploy:owedsaid "production is serving current code" while /docs was a whole rebuild behind- Dead-link checking on the docs site had been off since the day it was built
git pushto main refused with GH013 right after the org transfer, while the protection proof was all greendeploy:owedwent blind to a page production serves- A proof that was GREEN by hand and RED under its own runner
- A check whose own audience could not run it
- A guard that asserted the implementation, and went red on a refactor
- The browser smoke covered the smaller entry, and nobody noticed for months
- "Unset is SAFE" — a guard that would have called a preview pointed at production a PASS
- A proof that was written, run by hand, committed
pass-hardeningreported 9 failures and the database was innocent every time- The install guide explained a permission failure that a public repo cannot have
- The setup guide's four keys: two the app never read, two nobody should have had
npm run migrate:status --devanswered about PRODUCTION for monthsenv:pullrepointed the user's personal Bitwarden CLI, and its guard was satisfied by a commentnpm run env:pullprinted "Signing in." and then nothing, for ever- CI was red for 19 consecutive runs, and nobody read the last one
- The first migration replay condemned 27 healthy migrations, then found the truth
- A throwaway branch deleted two files' worth of work, and the commit message covered it up
- A registry sweep that reported CLEAN over a policy planted in the same transaction
- Repairing our own damage: the fix that bills the user is the wrong fix
- A control reported the detector blind, and the detector was fine
- A
--rows-onlyflag turned the sweep's own anti-vacuity rule against it: it printed "0 orphans" for a direction it nev… - A brand-new guard was green on the laptop and red on CI within an hour
- Two live proofs went red, and STATE.md recorded the wrong cause
- A redaction written against the healthy shape prints the secret on the broken one
team0183had the "describes today's data" defect TWICE, and one of them was 42 ticks from a false red- Two more proofs whose SCENARIO had run out
- I "corrected" a number that was already right, because I checked its value and not its UNIT
- I recommended a cheaper option without measuring it, and it saved one role out of fifty-one
- A proof whose subjects were "whoever comes back first", three times in one file
- The tool wrote both halves of the answer and neither was the file to upload
- A repair created the rows and left the old ones open
- The year-admin CSV's ชื่อเล่น column was blank for every held row
- "Run failed: build" on a push whose
npm testwas green - The night agent's memory lived on the VM and nothing ever synced it
- shop0202's "non-Google slip URL is refused" was GREEN on the code it exists to catch
passport.md — The Passport app's own write-ups (40)
Open when: anything under passport/ — scan, stamps, certificates, the dashboard.
- Wrapping Thai correctly (no spaces → break at word boundaries, not mid-syllable)
- Canvas postage-stamp: perforations bulge OUT instead of notching IN
- OAuth redirects to production instead of localhost in dev
- OAuth on a LAN IP (iPad testing) bounced to production even with the IP allow-listed
- sendBeacon does NOT follow redirects
- Passport pages shift up/down when changing pages (desktop)
- Google Drive images taint the canvas (certificate/QR export fails)
- Certificate features look broken until the table exists
- User memories/photos "disappear"
- Negative or non-numeric km
- Permissive RLS / hardcoded admin
- iPad: modal clips / page "locks up" when the keyboard opens
- iPad: blue background shows under the book on overscroll
- Flight Log / Leaderboard list overlaps the barcode footer
- Info page: only the flight log scrolls (don't make the whole page scroll)
- Admin uploads are deleted if the activity/cert isn't saved
- Scans are immutable snapshots — don't rewrite/delete past history
- Certificates are NOT season-scoped (reverted 2026-06-07)
- "Delete all data" / bulk delete: filter type + RLS DELETE policy
- Enabling RLS without ALL the policies locks a table (db/0007 → 0008 + 0009)
- "Current" SamoYear/Season = the open row (ended_at IS NULL)
- Vite is multi-page
- Dashboard inline onclick handlers must live on
window(ES-module trap) - Sidebar / bottom-nav buttons dead on a slow or cold load (esp. iPad)
- HTML partials & CSS @import indexes (modular build)
- Mobile dashboard: bottom nav hidden by iOS toolbar / top bar not at the edge
- Dashboard CSS silently overridden by main.css globals (specificity / inheritance)
- "Equal-height columns" / inflated gap from a row-spanning grid item
- Per-tab edge spacing differs on mobile (centred flex body shrink-to-fits .app-body)
- Bottom nav covered by Safari's bottom toolbar (min-height beats svh height)
- drop-shadow on a masked element gets clipped away (stamp tiles)
- se-* colour classes are shared by stamps AND flight-log icons
- Passport
buttonreset silently strips single-class button styles (specificity) - Certificate name "misaligned" on some devices but not others
- Letting a user delete their OWN scan is allowed (mis-scan recovery)
- QR poster is now a template image with hard-coded slot coordinates
- A root-absolute
public/asset path ('/foo.png') 404s on the/passport/VM subpath - Poster stamp / cert bg intermittently missing = lh3 rate-limiting (HTTP 429), NOT a bad link
- A permission granted in ANOTHER repo's admin console is decorative until this app calls the RPC that reads it
- The upload badge image button is gone
402 entries across 10 files.