Skip to content

Archived from STATE.md — 2026-08-05 ​

Both SHIPPED to prod and stable. Moved out of STATE.md to keep it a "what is true right now" document; git log --oneline is the chronology.

Org chart collapse + "ตำแหน่งของฉัน" (2026-08-05 — SHIPPED to prod) ​

LIVE. main at 12f93e3 (+ a follow-up commit for the proof scripts), pushed; KKU VM deployed and verified against the SERVED bundle (buildId ff617f917f9b). Behaviour re-run against https://samo.md.kku.ac.th/team in headless Chrome — identical to local. 279 tests green; every proof script in tools/ green.

Rendered and driven in a real browser this time (the previous session could not). No Chrome extension — the extension was not connected — so it was done with headless Chrome + CDP over a raw WebSocket: /private/tmp/.../scratchpad/cdp.mjs in that session. Node 22 has a global WebSocket, so a ~40-line driver gets navigate / evaluate / screenshot with no dependency. Worth rebuilding if you need to SEE a change; --dump-dom alone cannot click.

  • โครงสร้างองค์กร is now an accordion. It rendered all 279 ตำแหน่ง / 402 people at once — 63,912px measured; collapsed it lands at ~3,000px. Each root ฝ่าย is a card showing its subtree counts ("65 ตำแหน่ง · 81 คน"). ARIA accordion pattern (heading wraps button). Toggling mutates the DOM, it does not re-render — a repaint would drop the scroll position. Collapsed bodies use hidden, deliberately NOT a 0fr height animation: that is what stops 400 lazy portraits being fetched for branches nobody opened. A ตำแหน่ง with ≤3 people and no children stays inline (106 hold exactly one). Search results are always fully expanded with no toggles. Station rows are a named CSS grid — flex-wrap stranded the count pill and the chevron on their own lines at 390px.
  • ตำแหน่งของฉันในทีม SAMO — new card under the home greeting and in the โปรไฟล์ modal. A ทีม SAMO grant used to be invisible to the person holding it. Fed by public.get_my_team_seat() (migration 0109, applied): definer, takes NO argument so identity comes from auth.uid() and it cannot be aimed at anyone else; hand-built jsonb allow-list, never returns setof. Proof: tools/seat0109-my-seat.mjs (17 checks, incl. anon refused over real HTTPS and "the payload carries no other person's kkumail / รหัส"). The CTA respects the door it opens: a passport-only grantee is sent to /passport/, not /admin/, which would bounce them (ADMIN_FEATURES).
  • src/js/team-vocab.js is new — PERM_CATALOG / VS_DEPTS / PROJECT_SEATS / ADMIN_FEATURES moved out of the admin-only team/index.js + admin-main.js so the public card names things the same way. Behaviour unchanged on both sides; the user asked for no changes to the admin ทีม SAMO UI and there are none.

Two bugs found by the scan, both now in .claude/rules/mistakes.md:

  1. revoke ... from public did NOT strip the authenticated grant that this database's DEFAULT PRIVILEGES hand every new function — in the public schema, not just passport. team_node_path shipped world-callable on the first apply. Verify ACLs from pg_proc.proacl, never from the migration text.
  2. Two proof scripts were failing/mis-reporting for CORRECT reasons (prof0095 assumed the probe account is never a named prof_id; seat0109 substring-matched a placeholder kkumail = '-' against uuids). Both fixed.

Known, NOT fixed — needs the user's call:

  • ฝ่ายเอิงtest is live on the public org chart (root ฝ่าย, 7 ตำแหน่ง, 5 people, one ตำแหน่ง literally named hi). It is test data visible to the world at /team. Deleting it is a data change in ทีม SAMO, so it was left alone — ask before removing.
  • One team_members row carries kkumail = '-' (ชญาภา เลาหะตานนท์). Harmless today; ตรวจสอบข้อมูล should be showing it.

Release notes + versioning + the IT panel (2026-08-04 — SHIPPED to prod) ​

LIVE. main at 28fa020, pushed; tag v4.4.0 pushed; KKU VM deployed and verified against the SERVED bundle (buildId 9f65ec53b172, /build.json now reports {"buildId":…,"version":"4.4.0"}, /updates → 200). 265 tests green.

