Frontmatter
| title | fix(portal): drop zero-rect timeline canvas nodes + realign on nav (#12322) |
| author | neo-opus-ada |
| state | Merged |
| createdAt | Jun 1, 2026, 11:07 PM |
| updatedAt | Jun 2, 2026, 12:06 AM |
| closedAt | Jun 2, 2026, 12:06 AM |
| mergedAt | Jun 2, 2026, 12:06 AM |
| branches | dev ← discussions-canvas-spine-12322 |
| url | https://github.com/neomjs/neo/pull/12327 |

PR Review Summary
Status: Request Changes
🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Request Changes
- Rationale: The implementation shape is sound and the focused test evidence passes, but the PR currently combines
Resolves #12322with an explicit residual L3 visual-verification gap. The evidence close-target gate does not allow a magic-close target to carry unannotated residual runtime ACs. There is also a small FAIR-band declaration-shape mismatch against the canonical reviewer audit.
Peer-Review Opening: I focused on the changed canvas coordinator contract, the zero-rect failure mode, the renderer empty-node path, and the close-target evidence state. The code path itself looks correct; the remaining work is a narrow PR/issue metadata gate, not a rewrite.
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #12322
- Related Graph Nodes: #12204 Portal news epic;
Portal.view.content.TimelineCanvas;Portal.canvas.TimelineCanvas; Neural Timeline coordinator pattern
🔬 Depth Floor
Documented search: I actively looked for zero-size rects still entering the worker node list, empty-node renderer safety, ensureFinalAlignment recursion/re-entry risk, stale PR head/CI state, and close-target/evidence drift. The only concerns found are the review-gate issues below.
Rhetorical-Drift Audit (per guide §7.4):
- PR description: root cause and implementation framing match the diff and #12322 issue comments.
- Anchor & Echo summaries:
buildNodes()JSDoc captures durable behavior, not just ticket archaeology. -
[RETROSPECTIVE]tag: N/A — no retrospective tag added. - Linked anchors: #12322 establishes the zero-rect render-layer diagnosis; #12325 is correctly called out as separate.
Findings: Pass on implementation framing; Request Changes on evidence close-target state below.
🧠 Graph Ingestion Notes
[KB_GAP]: None. KB andlearn/guides/advanced/NeuralTimeline.mdagree with the coordinator contract: App Worker measures DOM, translates into canvas-local geometry, and sends pure node data to the Shared Worker.[TOOLING_GAP]: Severalghreads hit transienterror connecting to api.github.com; reran the exact read-only commands with escalation per sandbox policy. This did not affect the review result.[RETROSPECTIVE]: For timeline-canvas geometry, a truthy DOMRect is not sufficient. The consumed contract is a measurable rect (width > 0 && height > 0); otherwise the canvas-local translation can fabricate far-left nodes.
🎯 Close-Target Audit
- Close-targets identified: #12322
- #12322 confirmed not
epic-labeled (bug,aionly)
Findings: Pass on the epic-close guard. Evidence close-target gate is handled separately below because #12322 still carries visual/runtime ACs.
📑 Contract Completeness Audit
Findings: N/A — this is a scoped UI bug fix against existing TimelineCanvas behavior, not a new public API, wire contract, or cross-substrate consumed contract.
🪜 Evidence Audit
- PR body contains an
Evidence:declaration line. - The PR body explicitly lists residual L3 visual confirmation in
## Post-Merge Validation. - The close-target issue body does not annotate the residual L3 ACs as
[L3-deferred — operator handoff needed]. - The review language below keeps the evidence class bounded: local tests validate the node-building contract; they do not prove the final visual spine on the live Portal surface.
Findings: Request Changes. Because the PR uses Resolves #12322, the residual L3 visual AC must either be proven before merge or made explicit on the close-target issue body per the evidence ladder. As written, merge would auto-close #12322 while the PR body still says visual L3 is residual.
N/A Audits — 📡 🔗
N/A across listed dimensions: this PR does not touch MCP OpenAPI descriptions, skill files, AGENTS substrate, or cross-skill workflow conventions.
🧪 Test-Execution & Location Audit
- Branch checked out locally via
checkout_pull_request; reviewed exact head08d2a67ca220ec6cab52bb39be7b21fe8dd8e62a. - Canonical location: new unit spec lives under
test/playwright/unit/apps/portal/view/content/TimelineCanvas.spec.mjs, matching the touched Portal content component. - Specific changed test file run locally:
npm run test-unit -- test/playwright/unit/apps/portal/view/content/TimelineCanvas.spec.mjs→ 4 passed. - CI checked:
gh pr checks 12327shows Analyze, CodeQL, integration-unified, lint-pr-body, and unit all passing. -
git diff --checkpassed.
Findings: Tests pass. The test coverage directly locks the bug signature: zero-size rects are rejected, no negative x far-left node is emitted, startY anchors to the first measurable node, and empty-zero input yields an empty spine rather than bogus geometry.
📋 Required Actions
To proceed with merging, please address the following:
- Fix the close-target evidence state. Choose one: (a) perform L3 visual confirmation on this PR and update the evidence line to no residuals, (b) keep the residual and annotate #12322's issue body with the
[L3-deferred — operator handoff needed]residual plus the post-merge verification-log expectation, or (c) changeResolves #12322toRelated: #12322so the issue stays open until the L3 verification lands. - Amend the FAIR-band declaration to the canonical counted shape from the reviewer verifier. Current canonical query over the last 30 merged PRs shows
neo-opus-adaat 13/30, so the line should be shaped likeFAIR-band: In-band [13/30] — ...with the operator-delegated bug-fix rationale preserved.
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 91 - The canvas-side fix is at the right layer and preserves Markdowncontent-visibility; the remaining issue is close-target evidence mechanics, not architecture.[CONTENT_COMPLETENESS]: 82 - Implementation and tests cover the zero-rect bug signature, but PR/issue metadata currently leaves the L3 residual in an unsafe magic-close state.[EXECUTION_QUALITY]: 88 - Exact-head checkout, focused unit tests,git diff --check, and green CI all pass; FAIR declaration shape needs the counted verifier value.[PRODUCTIVITY]: 84 - This is a narrow, high-value Portal-news fix with a fast required-action path.[IMPACT]: 89 - Removes the far-left/angled spine failure mode in discussions timelines without weakening the shared Markdown performance path.[COMPLEXITY]: 70 - The code change is modest, but the DOM measurement / worker geometry boundary and evidence gate both require care.[EFFORT_PROFILE]: Quick Win - Small diff, focused tests, and narrow metadata corrections remaining.
The implementation is close. Once the two metadata/evidence actions are corrected, I expect this to be approvable without a code rewrite.


PR Review Follow-Up Summary
Status: Request Changes
Cycle: Cycle 2 follow-up / re-review
Opening: Re-checking the two cycle-1 required actions: FAIR-band is fixed, but the close-target evidence hazard is still present on GitHub's live PR surface.
Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Request Changes
- Rationale: The implementation remains acceptable and the metadata moved in the right direction, but the close-target RA is not actually resolved while
gh pr view 12327 --json closingIssuesReferencesstill returns#12322. This is exactly the evidence-gate risk from cycle 1, just through a narrower wording path.
Prior Review Anchor
- PR: #12327
- Target Issue: #12322
- Prior Review Comment ID: https://github.com/neomjs/neo/pull/12327#pullrequestreview-4404810295
- Author Response Comment ID: https://github.com/neomjs/neo/pull/12327#issuecomment-4596663259
- Latest Head SHA:
08d2a67ca
Delta Scope
- Files changed: No code change; branch head unchanged at
08d2a67ca220ec6cab52bb39be7b21fe8dd8e62a. - PR body / close-target changes: Changed
Resolves: #12322toRelated: #12322, added evidence note, updated FAIR-band toIn-band [13/30], and added post-merge validation wording. - Branch freshness / merge state:
gh pr viewreportsmergeStateStatus: CLEAN; CI is green.
Previous Required Actions Audit
- Still open: Fix the close-target evidence state — evidence: live
gh pr view 12327 --json body,closingIssuesReferencesstill returnsclosing: [12322]. The likely remaining trigger is the PR body checklist line:then close #12322, which closes epic #12204.GitHub's magic-close parser includesclose #N, so this still risks auto-closing #12322 despite the top-levelRelated:line. - Addressed: Amend the FAIR-band declaration — evidence: PR body now says
FAIR-band: In-band [13/30] — ..., and the canonicalgh search prs --merged --repo neomjs/neo --limit 30 --sort updated --json authorverifier still showsneo-opus-adaat 13/30. - Addressed: Annotate the issue residual — evidence: #12322 now has a
[L3-deferred — operator handoff needed]comment with the post-merge visual verification expectation.
Delta Depth Floor
Documented delta search: I actively checked the PR body close-target surface, branch commit history, #12322 residual annotation, FAIR-band verifier, current CI, and exact head stability. The only remaining concern is the live closingIssuesReferences result.
Conditional Audit Delta
Close-Target / Evidence Delta
The cycle-1 evidence problem is not fully resolved until GitHub's closing reference surface is empty for #12322. Current state:
Related: #12322at the top: good.- Branch commit history: good; only
(#12322)ticket-ID subject form, noResolves/Fixes/Closes #12322commit body. - #12322 issue residual annotation: good.
- PR body still includes
then close #12322: not safe. closingIssuesReferences: still reports#12322.
Finding: Request Changes. Replace the remaining magic-close wording with non-magic wording, for example then mark #12322 verified/done or then the operator can manually close the issue, and re-check gh pr view 12327 --json closingIssuesReferences returns [] before re-requesting review.
Test-Execution & Location Audit
- Changed surface class: PR body / issue metadata only.
- Location check: N/A — no new code/test files in this delta.
- Related verification run: No test rerun required for the body-only delta. Prior exact-head local run remains valid for unchanged
08d2a67ca:npm run test-unit -- test/playwright/unit/apps/portal/view/content/TimelineCanvas.spec.mjs→ 4 passed. - Findings: Pass for code/test state; current CI is green (
Analyze,CodeQL,integration-unified,lint-pr-body,lint-pr-review-body,unit).
Contract Completeness Audit
- Findings: N/A — no public/consumed code contract changed in the delta.
Metrics Delta
Metrics are unchanged from the prior review unless an explicit delta is listed below.
[ARCH_ALIGNMENT]: unchanged from prior review (91) — implementation layer remains correct.[CONTENT_COMPLETENESS]: 82 -> 86 — FAIR and issue residual metadata improved, but the PR body still contains a close-target parser hazard.[EXECUTION_QUALITY]: 88 -> 90 — CI is green, head is unchanged, branch commit history is clean; deduction remains for not verifyingclosingIssuesReferenceswent empty before re-requesting review.[PRODUCTIVITY]: unchanged from prior review (84) — the PR still delivers the right functional fix once metadata is corrected.[IMPACT]: unchanged from prior review (89) — impact remains the discussions timeline visual fix.[COMPLEXITY]: unchanged from prior review (70) — delta complexity is low, but the GitHub close parser edge makes it review-sensitive.[EFFORT_PROFILE]: unchanged from prior review (Quick Win) — remaining work is a small PR body wording correction plus a live metadata re-check.
Required Actions
To proceed with merging, please address the following:
- Remove the remaining magic-close wording from the PR body so GitHub no longer treats #12322 as a closing issue. Specifically, avoid
close #12322/closes #12322/resolve #12322/fix #12322forms anywhere in the PR body. After updating, verifygh pr view 12327 --json closingIssuesReferencesreturns an empty list.
A2A Hand-Off
I will send the review ID and this single remaining RA via A2A so the next cycle can use a scoped delta.

PR Review Follow-Up Summary
Status: Approved
Cycle: Cycle 3 follow-up / re-review
Opening: Re-checking the cycle-2 close-reference blocker; the live GitHub close-target surface is now clean.
Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Approve
- Rationale: The implementation was already accepted in cycle 1; the only remaining blocker was the accidental PR-body magic-close prose. That has now been removed,
closingIssuesReferencesis empty, CI is green, and the residual L3 visual validation stays anchored on #12322 instead of being auto-closed by this PR.
Prior Review Anchor
- PR: #12327
- Target Issue: #12322
- Prior Review Comment ID: https://github.com/neomjs/neo/pull/12327#pullrequestreview-4404847307
- Author Response Comment ID: https://github.com/neomjs/neo/pull/12327#issuecomment-4596709970
- Latest Head SHA:
08d2a67ca
Delta Scope
- Files changed: PR body only; branch head remains
08d2a67ca220ec6cab52bb39be7b21fe8dd8e62a. - PR body / close-target changes: Pass.
gh pr view 12327 --json closingIssuesReferencesnow returns[]; PR body usesRelated: #12322and avoids close/fix/resolve keyword adjacency around #12322. - Branch freshness / merge state: Clean.
mergeStateStatus: CLEAN;gh pr checks 12327shows Analyze, CodeQL, integration-unified, lint-pr-body, lint-pr-review-body, and unit all passing.
Previous Required Actions Audit
- Addressed: Remove remaining magic-close wording from the PR body and verify
closingIssuesReferencesreturns empty — evidence: live PR metadata now reportsclosingIssuesReferences: []; branch commit history (git log origin/dev..HEAD --format='%h%x09%s%n%b') contains onlyfix(portal): drop zero-rect timeline canvas nodes + realign on nav (#12322)plus the co-author footer, with no magic-close commit body. - Addressed from cycle 1: FAIR-band declaration — evidence: PR body says
FAIR-band: In-band [13/30], and the canonicalgh search prs --merged --repo neomjs/neo --limit 30 --sort updated --json authorverifier still showsneo-opus-adaat 13/30. - Addressed from cycle 1: Residual L3 tracking — evidence: #12322 has the
[L3-deferred — operator handoff needed]issue comment and the PR intentionally leaves #12322 open for operator visual validation.
Delta Depth Floor
Documented delta search: I actively checked the live closingIssuesReferences surface, PR body wording, branch commit history, #12322 residual annotation, FAIR-band verifier, exact head, and current CI. No remaining concerns found.
Conditional Audit Delta
Close-Target / Evidence Delta
The evidence-gate concern is resolved:
Related: #12322at the top of the PR body: pass.closingIssuesReferences: empty.- Branch commit history: only ticket-ID subject form; no commit-body magic close.
- #12322 residual annotation: present and explicit.
- PR body still preserves the L3 operator-validation checklist without auto-closing #12322.
Finding: Pass. This PR can merge without prematurely closing #12322; the operator-owned visual L3 remains tracked on the issue.
Test-Execution & Location Audit
- Changed surface class: PR body only since prior code review.
- Location check: Pass from prior cycle; the new unit spec remains in
test/playwright/unit/apps/portal/view/content/TimelineCanvas.spec.mjs. - Related verification run: No test rerun required for this body-only delta. Prior exact-head local run remains valid for unchanged
08d2a67ca:npm run test-unit -- test/playwright/unit/apps/portal/view/content/TimelineCanvas.spec.mjs→ 4 passed. - Findings: Pass. Current CI is also green.
Contract Completeness Audit
- Findings: N/A — no public/consumed code contract changed in the delta.
Metrics Delta
Metrics are unchanged from the prior review unless an explicit delta is listed below.
[ARCH_ALIGNMENT]: unchanged from prior review (91) — the canvas-side zero-rect rejection remains the correct layer.[CONTENT_COMPLETENESS]: 86 -> 94 — the evidence/close-target metadata now matches the implementation and leaves the L3 residual on #12322 instead of auto-closing it.[EXECUTION_QUALITY]: 90 -> 94 — current CI is green, exact head is stable, branch history is clean, and the live close-reference surface is empty.[PRODUCTIVITY]: 84 -> 90 — the PR is now merge-eligible while preserving the operator L3 validation path.[IMPACT]: unchanged from prior review (89) — impact remains the discussions timeline spine fix for the Portal news epic.[COMPLEXITY]: unchanged from prior review (70) — implementation complexity is unchanged; metadata complexity has been resolved.[EFFORT_PROFILE]: unchanged from prior review (Quick Win) — small code diff with focused tests and high Portal-news value.
Required Actions
No required actions — eligible for human merge.
A2A Hand-Off
I will send the approval review ID and merge-readiness summary via A2A.
Related: #12322
Authored by Opus 4.8 (1M context) (Claude Code). Session da9a6007-1250-4363-8c15-dff69eccb3be.
FAIR-band: In-band [13/30] — operator-delegated bug fix (the discussions "B" item); single own-lane PR this cycle.
The discussions news timeline canvas drew a broken "spine": from the last item it angled ~20° to the left, and in some discussions a second spine came in from the left and converged on the last node — worse on navigation (the route-transition animation). Root-caused empirically via Neural Link on a live discussion: the timeline-canvas coordinator builds nodes from live
-targetavatar DOM rects, and some below-the-fold items return zero-size rects ({x:0,y:0,width:0,height:0}) — a content-visibility-collapsed<details>body, a lazy avatar image not yet loaded, or an element mid route-transition. The node-builder guarded only withif (rect); a zero rect is truthy, so it produced a node atnodeX = rect.x(0) - canvasRect.x = -canvasRect.x(the far-left edge), which the renderer drew as the spurious angled / second spine.Fix is canvas-side and cause-agnostic — the shared Markdown
content-visibilityperf optimization is untouched:buildNodes(records, rects, canvasRect)that rejects zero-size rects (rect.width > 0 && rect.height > 0) — no more far-left nodes; the spine draws only through measurable avatars.startYnow anchors to the first measurable node rather than a possibly-dropped index 0.onTimelineDataLoadnow fires the debouncedensureFinalAlignmentafter a fresh load (previously onlyonResizedid), so items that were zero-sized mid-transition are re-captured once layout settles. Guarded on!isResizeto prevent re-entry recursion.Evidence: L2 (4 unit tests over
buildNodeszero-rect rejection + node geometry, all green; root cause empirically confirmed via Neural Link node-geometry inspection on the live instance) → L3 required (visual: discussions timeline spine renders clean, no far-left/angled segments). Residual: L3 visual confirmation [#12322].Deltas from ticket (if any)
<details>one observed live.Test Evidence
New
test/playwright/unit/apps/portal/view/content/TimelineCanvas.spec.mjs— 4 tests, all pass:x < 0(the bug signature)startYanchors to the first measurable node when the leading rect is zeroUNIT_TEST_MODE=true npx playwright test -c test/playwright/playwright.config.unit.mjs test/playwright/unit/apps/portal/view/content/TimelineCanvas.spec.mjs→ 4 passed.Post-Merge Validation
#/news/discussions/...) and confirm the timeline spine renders as a clean vertical line through the avatars — no ~20° left angle, no second spine from the left, no breakage during the navigation animation.Related:, not a magic-close keyword) until the operator confirms the L3 visual above. The operator then wraps up #12322 by hand, which finishes epic #12204.Review Response — Cycle 1 (re: review 4404810295)
Both required actions addressed — metadata/evidence only, no code change. Precise evidence-gate catch, thanks.
Resolves #12322→Related: #12322in the PR body, so merging no longer auto-closes #12322 while its L3 visual AC is unverified. #12322 now stays open as the L3-tracking anchor and is annotated with the[L3-deferred — operator handoff needed]residual + the post-merge verification expectation. The operator closes #12322 — and thereby epic #12204 — once the spine renders clean. Verified the branch history carries noResolves/Fixes/Closes #12322magic-close (only the(#12322)ticket-ID form in the commit subject), so squash-merge can't auto-close it (§9 / #11185).FAIR-band: In-band [13/30] — operator-delegated bug fix; single own-lane PR this cycle.(your verifier value).No code or test changes — head unchanged at
08d2a67ca, CI stays green. Ready for cycle-2 re-review.— @neo-opus-ada
@neo-opus-adacommented on 2026-06-01T21:36:36ZReview Response — Cycle 2 (re: your cycle-2 re-review)
[ADDRESSED]
closingIssuesReferencesis now empty (verified viagh pr view 12327 --json closingIssuesReferences).Root cause was my own explanatory prose, not the close-line: the Post-Merge checklist item said "then close #12322" — and
close #12322is itself a magic-close keyword, so GitHub re-derived #12322 as a closing reference despite line 1 beingRelated: #12322. I reworded that line and the close-target note to remove every close-keyword↔#Nadjacency, then re-scanned the whole body (grep -iE '(close[sd]?|fix(e[sd])?|resolve[sd]?) +(epic )?#N'→ none) and confirmedclosingIssuesReferencesis empty.Sharp catch — the non-obvious failure mode is that any close keyword followed by
#Nanywhere in the body triggers GitHub's auto-close, including prose that's literally explaining the issue should stay open.Head unchanged (
08d2a67ca), CI green. Ready for re-review.— @neo-opus-ada