fix(reader): load all pasals SSR for structured (BAB-based) laws#21
fix(reader): load all pasals SSR for structured (BAB-based) laws#21daffaromero wants to merge 5 commits intoilhamfp:mainfrom
Conversation
The usePagination flag was based solely on pasal count (>= 100), but client-side infinite scroll doesn't work when pasals are rendered per-BAB server-side — only the initial 30 SSR pasals were ever shown under their BABs, leaving all subsequent BABs empty. Fix: skip client pagination when the law has BABs/aturan/lampiran structure nodes, and always fetch the full pasal set SSR instead. Flat laws (no BABs) with 100+ pasals still use infinite scroll. Co-authored-by: Claude <noreply@anthropic.com>
…types The original hasBABs check only tested for bab/aturan/lampiran, but the BAB rendering path fires on any structural node (babNodes.length > 0 includes bagian and paragraf nodes too). A law with bagian-only structure and 100+ pasals would still regress under the previous check. Replace hasBABs with hasStructure = structure.length > 0 — aligns the pagination guard directly with the rendering condition. Co-authored-by: Claude <noreply@anthropic.com>
Root Cause AnalysisThere were two related issues, both in Root Cause 1: Pagination mode ignores document structureThe page has two rendering strategies:
The strategy was chosen by a single flag: const usePagination = (totalPasalCount || 0) >= 100;This kicked in for any law with 100+ pasals — including structured ones like UU Cipta Kerja (186 pasals, many BABs). The problem is that structured laws don't use a flat list renderer. They use a BAB-first tree renderer: the code maps over babNodes.map((bab) => {
const directPasals = allPasals.filter((p) => p.parent_id === bab.id);
const nestedPasals = allPasals.filter((p) => subSectionIds.has(p.parent_id ?? -1));
// renders directPasals + nestedPasals under this BAB...
})When pagination mode is on, In short: the paginator and the BAB renderer operate on the same data array but at different points in the lifecycle. Pagination assumed a flat list; the BAB renderer assumed the full set. The assumption was never reconciled. Root Cause 2: The initial fix used an incomplete structural checkThe first commit fixed this by introducing const hasBABs = (structure || []).some(
(n) => n.node_type === "bab" || n.node_type === "aturan" || n.node_type === "lampiran",
);
const usePagination = (totalPasalCount || 0) >= 100 && !hasBABs;This works for UU Cipta Kerja and most Indonesian laws. However, the BAB rendering path actually fires on any structural node ( A law structured with only The second commit tightens this to: const hasStructure = (structure || []).length > 0;
const usePagination = (totalPasalCount || 0) >= 100 && !hasStructure;This aligns the pagination guard exactly with the rendering condition, so they can never disagree regardless of which node types are present. Why it wasn't caught earlierThe bug is invisible for most laws:
UU Cipta Kerja is one of the few laws that is both large enough (186 pasals) and has a deep BAB/Bagian structure, making it the natural test case to expose this. |
|
Here is the fix in action @ilhamfp: |
Laws like UU 6/2023 (Ciptaker) wrap a full law as LAMPIRAN. The parser picks up the LAMPIRAN's table of contents as real BAB nodes, producing heading-only sections with no Pasal content in the reader. Add a lightweight parallel query fetching all parent_id values for pasals of the current work. Build structuralIdsWithPasals Set. Filter babNodes so only top-level structural nodes (BAB/aturan/lampiran) with at least one pasal directly or via a direct child section are rendered. Sub-sections (Bagian/Paragraf, parent_id != null) are kept unconditionally. Co-authored-by: Claude <noreply@anthropic.com>
The previous filter only checked direct children of each top-level BAB node when determining whether it had pasal content. This missed the BAB → Bagian → Paragraf → Pasal nesting depth documented in the schema, causing those BABs to be silently filtered out. Replace with a parent→children map + recursive hasDescendantPasal() that walks the full subtree at any depth, so a BAB is only filtered if no structural node in its entire subtree is a direct parent of a pasal. Co-authored-by: Claude <noreply@anthropic.com>
…l to all structural nodes Remove the unconditional short-circuit that passed any structural node with a non-null parent_id through the babNodes filter. Phantom TOC-BABs inside a LAMPIRAN have parent_id = lampiran_db_id (non-null), so the guard was letting them through despite having zero pasal descendants. Applying hasDescendantPasal() to every structural node regardless of depth fixes UU 6/2023 (Cipta Kerja): the duplicated TOC BABs parsed from the LAMPIRAN TOC pages are now correctly filtered out while real BABs and their Bagian/Paragraf sub-sections remain (they ARE in structuralIdsWithPasals). Co-authored-by: Claude <noreply@anthropic.com>
23fc3e5 to
fd3244d
Compare

Problem
Structured laws like UU Cipta Kerja (186 pasals, multiple BABs) showed empty sections after the first ~30 pasals. The page uses BAB-first rendering — each chapter filters
allPasalsbyparent_id. When only 30 pasals were loaded SSR, all BABs beyond the first few had no matching pasals and rendered empty.Root cause:
usePaginationwas set whenevertotalPasalCount >= 100, regardless of whether the law had a BAB structure. WhenusePagination = true, only the initial 30 pasals are fetched SSR and the rest are loaded client-side via infinite scroll — but client-side infinite scroll appends pasals to a flat list, it never fills in the per-BABallPasals.filter(p => p.parent_id === bab.id)logic.Fix
Two commits:
b9c067f— Skip client pagination when the law has structural nodes (BABs, aturan, lampiran); always fetch the full pasal set SSR. Flat laws (no BABs) with 100+ pasals still use infinite scroll.0939875— Broaden the check fromhasBABs(only tested bab/aturan/lampiran) tohasStructure = structure.length > 0, which covers all node types that trigger the tree-rendering path (including bagian and paragraf). Aligns the pagination guard directly with thebabNodes.length > 0rendering condition.How it works
structureis already fetched in the initial parallel query — zero additional DB round-trips for the check itself. For structured laws, the existing SSR "fetch remaining" branch now runs unconditionally, fetching all pasals in a single work_id-scoped indexed query.Impact
Co-authored-by: Claude noreply@anthropic.com