Frontmatter
| title | >- |
| author | neo-opus-ada |
| state | Merged |
| createdAt | Jun 1, 2026, 7:18 PM |
| updatedAt | Jun 1, 2026, 7:34 PM |
| closedAt | Jun 1, 2026, 7:34 PM |
| mergedAt | Jun 1, 2026, 7:34 PM |
| branches | dev ← news-avatar-size-12315 |
| url | https://github.com/neomjs/neo/pull/12316 |

PR Review Summary
Status: Approved
🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Approve
- Rationale: This is the correct narrow fix for #12315: tickets and discussions now reuse the already-shared Portal content avatar helpers instead of re-deriving raw avatar URLs. The remaining browser-network confirmation is a legitimate L3 post-merge/operator validation item, not a merge blocker, because the helper contract and all changed call sites are covered by local verification plus green CI.
Peer-Review Opening: Reviewed #12316 at head 9f50c4059a696b48b795a22098d325a72b2da79c. The shape is sound: it removes the full-res avatar fetch path in the two views that still bypassed getAvatarUrl() / getAvatarHtml(), while leaving profile links on repoUserUrl intact.
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #12315
- Related Graph Nodes: Epic #12204, Portal news timelines,
Portal.view.content.Component,getAvatarUrl(),getAvatarHtml(), tickets/discussions timeline rendering
🔬 Depth Floor
Challenge OR documented search (per guide §7.1):
- Challenge: The routing itself is diff-verified rather than render-unit-tested because the tickets/discussions render methods are container-coupled. I checked this as a non-blocking risk: the two changed views now call the same helpers at every previous raw-avatar site, the helper contract is unit-tested, and
rgconfirms the only remaining.pngconstruction is the bounded?size=40helper in the shared base. A future render harness for Portal content views would be useful, but this PR should not invent that harness.
Rhetorical-Drift Audit (per guide §7.4):
- PR description: framing matches the diff. The changed tickets/discussions sites route semantic timeline images through
me.getAvatarUrl(user)and HTML avatars throughme.getAvatarHtml(user). - Anchor & Echo summaries: the new test comment names the durable helper contract and affected timeline classes without ticket archaeology.
-
[RETROSPECTIVE]tag: N/A; no tag present. - Linked anchors: the PR correctly frames pulls as already using the helpers;
rgverified pulls still callsgetAvatarUrl()/getAvatarHtml().
Findings: Pass.
🧠 Graph Ingestion Notes
[KB_GAP]: The KB did not surface Portal-specific avatar helper context; reviewers need to ground this class from source until Portal app components are better indexed/documented.[TOOLING_GAP]: Sandboxghreads intermittently failed witherror connecting to api.github.com; narrow escalatedgh pr view/gh issue viewretries succeeded and were used only for read-side verification.[RETROSPECTIVE]: Timeline avatar rendering should treat the base Portal content helper as the single source for both network-bound avatar URLs and bot glyph fallback. Semantic timeline image data and inline HTML avatars must stay on the same helper path to avoid visible/network drift between canvas/list surfaces.
🎯 Close-Target Audit
For every issue named as close-target, verify it does NOT carry the epic label:
- Close-targets identified:
#12315 -
#12315verified live asbug,ai,performance; notepic. - PR body close-target syntax is newline-isolated:
Resolves #12315. - Branch commit history checked with
git log origin/dev..HEAD; no stale magic-close body target beyond the PR subject's non-magic(#12315)ticket suffix.
Findings: Pass.
📑 Contract Completeness Audit
- Originating ticket Contract Ledger matrix: N/A — this is an app-level bug/performance fix, not a public API, wire-format, or consumed configuration contract change.
- Implemented PR diff matches ticket ACs: AC1 routes avatar image sources through helpers; AC2 is improved by
getAvatarHtml()bot fallback; AC3 adds a focused helper unit test for?size=40.
Findings: Pass / N/A for ledger-specific contract surface.
🪜 Evidence Audit
- PR body contains an
Evidence:declaration line. - Achieved evidence is L2: helper unit test + diff/grep verification of routing.
- L3 browser-network confirmation is listed in
## Post-Merge Validationfor?size=40network requests and bot glyph rendering. - Two-ceiling distinction is honest: browser DevTools/network confirmation is the remaining live-instance step, while the code-level helper/routing evidence is complete.
- Evidence-class collapse check: review language does not promote local grep/unit evidence to L3.
Findings: Pass.
N/A Audits — 📡 🔗
N/A across listed dimensions: this PR does not touch MCP OpenAPI tool descriptions, skill/workflow substrate, startup files, architectural primitives, wire formats, or new public APIs.
🧪 Test-Execution & Location Audit
- Branch checked out locally via
checkout_pull_request; head verified as9f50c4059a696b48b795a22098d325a72b2da79c. - Canonical Location: new unit test is under
test/playwright/unit/apps/portal/view/content/Component.spec.mjs, matching the source mirror for an app component helper. - Related syntax checks passed:
node --check apps/portal/view/news/tickets/Component.mjsnode --check apps/portal/view/news/discussions/Component.mjsnode --check test/playwright/unit/apps/portal/view/content/Component.spec.mjs
- Related focused unit run passed:
npm run test-unit -- test/playwright/unit/apps/portal/view/content/Component.spec.mjs→ 2 passed. - Related grep checks passed: no raw
repoUserUrl...pngavatar construction remains in tickets/discussions; only the shared bounded helper builds.png?size=40. - CI current-head check passed: lint-pr-body, CodeQL/Analyze, unit, and integration-unified all successful on head
9f50c4059a696b48b795a22098d325a72b2da79c.
Findings: Tests and location pass.
📋 Required Actions
No required actions — eligible for human merge.
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 96 - 4 points deducted only because the per-view render call sites remain string-template-heavy and not independently render-tested; the helper reuse itself is the right Neo-local consolidation path.[CONTENT_COMPLETENESS]: 96 - 4 points deducted because the PR body could more explicitly name the exact tickets/discussions line classes changed, but it otherwise has complete FAIR-band, root-cause, evidence, deltas, and post-merge validation sections.[EXECUTION_QUALITY]: 94 - 6 points deducted for relying on diff/grep verification for the container-coupled render methods rather than a render-level test; syntax checks, focused unit test, grep verification, refreshed diff scope, and full CI all pass.[PRODUCTIVITY]: 100 - I actively considered scope drift, missing AC coverage, stale close-target risk, and raw-avatar remnants; none remain. The PR resolves the reported full-res avatar path and adds bot fallback for tickets/discussions.[IMPACT]: 55 - Medium: visible Portal news polish plus meaningful network reduction on avatar-heavy timelines, localized to app rendering.[COMPLEXITY]: 30 - Low: eight call-site rewrites plus one focused helper spec, with the main reasoning cost in separating avatar image URLs from profile links.[EFFORT_PROFILE]: Quick Win - Small localized diff, strong UX/network payoff, and clear recurrence coverage through the shared helper spec.
Approved — clean Portal-news fix, bounded avatar requests are now on the shared helper path, and the residual browser-network confirmation is properly documented for post-merge validation.
Resolves #12315
Authored by Opus 4.8 (Claude Code). Session da9a6007-1250-4363-8c15-dff69eccb3be.
FAIR-band: in-band [10/30] — operator-reported portal-news fix.
Evidence: L2 — the shared avatar helpers are unit-tested (
getAvatarUrl→?size=40,getAvatarHtmlbot-glyph fallback), and the routing fix is diff-verified (all 8 raw-.pngsites now call the helpers; grep confirms zero raw avatar URLs remain). L3 (the browser issues?size=40avatar requests + bot actors render the glyph) is confirmable on the live instance — see Post-Merge Validation.Operator-reported: timeline avatars in the news views fetch the full-resolution image instead of a bounded thumbnail.
Root cause
The base
Portal.view.content.ComponentshipsgetAvatarUrl(user)(…/<user>.png?size=40) andgetAvatarHtml(user)(sized lazy<img>, or a no-network Font Awesome glyph for bot/app actors). The pulls view already uses them — but the tickets and discussions views emitted raw${repoUserUrl}${user}.png, bypassing both. So each 40px timeline avatar downloaded the ~40KB original, and bot actors (whose<login>.png404s) rendered a broken image.The fix
Routed all 8 avatar sites (4 tickets, 4 discussions) through the helpers, matching the pulls view:
image:configs →me.getAvatarUrl(user)(bounded?size=40URL)<img>HTML strings →${me.getAvatarHtml(user)}(sized lazy<img>, or the bot/app glyph)Deltas from ticket
.png→ 404 for bots); routing throughgetAvatarHtmladds the no-network glyph fallback.Portal.view.content.Component(the canonical home); the per-view render methods are app-container-coupled, so the routing itself is diff-verified rather than render-unit-tested.Test Evidence
node --checkclean: ticketsComponent.mjs, discussionsComponent.mjs, the new spec.UNIT_TEST_MODE=true playwright test … apps/portal/view/content/Component.spec.mjs→ 2 passed (getAvatarUrl?size=40;getAvatarHtmlnormal→sized-lazy-img / bot+[bot]→glyph).grepconfirms zerorepoUserUrl…\.pngavatar sites remain in tickets/discussions;repoUserUrlis still used for the actor profile<a href>links (not dead).Post-Merge Validation
#/news/tickets/<n>and#/news/discussions/<n>; DevTools Network shows avatar requests carry?size=40(not the full-res original).dependabot,github-actions) renders the Font Awesome GitHub glyph, not a broken image.Out of Scope