Single path for text, diff, and media
Introduce one public UI component API that renders plain text files or diffs from the same entry point, so selection, comments, search, theming, and media behavior are maintained once.
File component in packages/ui/src/components/file.tsx that chooses plain or diff rendering from props.packages/ui/src/components/code.tsx and packages/ui/src/components/diff.tsx.packages/ui/src/components/session-review.tsx and packages/app/src/pages/session/file-tabs.tsx.@pierre/diffs behavior or fork its internals.Code/Diff APIs in the first pass.The current split duplicates runtime logic and makes feature parity drift likely.
code.tsx and diff.tsx, including instance creation, cleanup, onRendered readiness, and shadow root lookup.code.tsx, diff.tsx, and diff-ssr.tsx through similar applyScheme and MutationObserver code.code.tsx and diff.tsx, including drag state, shadow selection reads, and line-number bridge integration.code.tsx, diff.tsx, and diff-ssr.tsx.markCommentedFileLines and markCommentedDiffLines, with similar timing and effect wiring.fixSelection) exists twice in diff.tsx and diff-ssr.tsx.code.tsx, so diff lacks find and the feature cannot be maintained in one place.context/code.tsx, context/diff.tsx), which forces consumers to choose paths early.session-review.tsx and file-tabs.tsx.Use one public component with a discriminated prop shape and split shared behavior into small runtime modules.
packages/ui/src/components/file.tsx as the primary client entry point.File component that accepts a discriminated union with two primary modes.mode prop ("text" or "diff") to avoid ambiguous prop inference and keep type errors clear.annotationsselectedLinescommentedLinesonLineSelectedonLineSelectionEndonLineNumberSelectionEndonRenderedclassclassListmode: "text"file (FileContents)@pierre/diffs FileOptionsmode: "diff"beforeafterdiffStyleFileDiffOptionspreloadedDiff only for SSR-aware entry or hydration adaptermedia config for "auto" | "off" behaviorreadFile) for session review usepackages/ui/src/components/file.tsx
Public unified component and mode routing.packages/ui/src/components/file-ssr.tsx
Unified SSR entry for preloaded diff hydration.packages/ui/src/components/file-search.tsx
Shared find bar UI and host registration.packages/ui/src/components/file-media.tsx
Shared image/audio/svg/binary rendering shell.packages/ui/src/pierre/file-runtime.ts
Common render lifecycle, instance setup, cleanup, scheme sync, and readiness notification.packages/ui/src/pierre/file-selection.ts
Shared selection/drag/line-number bridge controller with mode adapters.packages/ui/src/pierre/diff-selection.ts
Diff-specific fixSelection and row/side normalization reused by client and SSR.packages/ui/src/pierre/file-find.ts
Shared find engine (scan, highlight API, overlay fallback, match navigation).packages/ui/src/pierre/media.ts
MIME normalization, data URL helpers, and media type detection.packages/ui/src/components/code.tsx as a thin compatibility wrapper over unified File in text mode.packages/ui/src/components/diff.tsx as a thin compatibility wrapper over unified File in diff mode.packages/ui/src/components/diff-ssr.tsx as a thin compatibility wrapper over unified SSR entry.Ship this in small phases so each step is reviewable and reversible.
code.tsx and diff.tsx into shared runtime helpers.fixSelection and helpers) out of both diff.tsx and diff-ssr.tsx into packages/ui/src/pierre/diff-selection.ts.packages/ui/src/pierre/file-selection.ts with mode callbacks for line parsing and normalization.code.tsx, diff.tsx, and diff-ssr.tsx behavior unchanged from the outside.code.tsx, diff.tsx, and diff-ssr.tsx are smaller and call shared helpers.packages/ui/src/components/file.tsx and wire it to shared runtime pieces.@pierre/diffs File or VirtualizedFile and diff mode to FileDiff or VirtualizedFileDiff.code.tsx and diff.tsx can be rewritten as thin adapters without behavior changes.Code and Diff exports.packages/ui/src/context/file.tsx with FileComponentProvider and useFileComponent.packages/ui/src/context/index.ts to export the new context.context/code.tsx and context/diff.tsx as compatibility shims that adapt to useFileComponent.packages/app/src/app.tsx and packages/enterprise/src/routes/share/[shareID].tsx to provide the unified component once wrappers are stable.useCodeComponent and useDiffComponent hooks still resolve and render correctly.code.tsx into shared modules.File for both text and diff modes.Ctrl/Cmd+F, Ctrl/Cmd+G, Shift+Ctrl/Cmd+G) and active-host behavior.session-review.tsx and file-tabs.tsx into shared UI helpers.file-media.tsx and let unified File optionally render media or binary placeholders before falling back to text/diff.session-review.tsx and file-tabs.tsx to pass media props instead of owning media-specific branches.packages/ui/src/components/file-ssr.tsx with the same unified prop shape plus preloadedDiff.packages/ui/src/components/diff-ssr.tsx into a thin adapter that forwards to the unified SSR entry in diff mode.@opencode-ai/ui/file-ssr when convenient, but keep diff-ssr export working.packages/enterprise/src/routes/share/[shareID].tsx.fixSelection implementation remains.code.tsx and diff.tsx.Code/Diff to unified File.Code/Diff contexts and components as compatibility APIs in comments or docs.Keep old APIs working while moving internals under them.
FileComponentProvider without deleting CodeComponentProvider or DiffComponentProvider.useCodeComponent and useDiffComponent as adapters around the unified context where possible.packages/app/src/pages/session/file-tabs.tsx should move from useCodeComponent to useFileComponent.packages/ui/src/components/session-review.tsx, session-turn.tsx, and message-part.tsx should move from useDiffComponent to useFileComponent.packages/app/src/app.tsx and packages/enterprise/src/routes/share/[shareID].tsx should eventually provide only the unified provider.@opencode-ai/ui/code, @opencode-ai/ui/diff, and @opencode-ai/ui/diff-ssr imports must keep working during migration.Code and Diff wrappers should remain stable to avoid broad app changes in one PR.Port the current find feature into a shared engine and attach it to both modes.
code.tsx into packages/ui/src/pierre/file-find.ts.packages/ui/src/components/file-search.tsx.onRendered, diff style changes, annotation rerenders, and query changes.Move media rendering logic into shared UI so text, diff, and media routing live behind one entry.
packages/ui/src/pierre/media.ts.packages/ui/src/components/file-media.tsx.session-review.tsx and file-tabs.tsx, but remove duplicated media branching and load-state code from them.media.mode: "auto" or "off" for default behavior.media.path: file path for extension checks and labels.media.current: loaded file content for plain-file views.media.before and media.after: diff-side values for image/audio previews.media.readFile: optional lazy loader for session review expansion.media.renderBinaryPlaceholder: optional consumer override for binary states.media.renderLoading and media.renderError: optional consumer overrides when generic text is not enough.Make SSR diff hydration a mode of the unified viewer instead of a parallel implementation.
packages/ui/src/components/file-ssr.tsx as the unified SSR entry with a diff-only path in phase one.fixSelection, theme sync, and commented-line marking.fileContainer hydration workaround isolated in the SSR module so client code stays clean.packages/ui/src/components/diff-ssr.tsx as a forwarding adapter for compatibility.File component so SessionReview can swap client/SSR providers without branching logic.Use typechecks and targeted UI checks after each phase, and avoid repo-root runs.
bun run typecheck from packages/ui after phases 1-7 changes there.bun run typecheck from packages/app after migrating file tabs or app provider wiring.bun run typecheck from packages/enterprise after SSR/provider changes on the share route.packages/app/src/pages/session/file-tabs.tsx.Keep wrappers and adapters in place until the unified path is proven.
dev.code.tsx, diff.tsx, and diff-ssr.tsx wrappers intact until final verification, so a rollback only changes internals.Follow this sequence to keep reviews small and reduce merge risk.
code.tsx and diff.tsx to it.File component and convert code.tsx/diff.tsx into wrappers.FileComponentProvider and migrate provider wiring in app.tsx and enterprise share route.file-tabs, session-review, message-part, session-turn) to the unified context.session-review.tsx and file-tabs.tsx.file-ssr.tsx, convert diff-ssr.tsx to a wrapper, and migrate enterprise imports.Resolve these before coding to avoid rework mid-refactor.
mode, or should it infer mode from props for convenience.File only, or also ship a temporary alias like UnifiedFile for migration clarity.preloadedDiff live on the main File props or only on file-ssr.tsx.CodeComponentProvider and DiffComponentProvider should remain supported.diff-ssr should remain as a permanent alias for compatibility.