Context
src/cli/ui/log-frame.tsx walks each DisplayEvent and either produces a frame atom (row-precise scroll, deterministic layout) or falls back to a legacy ink atom (snap-to-boundary scroll, height estimate). The remaining ink fallbacks are listed in the comment around eventToAtom:
// Fall back to legacy Ink rendering for roles we haven't migrated
// (currently: streaming assistant, plan-replay, plan-resumed; …)
We want to migrate them one by one. This issue is about plan-resumed.
What plan-resumed renders
When a session is resumed and a previously-saved plan is reattached. Existing renderer lives in src/cli/ui/EventLog.tsx around line 444. The event payload is on event.resumedPlan ({ steps, ... }).
Task
- Add a
planResumedFrame(event, width): Frame builder in log-frame.tsx, near the existing planFrame / ctxBreakdownFrame helpers.
- Wire it into the dispatch in
eventToAtom so event.role === 'plan-resumed' returns { kind: 'frame', ... } instead of falling through to ink.
- Visually match the legacy
<EventRow event={event} /> output — header pill + indented step list + any meta lines. Use the existing primitives (borderLeft, vstack, pad, text, rowFrame).
- No tests required, but a manual check via
npm run dev + a session that has a resumed plan should look identical row for row.
Why it's a good first PR
- Self-contained: one new function + one branch in the dispatch.
- The
planFrame next door is the closest pattern to copy.
- No behavior change for any other role.
Tag me on the PR if you get stuck on width budgets or cell layout.
Context
src/cli/ui/log-frame.tsxwalks eachDisplayEventand either produces aframeatom (row-precise scroll, deterministic layout) or falls back to a legacyinkatom (snap-to-boundary scroll, height estimate). The remaining ink fallbacks are listed in the comment aroundeventToAtom:We want to migrate them one by one. This issue is about
plan-resumed.What
plan-resumedrendersWhen a session is resumed and a previously-saved plan is reattached. Existing renderer lives in
src/cli/ui/EventLog.tsxaround line 444. The event payload is onevent.resumedPlan({ steps, ... }).Task
planResumedFrame(event, width): Framebuilder inlog-frame.tsx, near the existingplanFrame/ctxBreakdownFramehelpers.eventToAtomsoevent.role === 'plan-resumed'returns{ kind: 'frame', ... }instead of falling through toink.<EventRow event={event} />output — header pill + indented step list + any meta lines. Use the existing primitives (borderLeft,vstack,pad,text,rowFrame).npm run dev+ a session that has a resumed plan should look identical row for row.Why it's a good first PR
planFramenext door is the closest pattern to copy.Tag me on the PR if you get stuck on width budgets or cell layout.