feat(web): show a fork preview dialog before forking a conversation - #1673
feat(web): show a fork preview dialog before forking a conversation#1673junmo-kim wants to merge 6 commits into
Conversation
Fork now opens a confirm dialog that renders the transcript boundary: kept turns above the fork point, the selected message starting the new session below it. Confirming executes the existing fork flow; cancel leaves the session untouched.
There was a problem hiding this comment.
Findings
- [Major] Fork still requires the old generic confirmation before the new preview opens - the added handler only runs after
MessageActionsconfirms, producing two dialogs. Evidence:web/src/components/SessionChat.tsx:584. Suggested fix: invoke the preview callback directly from the Fork action and remove the fork-specific genericConfirmDialog. - [Major] The preview reverses historical-fork semantics - the hub and native Codex fork exclude the selected boundary, but the dialog says the new session starts with it. Evidence:
web/src/components/AssistantChat/ForkPreviewDialog.tsx:51. Suggested fix: label the prefix as copied into the child and the selected message as excluded/the cutoff. - [Major] Pending queued rows are rendered as part of the fork preview even though the hub never copies
invokedAt == nullrows. Evidence:web/src/lib/forkPreview.ts:55. Suggested fix: skip blocks whoseinvokedAt === nulland add a queued-row test.
Summary
Review mode: initial
Three Major issues found in the confirmation flow and preview/backend semantic parity.
Testing
Not run (automation; PR-provided code was not executed). Coverage gap: no integration test enters the preview through MessageActions, and no preview test covers queued/uninvoked rows.
HAPI Bot
… semantics - Open the preview directly from the Fork action; drop MessageActions' fork ConfirmDialog so the preview is a single confirmation. - Correct boundary wording: the hub copies the prefix before the selected message into the child, so label kept turns as copied and the selected message as excluded. - Skip never-invoked (queued) user blocks in the preview, matching selectForkTranscriptPrefix's invokedAt filter.
There was a problem hiding this comment.
Findings
-
[Major] Failed fork requests close the preview before completion and surface no error —
setForkPreviewRequest(null)unmounts the dialog before the API call, while the click handler discards the returned promise. The added pending UI therefore never renders, and backend/network failures become an unhandled rejection with no retry context. Evidence:web/src/components/SessionChat.tsx:569.
Suggested fix:const executeForkConversation = useCallback(async (messageLocalId?: string) => { setHistoryActionPending(true) try { const result = await props.api.forkConversation(props.session.id, messageLocalId) await navigate({ to: '/sessions/$sessionId', params: { sessionId: result.sessionId } }) setForkPreviewRequest(null) } finally { setHistoryActionPending(false) } }, [navigate, props.api, props.session.id])
Make the dialog await
onConfirmand render caught errors, matchingConfirmDialog. -
[Major] Current-tail forks render a historical cutoff that does not exist — current fork actions pass no
messageLocalId, so the hub copies the complete transcript andboundaryTextis null. The dialog still renders “Fork point” and always says the child resumes before “this message,” which is false for this common path. Evidence:web/src/components/AssistantChat/ForkPreviewDialog.tsx:41.
Suggested fix:{previewKind === 'historical' ? <ForkBoundary text={boundaryText} /> : null} <p>{t(previewKind === 'historical' ? 'forkPreview.below' : 'forkPreview.currentTail')}</p>
Return an explicit
'current' | 'historical'kind frombuildForkPreviewrather than inferring it from nullable text. -
[Minor] Attachment-only messages disappear from the preview — fork actions are available for user messages without text, and the API accepts attachment-only messages, but the preview extractor reads only
block.text. A selected attachment-only boundary is shown with no quoted message, and copied attachment-only turns are omitted. Evidence:web/src/lib/forkPreview.ts:28.
Suggested fix:if (block.kind === 'user-text') { const attachments = block.attachments?.map(({ filename }) => filename).join(', ') text = [block.text, attachments].filter(Boolean).join(' ') }
Summary
Review mode: follow-up after new commits
The three findings from the prior bot pass are no longer present. The current head has two Major correctness/UX regressions and one Minor preview-parity gap.
Testing
Not run (automation; PR-provided code was not executed). GitHub integration check passed; the main test check was still in progress when reviewed. Missing coverage: a SessionChat-level failed-fork case, current-tail dialog semantics, and attachment-only preview input.
HAPI Bot
… historical forks - The dialog owns pending/error state: a failed fork request keeps it open and shows the error instead of dismissing with an unhandled rejection. - Only historical forks render the cutoff divider and excluded-message quote; current-tail forks show a dedicated note since nothing is excluded. - Attachment-only user messages appear in the preview via their filenames.
There was a problem hiding this comment.
Findings
- [Major] The empty-prefix state can misrepresent a paginated fork — the preview is built only from the currently loaded message window, but
keptTurns.length === 0is rendered as “The new session starts empty.” When the selected boundary is the oldest loaded message andhasMoreMessagesis true, the hub still copies all older invoked messages. Evidence:web/src/components/SessionChat.tsx:1362,web/src/components/AssistantChat/ForkPreviewDialog.tsx:54.
Suggested fix:// Thread through whether older copied history is outside this window. const prefixMayContainUnshownContent = props.hasMoreMessages {keptTurns.length === 0 ? ( <div>{t(prefixMayContainUnshownContent ? "forkPreview.noTextPreview" : "forkPreview.emptyPrefix")}</div> ) : null}
- [Minor] The new Playwright coverage is not run by PR CI —
.github/workflows/test.ymlinvokes onlyterminal-wrap-fidelity.spec.tsandcomposer-copy.spec.ts, so this added spec never executes in automation. Evidence:e2e/fork-preview.spec.ts:1.
Suggested fix:- run: bun run test:e2e -- terminal-wrap-fidelity.spec.ts composer-copy.spec.ts fork-preview.spec.ts
Summary
Review mode: follow-up after new commits
The previous bot findings are addressed. Two fresh-head issues remain: one preview-correctness bug for paginated history and one CI coverage gap.
Testing
Not run (automation): Bun is unavailable in this review environment. GitHub integration check passed; the main test check was still pending when reviewed.
HAPI Bot
When the fork boundary sits in older history that is not loaded yet (hasMoreMessages), the child inherits those rows from the hub even though the preview window shows no turns. Show a dedicated note instead of claiming the session starts empty, and include the new spec in the CI e2e workflow so it runs on every PR.
There was a problem hiding this comment.
Findings
No high-confidence issues found.
Summary
Review mode: follow-up after new commits
The previous HAPI Bot findings are addressed on the current head. The full PR diff was reviewed; no Blocker/Major/Minor/Nit findings met the confidence threshold. Residual risk: the pagination-specific copy wording and the complete SessionChat → preview dialog → fork API wiring are not directly exercised by the added tests.
Testing
Not run locally (automation/review policy). GitHub integration and test checks pass at the reviewed head.
HAPI Bot
The hardcoded text-white became invisible in dark themes where --app-link resolves to a light color. Use --app-bg for contrast, matching the QuickKeys active-tab convention.
There was a problem hiding this comment.
Findings
No high-confidence issues found.
Summary
Review mode: follow-up after new commits
The full current diff was reviewed. The commits since the prior HAPI Bot pass only change the fork-point badge and confirm-button foreground colors. Residual risk: the complete SessionChat → preview dialog → fork API wiring and pending/error behavior are not directly exercised; the added browser test mounts the dialog in an isolated fixture.
Testing
Not run locally (automation/security policy). Current-head CI typecheck, targeted Playwright (including fork-preview.spec.ts), and root tests passed. The separate CLI runner integration job failed in unchanged CLI tests; no CLI or integration source files are changed by this PR, so it is not attributed to this diff.
HAPI Bot
Summary
Forking a conversation currently gives no indication of what will happen:
which messages stay in the original session and where the new session
starts. This adds a confirmation dialog that renders the transcript
boundary before the fork executes.
forking immediately.
highlighted "Fork point" divider, and the selected message quoted as the
first message of the new session below it. The boundary semantics mirror
the hub's existing prefix selection (
selectForkTranscriptPrefix).Testing
web/src/lib/forkPreview.test.ts(boundary slicing,turn merging/truncation, current-fork semantics) — 5 cases.
e2e/fork-preview.spec.tsdrives the productionForkPreviewDialogvia the standalone fixture page (scratchlistpattern): rendering, boundary marker, cancel/confirm callbacks, zh-CN
localization. RED was verified before the implementation commit.
bun run testpass,
bun typecheckclean, Playwright 52 passed.