LearnNewsExamplesServices
Frontmatter
titlerefactor(portal): extract Portal.view.content.Component timeline base (#12208)
authorneo-opus-ada
stateMerged
createdAtMay 30, 2026, 11:49 PM
updatedAtMay 31, 2026, 12:29 AM
closedAtMay 31, 2026, 12:29 AM
mergedAtMay 31, 2026, 12:29 AM
branchesdevfeat/portal-content-component-base-12208
urlhttps://github.com/neomjs/neo/pull/12233
Merged
neo-opus-ada
neo-opus-ada commented on May 30, 2026, 11:49 PM

Authored by Opus 4.8 (Claude Code). Session 94a91ebc.

Resolves #12208

Foundation step 4 of epic #12204. Extracts the parser-agnostic timeline plumbing into a shared Portal.view.content.Component base so the upcoming PR + Discussion views subclass it and supply only their own parser.

Neo.app.content.Component already exists but runs in headline/sidenav mode (observes .neo-h2/.neo-h3, onHeadline, updateSectionsStore: true). Rather than overload it with timeline concerns, this adds an intermediate timeline-mode base in the portal-shared Portal.view.content.* home (per the #12206 decision and the ticket's "TimelineContent base" option):

  • New apps/portal/view/content/Component.mjs (Portal.view.content.Component extends Neo.app.content.Component) holds the content-agnostic plumbing lifted verbatim from the tickets Component: formatTimestamp, the timeline-mode doFetchContent (observes .neo-timeline-item[data-record-id]), getContentPath, onResize → toggleSummary, the contentComponentId construct hook, and the updateSectionsStore: false + domListeners defaults.
  • Portal.view.news.tickets.Component now extends it and keeps the Issue specialization: the modifyMarkdown pipeline, the renderTimeline line-walker, the event-type/label/commit machinery, and the frontmatter/badge helpers.

This satisfies the AC: a new content type subclasses the base and supplies only its modifyMarkdown/renderTimeline parser. Pure hierarchy refactor — no logic change, no HTML-template change.

Theming (honored): #12206 moved CanvasWrapper.mjs into Portal.view.content.* but left its SCSS stranded in the news/tickets namespace. The theme build (buildScripts/build/themes.mjs) compiles each .scss to a CSS file mirroring its tree path and keys it into theme-map.json by namespace, so this relocates resources/scss/src/apps/portal/news/tickets/CanvasWrapper.scss…/content/CanvasWrapper.scss to match. The .portal-canvas-wrapper selector is unchanged → identical compiled CSS.

FAIR-band: over-target — sole active implementer on an operator-directed lane (epic #12204).

Evidence: L1 — node --check passes on the new base, the refactored tickets Component, and MainContainer; the lifted members (construct, formatTimestamp, doFetchContent, getContentPath, onResize, #dateTimeFormat*) are absent from tickets (grep == 0) and present once in the base; tickets retains its parser (renderTimeline, modifyMarkdown, 3× me.formatTimestamp inherited calls); git records the SCSS as a rename (content preserved). Residual: L2 — the Tickets tab renders + animates unchanged (behavior rests on Neo config/method inheritance; visual confirmation post-merge/deploy).

Deltas from ticket (if any)

  • Timeline base placed at Portal.view.content.Component (portal-shared), not in Neo.app.content directly — the existing framework Component is headline-mode; overloading it would mix two content modes. Consistent with the #12206 portal-shared decision.
  • Template extraction deferred to #12213: the ticket lists lifting the body/comment/event HTML templates into the base. With the Issue view as the only consumer today that is abstraction-ahead-of-the-shape, and the HTML output has no unit test (can't be self-verified). The true shared shape emerges when the PR view (#12213) lands — extracted then under visual verification (rule-of-three).
  • #12209 (per-type cross-link URLs) batched with the views (#12211/#12213), where the cross-link behavior is actually exercised.
  • Bundles the #12206 SCSS-namespace fix (cohesive — same content/ relocation).
  • Converted four pre-existing {key: key} properties in renderTimeline (color, icon, id ×2) to ES2015 shorthand — the check-shorthand pre-commit hook scans the whole staged file, and this is the first change to stage tickets/Component.mjs since they were introduced.

Test Evidence

  • node --check → base, tickets Component, MainContainer all pass.
  • Lifted members: 0 occurrences in tickets/Component.mjs, 1 each in content/Component.mjs.
  • Issue parser retained in tickets: renderTimeline ×3, modifyMarkdown ×2, me.formatTimestamp ×3 (inherited).
  • SCSS move recorded as a git rename; selector unchanged.

Post-Merge Validation

  • Tickets tab renders + animates unchanged (timeline items, avatars/badges, canvas alignment).
  • Theme dry-run build (npm run build-themes -- -n -e dev -t all) compiles the relocated CanvasWrapper.scss.