ธีม
2026-08-10 — ชั้นปี becomes one derived fact, and v4.6.0 ships
Migrations 0145–0146. One VM deploy. The invariants this produced live in STATE.md's NEXT-SESSION PROMPT; this file is the reasoning.
What the owner reported
Five things in one message. The first was three symptoms of one cause:
when i change ชั้นปี in the main web, nothing happens, and when i change รหัสนักศึกษา the รุ่น does change, but ปี doesn't change … i've test changing my student id to 603070316-0 it shows ชั้นปี 5 on main web, it shows จบแล้ว in ระบบบ้าน(ปี10), it shows ปี5 in teamsamo. the data become not syncing
…plus: fold คำขอแก้ไขของฉัน, fold อาจารย์ในบ้านเดียวกัน, design a real workflow for adding a นักศึกษา who already exists (raw 23505 on screen), and "scan for bugs, update the release, push to prod".
0145 — the ชั้นปี model
Two independent defects, both reproduced from the authority before anything was written. Full write-up: docs/mistakes/postgres-schema.md.
person_mirror_downpushedpeople.yeardown; nothing carried it up. Soupdate_my_identity's own last statement (an unrelatedupdate people) reverted the edit the card had just made. Proved in a rollback transaction:tm=5 → tm=3 → tm=5.- ระบบบ้าน derived ชั้นปี, ทีม SAMO stored it.
house/fields.jshad predicted this in a comment since 0131 and it prevented nothing: 9 of 400 members were a year behind, 13 more had a ชั้นปี and no รหัส, 3 had a รหัส and no ชั้นปี.
The fix moved the rule to src/js/study-year.js (a rule two systems need does not belong inside one of them) and moved the INGREDIENTS to the placements: team_members gained cohort_year + year_offset, mirrored down from public.people, with people_fill_cohort giving the registry 0128's re-derivation rule.
Three ordering/safety details that were each nearly wrong — see the mistakes entry: the backfill must run AFTER the new trigger body (or it blanks 109 postings for the still-served bundle); the backfill CONVERTS a stored ชั้นปี into a ปีที่เข้า rather than discarding it; and person_mirror_down must save/restore app.team_sync rather than blank it, or a member with two ตำแหน่ง cannot save.
The owner's question — "should changing รหัสนักศึกษา change ชั้นปี, i think it shouldn't" — was answered yes, with the reasoning that their instinct is already satisfied by year_offset being a DIFFERENCE. Written into study-year.js's header and pinned by two tests.
Result, measured live afterwards
| before | after | |
|---|---|---|
| distinct ชั้นปี answers for the reported person | 3 | 1 |
| postings disagreeing with the registry | — | 0 |
| members with no ปีที่เข้า | 16 | 3 (they have no ชั้นปี either) |
0146 — the crest refcount
Carried over from the 2026-08-09 handoff. photo_reference_count() answered 0 for every house crest because it counted five photo_url columns and the crest is icon_url. The guard test written for exactly this hazard scanned for photo_url and reported green. Write-up: docs/mistakes/integrations.md.
The three UI pieces
- Folds (
my-house.js): native<details>, because this card has already shipped the hand-rolled version and it opened only on odd-numbered paints. A PENDING request is never folded; the newest decision stays visible; the rest fold with the count on the summary. - "This person already exists" (
house/index.js): three states, not one error. Write-up:docs/mistakes/frontend-ui.md. - The ทีม SAMO admin ชั้นปี box became read-only and computed, repainting as the รหัส is typed.
v4.6.0
86 commits since v4.5.0 → MINOR. All 46 notes came from PENDING; the 48 TODO lines tools/release.mjs emitted were deleted rather than rewritten, because every one was already covered by a note written at ship time. That is what the PENDING mechanism is for.
Verification
Deployed and checked from the SERVED artifacts, never the local files: /build.json → 4.6.0; six new Thai string literals present; a control string that must be absent returns 0; headless drive of /, /admin/, /updates, /vssound → zero console errors.
⚠️ studyYearLabel landed in the SHARED chunk analytics-*.js, which BOTH entries import — grepping only the entry bundles for it returns 0 on a good deploy.