Berakhirnya Status PT — Final Consolidated Fix Wave (2026-07-03)
Branch feat/pt-flows-registry. Commits: b0252fe (backend) + d93951b (frontend). Status: COMPLETE, all suites green.
C1 — registry-gate scan window anchored at the pendirian year (live-SABH false FAIL)
backend/src/flow-engine/context/berakhirnya-context.ts passed selectedTahun ?? currentYear as the
loadPembubaranRegistryState start year — but on the auto-select path selectedTahun is the pendirian
transaction's year (Phase-2 SK-exact match), and the helper scans shards downward. The approved pembubaran
row (id_aksi 12, filed years after pendirian) sat above the window, so approvedPembubaran came back
false and BSBHP_REGISTRY_STATE false-FAILed every legitimate live filing (the "sudah berakhir" guard never
fired either).
Fix: exported registryScanStartYear(selectedTahun, currentYear?) = Math.max(selectedTahun ?? 0, currentYear).
- Rationale (in the code comment): pembubaran/berakhirnya rows are recent by construction — the filing is
happening NOW, and its prerequisite pembubaran was approved shortly before. - Manual path: the operator-typed year is correct under the same recency argument — a future-dated year
wins the max, a past one degrades to the current year. Traced: no other consumer ofselectedTahunfeeds
this scan (perubahan/akuisisi/peleburan use it only for their own lookups). - MAX_YEAR_SCAN coverage: it is 5 → the window reaches
currentYear-5. Liquidations longer than that
degrade to a FAIL the operator overrides — acceptable, documented in the anchor comment. - Test seam:
loadPembubaranRegistryStategained an optional injectable{ available, queryShard }deps
parameter (defaults = real SABH driver; zero behavior change). - Regression test (
berakhirnya-context.test.ts): mocked shards put the approved row in the CURRENT-year
shard with pendirian = currentYear-9. Old anchor →{approvedPembubaran:false}(the invisible case);
fixed anchor →true;registryStateRulethen PASSes on that snapshot. - Plan erratum follow-on appended to the header erratum block of
docs/superpowers/plans/2026-07-03-berakhirnya-status-pt.md.
C2 — review-page inline components lose keystrokes
frontend/src/pages/BerakhirnyaReviewPage.tsx defined ManualEditor/ClipCard (input-bearing) plus
FieldList/ApproveButton inside the page component — new function identity every render → React
remounted the subtree → uncontrolled defaultValue inputs reset + dropped focus on every keystroke.
- All four hoisted to module scope with explicit props (drafts/handlers); identity now stable across renders.
- Drafts clear on successful save (
setSpDraft(null)/setLalDraft(null)/ per-docclipDraftsprune in
onSuccess) — the stale-resend nit. - ClipCard payload → touched-keys-only:
ClipDraftis now partial; verified against
routes/berakhirnya.ts's pengumuman handler, which accepts partial bodies via"key" in bodyguards —
untouched NOT_FOUND nulls are no longer rewritten as MANUAL empty strings. - Keystroke-discriminator test: two sequential change events into the Surat Permohonan editor; asserts the
first input's DOM value survives the re-render and the final PUT body carries BOTH edits. Plus a
touched-keys-only ClipCard test. Red-checked: with the page stashed back to the inline-component
version, both tests FAIL (input resets to ""; payload carries all three keys); on the fixed page both PASS.
I3 — stale status-3 UI hint
(3 = pembubaran tercatat) (debunked — approved rows sit at status 1 regardless of aksi, per the 2026-07-03
erratum) replaced with (prasyarat pembubaran diverifikasi pada Hasil Validasi) + a code comment.
Minor fold — api-client known keys
frontend/src/lib/api-client.ts no longer does blanket Object.assign(err, body); it copies only the
envelope keys the backend routes actually emit alongside error — code, failing, missingSections,
status, detail (grepped across routes/*.ts). A hypothetical body.message can no longer override the
constructed message. New test covers the message-override + unknown-key-drop case; existing tests unchanged.
Verification
- Backend:
bunx tsc --noEmitclean;bun test src/flow-engine src/routes/__tests__/berakhirnya-routes.test.ts
→ 189 pass / 0 fail (includes the context + rules suites). - Frontend:
bunx tsc --noEmitclean; the four review-page suites (Berakhirnya/Pembubaran/Peleburan/
LaporanRups) + api-client + pembubaran-detect + 3 detect-banner suites → 40 pass / 0 fail
(33 + 7 across the two vitest runs).
Concerns
- The
statusenvelope key shadows nothing today, but copying it onto anErrorobject is unconventional;
kept because two routes emit it and a consumer may read it. ApproveButton/FieldListcall sites got two extra props each — slightly noisier JSX, the price of stable
identity without inventing a context.