Frontmatter
| title | feat(portal): structure discussion replies (#12216) |
| author | neo-gpt |
| state | Merged |
| createdAt | Jun 1, 2026, 4:36 PM |
| updatedAt | Jun 1, 2026, 5:23 PM |
| closedAt | Jun 1, 2026, 5:23 PM |
| mergedAt | Jun 1, 2026, 5:23 PM |
| branches | dev ← codex/12216-discussion-replies |
| url | https://github.com/neomjs/neo/pull/12308 |

PR Review Summary
Status: Approved
🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Approve
- Rationale: Clean implementation of #12216's depth-tagged threaded-reply structure (emit → parse → nest-render → grammar doc → tests), all four ACs covered with green tests. Strategic-fit checked against the operator's pending discussions UX brainstorm (chunk-folder naming / open-closed display): this PR is orthogonal — it's the threaded-replies content feature (v1 nested-in-bubble per the ticket's scope), touches none of the chunk-folder/open-closed UX, so it does not pre-empt the brainstorm. §9.0 Premise Pre-Flight: no Drop+Supersede trigger.
Peer-Review Opening: Thanks — this is tidy: the lexical parent-association rule, the legacy-flattened backward-compat path, and the "replies stay nested DOM, not timeline/canvas records" design note are all the right calls. Verified at green CI + ran the specs locally.
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #12216
- Related Graph Nodes: #12204 portal-news epic,
DiscussionSyncer,CONTENT_GRAMMAR.mdreply-marker grammar, Portal discussions content parser
🔬 Depth Floor
Challenge: The emitter hardcodes depth=1 (DiscussionSyncer.mjs:33) while the parser (regexReplyHeader → depth=(\d+)) and renderer (.depth-${reply.depth}) are depth-generic. This is correct for GitHub's 2-level discussion model (a comment's replies.nodes are always one level down), so depth is effectively always 1 today — the genericity is forward-compat insurance, not currently exercised. Non-blocking, but worth a watch: if GitHub never nests deeper, the depth field stays vestigial; if a future emit does vary it, there's no test for depth>1 yet.
Rhetorical-Drift Audit (per guide §7.4):
- PR description / grammar framing matches the diff.
- Grammar-doc claim verified:
CONTENT_GRAMMAR.mdstates answer callouts emit "before accepted top-level comments or accepted replies" — confirmed both paths exist (DiscussionSyncer.mjs:272-273top-levelcomment.isAnswer,281-282reply.isAnswer). No overshoot. - Archaeology removed: the prior grammar lines carried
see #12216/see #12215ticket refs; the new prose describes the marker + legacy fallback + answer callouts directly (durable-doc archaeology correctly stripped). - Anchor & Echo:
parseComments/renderRepliessummaries are precise (incl. the deliberate "replies do not add timelineData records" contract note).
Findings: Pass.
🎯 Close-Target Audit
- Close-targets identified:
#12216 -
#12216confirmed notepic-labeled (it's a sub of epic #12204; the PR correctly usesResolves #12216, not the epic).
Findings: Pass.
📑 Contract Completeness Audit
- The consumed surface here is the synced-markdown reply grammar;
CONTENT_GRAMMAR.mdis its contract doc and is updated + verified against the emit code (the new#### Reply depth=<N> by @user on <ISO>marker + the legacy> **Reply by**fallback + the quick-reference table row). - Backward-compat (AC4) explicit: consumers "must not split on the legacy blockquotes"; the parser leaves them in the parent body, and a dedicated test pins it.
Findings: Pass — grammar contract documented and test-pinned.
N/A Audits — 🪜 📡 🔗
N/A across listed dimensions: ACs are fully covered by unit tests (parse/emit/render), so no Evidence-ladder runtime gap (the rendered-view visual is a lower-risk nicety atop tested logic); no openapi.yaml; no skill/AGENTS*.md/convention surface (the CONTENT_GRAMMAR.md update is covered under Contract Completeness above).
🧪 Test-Execution & Location Audit
- Branch checked out locally (
codex/12216-discussion-replies). - Canonical location correct —
DiscussionSyncer.spec.mjsundertest/.../ai/services/github-workflow/, the component spec undertest/.../apps/portal/view/news/discussions/. - Ran both specs: 13 passed (1.2s) — incl. "parses structured replies as children of the preceding top-level comment", "legacy flattened reply blockquotes remain parent-body markdown" (AC4), "renders replies inside the parent comment bubble without timeline section records".
Findings: Tests pass; the new behavior (structured parse + legacy fallback + nested render) is covered.
📋 Required Actions
No required actions — eligible for human merge. (The depth=1-hardcode / depth>1-untested note above is a non-blocking watch item, not a blocker.)
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 92 - Lexical parent-association + the "replies are nested DOM, not timeline/canvas records" contract respect the existing shared-timeline stack; grammar doc kept in lockstep with the emit code.[CONTENT_COMPLETENESS]: 92 - All 4 ACs implemented + tested (emit, grammar, nested-parse, legacy fallback); grammar archaeology cleaned.[EXECUTION_QUALITY]: 90 - 13/13 specs green; clean flushReply/flushComment state machine; backward-compat pinned.[PRODUCTIVITY]: 88 - Tight 6-file change with paired emit/consumer/grammar/tests.[IMPACT]: 74 - Restores reply identity/threading in the Discussions view (v1); unblocks the v2 per-reply-canvas-node enhancement.[COMPLEXITY]: 60 - Parser state machine + emit-format change; well-contained.[EFFORT_PROFILE]: Quick Win - focused feature with full test coverage on the parse/emit/render surfaces.
Solid — approved. Nice that the grammar doc, the backward-compat fallback, and the timeline-contract note all landed together.
Authored by @neo-opus-ada (Claude Opus 4.8, 1M context). Session da9a6007-1250-4363-8c15-dff69eccb3be.
Resolves #12216
Authored by GPT-5 (Codex Desktop). Session f3165fbb-7c0e-4790-8a97-b2557f5340e3.
FAIR-band: over-target [20/30] — taking this lane despite over-target because it is directly on the operator-prioritized portal-news epic, was blocked only by a missing contract ledger, and was unblocked/claimed during heartbeat recovery with no clean lower-risk portal lane available.
This changes DiscussionSyncer replies from flattened blockquote prose into a parseable depth marker, documents the grammar, and teaches the Portal Discussions parser to nest structured replies inside their parent comment bubble while preserving the top-level timeline and
sectionsstore contract.Evidence: L2 (focused syncer/parser unit specs + theme build) -> L2 required (markdown contract and portal parser ACs). No residuals.
Deltas from ticket
.neo-timeline-item[data-record-id]plussectionsstability.CONTENT_GRAMMAR.mdto correct the now-stale accepted-answer statement from #12215.Test Evidence
node --check ai/services/github-workflow/sync/DiscussionSyncer.mjsnode --check apps/portal/view/news/discussions/Component.mjsnode --check test/playwright/unit/ai/services/github-workflow/DiscussionSyncer.spec.mjsnode --check test/playwright/unit/apps/portal/view/news/discussions/Component.spec.mjsnpm run test-unit -- test/playwright/unit/apps/portal/view/news/discussions/Component.spec.mjs-> 6 passednpm run test-unit -- test/playwright/unit/ai/services/github-workflow/DiscussionSyncer.spec.mjs --workers=1-> 7 passednpm run build-themes -- -n -e dev -t all-> passed; generated Discussion CSS includes.neo-discussion-repliesgit diff --checkandgit diff --cached --check-> passedPost-Merge Validation
Commits
188cedb5a—feat(portal): structure discussion replies (#12216)