STILL NEVER RENDERED IN A BROWSER BY AN AGENT — the Chrome extension was not connected for this whole session, so every layout/animation decision was reasoned about and unit-tested, never seen. The user reviewed it by screenshot and caught one thing tests cannot (the sticky bar reading as a cut-off rectangle). If anything looks wrong on / or /updates, that is why.

  • /updates — the public changelog. Content is src/data/changelog.js (22 curated releases, 2026-04-30 → 2026-08-01, condensed before July because that stretch ran ~13 commits/day). Reached from the footer's เกี่ยวกับเรา column and from the version chip in the footer bar; off-tablist tab like pills-article-tab, path route /updates in PATH_ROUTES.
  • A real version system — docs/VERSIONING.md is the policy, read it first.MAJOR.MINOR.PATCH with MAJOR redefined as "the portal's SCOPE changed" (SemVer's "breaking API change" can never fire on a website, so it would pin us at 1.x forever). 4 majors / 18 minors → current v4.4.0, assigned retroactively. npm run release derives the bump from Conventional Commits, drafts the changelog stub, and optionally tags; it never pushes. v4.4.0 is tagged LOCALLY and not pushed — push it when you next push. package.json is the single source of truth; /build.json now carries {buildId, version} and __APP_VERSION__ is defined at build time. Tests enforce that each bump matches its tier and that package.json agrees.
  • "เบื้องหลังการพัฒนา" on the landing page (#devActivity) — THREE tiles (7 ระบบ · 22 เวอร์ชัน · 14 สัปดาห์) over a timeline of when each system opened (SYSTEMS in src/data/changelog.js). A fourth tile ("91 รายการที่อัปเดต") was removed: the user twice said it communicated nothing, and they were right — a count of changelog bullet points is a number only we can judge. The version and last-update date moved into the lead sentence instead. Home order is deliberate and was set by the user: banner → sign-in → ประกาศ → เบื้องหลังการพัฒนา → สถิติการใช้งาน → quick actions.
  • Two claims are BANNED from the panel and both have guard tests. (1) No "100% built in-house / ไม่ได้จ้าง" — this project is built with AI assistance and the claim overstated it; the user asked for it gone. (2) No cadence promise ("ทุกสัปดาห์") — real gaps run to weeks. Credit line reads "ดูแลโดย IT SAMO'69", not individual names.
  • Thai copy — four rules the user gave, learned the slow way over ~5 rounds. (1) No literal translations of English idiom: "SAMO Portal ในตัวเลข" (from "by the numbers") and "ชุมชน…ที่กำลังเติบโตและให้บริการทุกวัน" both read as AI output. (2) Professional register, not casual — "เว็บนี้ยังพัฒนาต่อเรื่อย ๆ" was rejected; think professional web agency. (3) Do not mix languages inside one group — a row reading "22 เวอร์ชันทั้งหมด / 4 Major release" is the complaint; the changelog hero is now all-English (Releases · Major releases · Changes · Weeks) because its eyebrow already says "Release notes", while the landing panel stays all-Thai. (4) LEVELS labels stay English (Major/Minor/Patch) — "รุ่นใหญ่/รุ่นย่อย" is a translation nobody says. I cannot reliably judge natural Thai — get the user to read new copy. Every string I own is listed in the git log for this session's final commit.
  • npm run check:icons is new — run it before using a Bootstrap icon.bi-passport / bi-passport-fill / bi-envelope-arrow-up were all added in bootstrap-icons 1.11 and both entries pin 1.10.5, so they rendered as empty boxes — silently, for months, in the ทีม SAMO permission modal and the profile "รอยืนยัน" badge. A missing glyph is not a 404 and not a console error. Full write-up in .claude/rules/mistakes.md. Passport now uses a plane, which is both correct and on-theme ("Life is a Journey").
  • SYSTEMS dates are LAUNCH dates. SAMO Passport was first dated 2026-07-22 — the day its DATABASE merged into this project, which no student experienced. Its real launch is 2026-05-12, in its own repo (samomdkku/samomdkkupassport, cloned at ~/development/samodevmdkku69/passport). Every other entry was verified with git log --diff-filter=A on the module or migration that introduced it. Known gap: Passport's launch has no release entry in changelog.js — adding one means renumbering every version after it, so it was left for a deliberate pass.
  • The sticky filter bar on /updates is a FLOATING ROUNDED bar, matching .samo-navbar. A plain white rectangle inside the 900px column reads as "a rectangle that got cut off" (the user's words) because its hard edges stop mid-page against the body gradient. Full-bleed was the other fix and was REJECTED: it needs overflow-x: clip to contain the width, which older iOS Safari does not support and would degrade to a horizontal scrollbar. It is also opaque, not frosted — blur over the green spine went muddy.
  • npm run gen:activity regenerates src/data/dev-activity.json from git. Not wired into build on purpose. --check fails when stale. It publishes no email addresses (repo is public, JSON is bundled) — a test asserts it.

The first version of the panel was wrong and was rebuilt — do not put it back. It showed commits (549), active days, lines added/deleted, longest streak and a GitHub-style commit heatmap. The user's objection was correct and is the general rule: those measure EFFORT, not outcome; lines-of-code and commit counts are discredited even inside engineering; and to a SAMO member a dense heatmap of nights and weekends reads as grinding, not competence. The panel now measures what exists that did not exist before. changelog.test.js has a guard test ("publishes no effort metrics") that fails if any of it creeps back. The heatmap data is still generated (a few KB, the honest record) and the validated 5-step green ramp is preserved in git history if it is ever wanted for an internal-only page.

Still needs a human, same reason as the item below: the Chrome extension was not connected, so none of this has been rendered. Check on / and /updates: the launch timeline flips from a vertical spine to a horizontal track at 768px and the connecting line lands on the nodes in both; the changelog hero aurora does not bleed sideways (it is margin-negative to full-bleed past .container-fluid px-4); the sticky filter bar's sliding pill sits under the active button after a resize; and the per-release spine segments join up rather than leaving gaps.

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