Authored by Opus 4.8 (Claude Code). Session 94a91ebc.
Resolves #12206
Foundation step 2 of epic #12204. Promotes the App-worker canvas glue out of the tickets view into a shared portal-content home so the upcoming PR + Discussion views reuse it via config.
- Relocates the bridge
Portal.view.news.tickets.TimelineCanvas → Portal.view.content.TimelineCanvas and the wrapper ...tickets.CanvasWrapper → Portal.view.content.CanvasWrapper (into apps/portal/view/content/).
- Removes CanvasWrapper's only per-type coupling — the hardcoded
import Component from './Component.mjs' default. contentComponent now defaults to null; consumers supply it through the existing config seam.
- The tickets
MainContainer now imports the shared CanvasWrapper and passes its own Component as contentComponent via pageContainerConfig.contentConfig.
- Generalizes the wrapper cls
neo-ticket-timeline-wrapper → neo-timeline-wrapper (per-type prefixes stay).
Per the operator's call, the shared home is portal-scoped (Portal.view.content.*), not framework — #12208 still adds the Neo.app.content.Component base these extend, keeping the framework-API commitment to one deliberate step.
FAIR-band: over-target — sole active implementer on an operator-directed lane (epic #12204).
Evidence: L1 — grep for the old view.news.tickets.{TimelineCanvas,CanvasWrapper} ns + tickets/CanvasWrapper import == 0; node --check passes on the bridge, wrapper, and MainContainer; git records both files as renames (content preserved). The sections store key and .neo-timeline-item[data-record-id] selector are untouched (canvas-bridge contract, per AC). Residual: L2 — Tickets tab renders + animates unchanged (post-merge/deploy).
Deltas from ticket (if any)
- Shared home is
apps/portal/view/content/ (Portal.view.content.*), per the operator's portal-shared decision over the framework (src/app/content/) option.
Test Evidence
grep -rn "view.news.tickets.TimelineCanvas|...CanvasWrapper|tickets/CanvasWrapper" apps/portal → 0 matches.
node --check on view/content/TimelineCanvas.mjs, view/content/CanvasWrapper.mjs, view/news/tickets/MainContainer.mjs → pass.
sections store key + .neo-timeline-item[data-record-id] selector present and unchanged.
Post-Merge Validation
🤖 Generated with Claude Code
Authored by Opus 4.8 (Claude Code). Session 94a91ebc.
Resolves #12206
Foundation step 2 of epic #12204. Promotes the App-worker canvas glue out of the tickets view into a shared portal-content home so the upcoming PR + Discussion views reuse it via config.
Portal.view.news.tickets.TimelineCanvas→Portal.view.content.TimelineCanvasand the wrapper...tickets.CanvasWrapper→Portal.view.content.CanvasWrapper(intoapps/portal/view/content/).import Component from './Component.mjs'default.contentComponentnow defaults tonull; consumers supply it through the existing config seam.MainContainernow imports the sharedCanvasWrapperand passes its ownComponentascontentComponentviapageContainerConfig.contentConfig.neo-ticket-timeline-wrapper→neo-timeline-wrapper(per-type prefixes stay).Per the operator's call, the shared home is portal-scoped (
Portal.view.content.*), not framework — #12208 still adds theNeo.app.content.Componentbase these extend, keeping the framework-API commitment to one deliberate step.FAIR-band: over-target — sole active implementer on an operator-directed lane (epic #12204).
Evidence: L1 —
grepfor the oldview.news.tickets.{TimelineCanvas,CanvasWrapper}ns +tickets/CanvasWrapperimport == 0;node --checkpasses on the bridge, wrapper, and MainContainer; git records both files as renames (content preserved). Thesectionsstore key and.neo-timeline-item[data-record-id]selector are untouched (canvas-bridge contract, per AC). Residual: L2 — Tickets tab renders + animates unchanged (post-merge/deploy).Deltas from ticket (if any)
apps/portal/view/content/(Portal.view.content.*), per the operator's portal-shared decision over the framework (src/app/content/) option.Test Evidence
grep -rn "view.news.tickets.TimelineCanvas|...CanvasWrapper|tickets/CanvasWrapper" apps/portal→ 0 matches.node --checkonview/content/TimelineCanvas.mjs,view/content/CanvasWrapper.mjs,view/news/tickets/MainContainer.mjs→ pass.sectionsstore key +.neo-timeline-item[data-record-id]selector present and unchanged.Post-Merge Validation
🤖 Generated with Claude Code