Context
Sibling to #12304 (GPT's tickets-store switch) on the shared #12219 lazy-aware-nav track. Operator direction 2026-06-01: "gpt on tickets and you on pulls". After #12301 merged (release-grouped PR tree + chunked surface emitted), the operator pulled dev + theme-built and found the pulls view does not yet run in lazy mode:
apps/portal/store/Pulls.mjs still loads the flat ../../apps/portal/resources/data/pulls.json (all leaves), so the Latest group shows PR leaves directly with no chunk-folder nodes in the tree nav.
apps/portal/resources/data/pulls/index.json, manifest.json, and per-chunk leaf files already exist (emitted by #12301's buildScripts/docs/index/pulls.mjs).
apps/portal/view/news/pulls/MainContainer.mjs sets lazyChildLoad: true, but that is inert while the store consumes the flat all-leaves tree.
- Separately: the PR-view timeline renders with no content-box borders —
border: 1px solid var(--gh-timeline-border) resolves to an undefined variable because the pulls theme scss does not define the --gh-* set (tickets + discussions theme scss define them at :root .neo-theme-neo-light).
The Problem
The Portal news perf track expects pulls to load only the root/group skeleton first, then fetch chunk leaves on folder expansion. The chunked data exists, but the consumer still points at the legacy monolith — so first paint pays the full PR-tree payload, the chunk folders are absent from the nav, and store.get(itemId) succeeds only against eagerly-loaded leaves (deep-links cannot honestly test lazy-aware resolution). The theme-var gap is a per-component lazy-CSS-load issue on the theme side (the structural rules ship via the @use added in #12301, but the color variables do not).
The Architectural Reality
apps/portal/store/Pulls.mjs — url points at the flat tree, not the chunked pulls/index.json.
apps/portal/view/news/pulls/MainContainerController.mjs — onRouteItem/getDefaultRouteId call store.get(itemId) directly (the flat shape). The lazy-aware shape (already implemented in discussions/MainContainerController.mjs and being adopted for tickets in #12304/#12219) pre-loads unloaded chunk folders via childrenUrl / isChildrenLoaded before store.get.
resources/scss/theme-neo-{light,dark}/apps/portal/news/pulls/Component.scss — define pulls-specific badge colors only; do not define / @use the shared --gh-* timeline variables.
The Fix
- Store —
Pulls.mjs url → ../../apps/portal/resources/data/pulls/index.json.
- Controller —
pulls/MainContainerController.mjs adopts the lazy-aware getDefaultRouteId (folder-aware) + onRouteItem (pre-load the relevant chunk folder before store.get(itemId)), the same mechanism discussions/MainContainerController.mjs uses and #12304 adopts for tickets.
- Theme —
pulls/Component.scss (theme-neo-light + theme-neo-dark) @use "../tickets/Component" so the --gh-* variables resolve (symmetric with the structural @use shipped in #12301).
Acceptance Criteria
Out of Scope
- Range-based chunk-folder display names (e.g. "1 – 100" / "101 – 134" instead of
chunk-2) and open/closed state display in the tree — these are the cross-view UX brainstorm topic the operator raised for discussions; they apply to all chunked views and are tracked separately, not here.
- Tickets adoption (#12304) and the discussions view (separate brainstorm).
- The generator itself (already emits the chunked surface, shipped in #12301).
Decision Record impact
none — aligned-with the chunked-index emitter work (#12204 epic); no ADR amended/superseded.
Related
- Blocks #12219 (lazy-aware nav); sibling to #12304 (tickets store switch).
- Parent epic #12204 (Portal news — chunked lazy-load tree).
- Origin: #12300 / PR #12301 (release-grouped + chunked-surface emitter merged 2026-06-01).
Origin Session ID: da9a6007-1250-4363-8c15-dff69eccb3be
Retrieval Hint: "portal pulls chunked lazy store controller theme border var" / mirror discussions MainContainerController lazy onRouteItem
Context
Sibling to #12304 (GPT's tickets-store switch) on the shared #12219 lazy-aware-nav track. Operator direction 2026-06-01: "gpt on tickets and you on pulls". After #12301 merged (release-grouped PR tree + chunked surface emitted), the operator pulled
dev+ theme-built and found the pulls view does not yet run in lazy mode:apps/portal/store/Pulls.mjsstill loads the flat../../apps/portal/resources/data/pulls.json(all leaves), so theLatestgroup shows PR leaves directly with no chunk-folder nodes in the tree nav.apps/portal/resources/data/pulls/index.json,manifest.json, and per-chunk leaf files already exist (emitted by #12301'sbuildScripts/docs/index/pulls.mjs).apps/portal/view/news/pulls/MainContainer.mjssetslazyChildLoad: true, but that is inert while the store consumes the flat all-leaves tree.border: 1px solid var(--gh-timeline-border)resolves to an undefined variable because the pulls theme scss does not define the--gh-*set (tickets + discussions theme scss define them at:root .neo-theme-neo-light).The Problem
The Portal news perf track expects pulls to load only the root/group skeleton first, then fetch chunk leaves on folder expansion. The chunked data exists, but the consumer still points at the legacy monolith — so first paint pays the full PR-tree payload, the chunk folders are absent from the nav, and
store.get(itemId)succeeds only against eagerly-loaded leaves (deep-links cannot honestly test lazy-aware resolution). The theme-var gap is a per-component lazy-CSS-load issue on the theme side (the structural rules ship via the@useadded in #12301, but the color variables do not).The Architectural Reality
apps/portal/store/Pulls.mjs—urlpoints at the flat tree, not the chunkedpulls/index.json.apps/portal/view/news/pulls/MainContainerController.mjs—onRouteItem/getDefaultRouteIdcallstore.get(itemId)directly (the flat shape). The lazy-aware shape (already implemented indiscussions/MainContainerController.mjsand being adopted for tickets in #12304/#12219) pre-loads unloaded chunk folders viachildrenUrl/isChildrenLoadedbeforestore.get.resources/scss/theme-neo-{light,dark}/apps/portal/news/pulls/Component.scss— define pulls-specific badge colors only; do not define /@usethe shared--gh-*timeline variables.The Fix
Pulls.mjsurl→../../apps/portal/resources/data/pulls/index.json.pulls/MainContainerController.mjsadopts the lazy-awaregetDefaultRouteId(folder-aware) +onRouteItem(pre-load the relevant chunk folder beforestore.get(itemId)), the same mechanismdiscussions/MainContainerController.mjsuses and #12304 adopts for tickets.pulls/Component.scss(theme-neo-light + theme-neo-dark)@use "../tickets/Component"so the--gh-*variables resolve (symmetric with the structural@useshipped in #12301).Acceptance Criteria
Pullsstore loadspulls/index.json; theLatestgroup + release-version groups show chunk-folder nodes; PR leaves lazy-load on folder expansion (first paint loads only the skeleton).#/news/pulls/<number>resolves under lazy load (controller pre-loads the containing chunk before selecting/scrolling).#/news/pullsroute resolves the first PR under lazy load.--gh-*vars defined in the pulls theme CSS); build-output verifiable + L3 visual.Out of Scope
chunk-2) and open/closed state display in the tree — these are the cross-view UX brainstorm topic the operator raised for discussions; they apply to all chunked views and are tracked separately, not here.Decision Record impact
none— aligned-with the chunked-index emitter work (#12204 epic); no ADR amended/superseded.Related
Origin Session ID: da9a6007-1250-4363-8c15-dff69eccb3be Retrieval Hint: "portal pulls chunked lazy store controller theme border var" / mirror discussions MainContainerController lazy onRouteItem