feat: better code and diff rendering performance

This commit is contained in:
Adam
2025-12-02 06:50:16 -06:00
parent 221bb64aeb
commit c0a35141e6
14 changed files with 225 additions and 100 deletions

View File

@@ -1,9 +1,11 @@
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["opencode-openai-codex-auth"],
"enterprise": {
"url": "https://enterprise.dev.opencode.ai",
},
"plugin": [
"opencode-openai-codex-auth"
],
// "enterprise": {
// "url": "https://enterprise.dev.opencode.ai",
// },
"provider": {
"opencode": {
"options": {
@@ -18,7 +20,10 @@
},
"morph": {
"type": "local",
"command": ["bunx", "@morphllm/morphmcp"],
"command": [
"bunx",
"@morphllm/morphmcp"
],
"environment": {
"ENABLED_TOOLS": "warp_grep",
},

View File

@@ -443,7 +443,7 @@
"@hono/zod-validator": "0.4.2",
"@kobalte/core": "0.13.11",
"@openauthjs/openauth": "0.0.0-20250322224806",
"@pierre/precision-diffs": "0.5.7",
"@pierre/precision-diffs": "0.6.0-beta.3",
"@solidjs/meta": "0.29.4",
"@solidjs/router": "0.15.4",
"@solidjs/start": "https://pkg.pr.new/@solidjs/start@dfb2020",
@@ -1218,7 +1218,7 @@
"@petamoriken/float16": ["@petamoriken/float16@3.9.3", "", {}, "sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g=="],
"@pierre/precision-diffs": ["@pierre/precision-diffs@0.5.7", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/transformers": "3.15.0", "diff": "8.0.2", "fast-deep-equal": "3.1.3", "hast-util-to-html": "9.0.5", "shiki": "3.15.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-Y+e4kJ9pT2I4NS5fE39KdoiXtwMkVPRvrwLM6O2IqO7PDCRWLBS7CYxcSgSyngEndccUll2krx66I2QnfO0Ovg=="],
"@pierre/precision-diffs": ["@pierre/precision-diffs@0.6.0-beta.3", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/transformers": "3.15.0", "diff": "8.0.2", "fast-deep-equal": "3.1.3", "hast-util-to-html": "9.0.5", "shiki": "3.15.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-1FBm9jhLWZvs7BqN3yG2Wh9SpGuO1us2QsKZlQqSwyCctMr9DRGzYQJ9lF6yR03LHzXs3fuIzO++d9sCObYzrQ=="],
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],

View File

@@ -30,7 +30,7 @@
"@tsconfig/bun": "1.0.9",
"@cloudflare/workers-types": "4.20251008.0",
"@openauthjs/openauth": "0.0.0-20250322224806",
"@pierre/precision-diffs": "0.5.7",
"@pierre/precision-diffs": "0.6.0-beta.3",
"@tailwindcss/vite": "4.1.11",
"diff": "8.0.2",
"ai": "5.0.97",

View File

@@ -30,6 +30,7 @@ import { useSync } from "@/context/sync"
import { useSession } from "@/context/session"
import { useLayout } from "@/context/layout"
import { getDirectory, getFilename } from "@opencode-ai/util/path"
import { Diff } from "@opencode-ai/ui/diff"
export default function Page() {
const layout = useLayout()
@@ -357,6 +358,7 @@ export default function Page() {
content: "pb-20",
container: "w-full " + (wide() ? "max-w-146 mx-auto px-6" : "pr-6 pl-18"),
}}
diffComponent={Diff}
/>
</div>
</Match>
@@ -405,6 +407,7 @@ export default function Page() {
container: "px-6",
}}
diffs={session.diffs()}
diffComponent={Diff}
actions={
<Tooltip value="Open in tab">
<IconButton
@@ -436,6 +439,7 @@ export default function Page() {
container: "px-6",
}}
diffs={session.diffs()}
diffComponent={Diff}
split
/>
</div>

View File

@@ -18,6 +18,10 @@ import z from "zod"
import NotFound from "../[...404]"
import { Tabs } from "@opencode-ai/ui/tabs"
import { preloadMultiFileDiff, PreloadMultiFileDiffResult } from "@pierre/precision-diffs/ssr"
import { Diff } from "@opencode-ai/ui/diff-ssr"
import { clientOnly } from "@solidjs/start"
const ClientOnlyDiff = clientOnly(() => import("@opencode-ai/ui/diff").then((m) => ({ default: m.Diff })))
const SessionDataMissingError = NamedError.create(
"SessionDataMissingError",
@@ -230,6 +234,7 @@ export default function () {
"flex flex-col justify-between !overflow-visible [&_[data-slot=session-turn-message-header]]:top-[-32px]",
container: "px-4",
}}
diffComponent={ClientOnlyDiff}
/>
)}
</For>
@@ -299,6 +304,7 @@ export default function () {
content: "flex flex-col justify-between items-start",
container: "w-full pb-20 " + (wide() ? "max-w-146 mx-auto px-6" : "pr-6 pl-18"),
}}
diffComponent={ClientOnlyDiff}
>
<div classList={{ "w-full flex items-center justify-center pb-8 shrink-0": true }}>
<Logo class="w-58.5 opacity-12" />
@@ -311,6 +317,7 @@ export default function () {
<SessionReview
class="@4xl:hidden"
diffs={diffs()}
diffComponent={Diff}
classes={{
root: "pb-20",
header: "px-6",
@@ -318,9 +325,10 @@ export default function () {
}}
/>
<SessionReview
class="hidden @4xl:flex"
split
class="hidden @4xl:flex"
diffs={splitDiffs()}
diffComponent={Diff}
classes={{
root: "pb-20",
header: "px-6",
@@ -352,6 +360,7 @@ export default function () {
<div class="relative h-full pt-8 overflow-y-auto no-scrollbar">
<SessionReview
diffs={diffs()}
diffComponent={Diff}
classes={{
root: "pb-20",
header: "px-4",

View File

@@ -4,7 +4,7 @@
"type": "module",
"exports": {
"./*": "./src/components/*.tsx",
"./pierre": "./src/components/pierre.ts",
"./pierre": "./src/pierre/index.ts",
"./hooks": "./src/hooks/index.ts",
"./context": "./src/context/index.ts",
"./context/*": "./src/context/*.tsx",

View File

@@ -1,6 +1,22 @@
import { type FileContents, File, FileOptions, LineAnnotation } from "@pierre/precision-diffs"
import { ComponentProps, createEffect, splitProps } from "solid-js"
import { createDefaultOptions, styleVariables } from "./pierre"
import { createDefaultOptions, styleVariables } from "../pierre"
import { getOrCreateWorkerPoolSingleton } from "@pierre/precision-diffs/worker"
import { workerFactory } from "../pierre/worker"
const workerPool = getOrCreateWorkerPoolSingleton({
poolOptions: {
workerFactory,
// poolSize defaults to 8. More workers = more parallelism but
// also more memory. Too many can actually slow things down.
// poolSize: 8,
},
highlighterOptions: {
theme: "OpenCode",
// Optionally preload languages to avoid lazy-loading delays
// langs: ["typescript", "javascript", "css", "html"],
},
})
export type CodeProps<T = {}> = FileOptions<T> & {
file: FileContents
@@ -14,10 +30,13 @@ export function Code<T>(props: CodeProps<T>) {
const [local, others] = splitProps(props, ["file", "class", "classList", "annotations"])
createEffect(() => {
const instance = new File<T>({
...createDefaultOptions<T>("unified"),
...others,
})
const instance = new File<T>(
{
...createDefaultOptions<T>("unified"),
...others,
},
workerPool,
)
container.innerHTML = ""
instance.render({

View File

@@ -0,0 +1,75 @@
import { FileDiff } from "@pierre/precision-diffs"
import { PreloadMultiFileDiffResult } from "@pierre/precision-diffs/ssr"
import { onCleanup, onMount, Show, splitProps } from "solid-js"
import { isServer } from "solid-js/web"
import { createDefaultOptions, styleVariables, type DiffProps } from "../pierre"
export type SSRDiffProps<T = {}> = DiffProps<T> & {
preloadedDiff: PreloadMultiFileDiffResult<T>
}
export function Diff<T>(props: SSRDiffProps<T>) {
let container!: HTMLDivElement
let fileDiffRef!: HTMLElement
const [local, others] = splitProps(props, ["before", "after", "class", "classList", "annotations"])
let fileDiffInstance: FileDiff<T> | undefined
const cleanupFunctions: Array<() => void> = []
onMount(() => {
if (isServer || !props.preloadedDiff) return
fileDiffInstance = new FileDiff<T>({
...createDefaultOptions(props.diffStyle),
...others,
...props.preloadedDiff,
})
// @ts-expect-error - fileContainer is private but needed for SSR hydration
fileDiffInstance.fileContainer = fileDiffRef
fileDiffInstance.hydrate({
oldFile: local.before,
newFile: local.after,
lineAnnotations: local.annotations,
fileContainer: fileDiffRef,
containerWrapper: container,
})
// Hydrate annotation slots with interactive SolidJS components
// if (props.annotations.length > 0 && props.renderAnnotation != null) {
// for (const annotation of props.annotations) {
// const slotName = `annotation-${annotation.side}-${annotation.lineNumber}`;
// const slotElement = fileDiffRef.querySelector(
// `[slot="${slotName}"]`
// ) as HTMLElement;
//
// if (slotElement != null) {
// // Clear the static server-rendered content from the slot
// slotElement.innerHTML = '';
//
// // Mount a fresh SolidJS component into this slot using render().
// // This enables full SolidJS reactivity (signals, effects, etc.)
// const dispose = render(
// () => props.renderAnnotation!(annotation),
// slotElement
// );
// cleanupFunctions.push(dispose);
// }
// }
// }
})
onCleanup(() => {
// Clean up FileDiff event handlers and dispose SolidJS components
fileDiffInstance?.cleanUp()
cleanupFunctions.forEach((dispose) => dispose())
})
return (
<div data-component="diff" style={styleVariables} ref={container}>
<file-diff ref={fileDiffRef} id="ssr-diff">
<Show when={isServer}>
<template shadowrootmode="open" innerHTML={props.preloadedDiff.prerenderedHTML} />
</Show>
</file-diff>
</div>
)
}

View File

@@ -1,17 +1,22 @@
import { type FileContents, FileDiff, type DiffLineAnnotation, FileDiffOptions } from "@pierre/precision-diffs"
import { PreloadMultiFileDiffResult } from "@pierre/precision-diffs/ssr"
import { ComponentProps, createEffect, onCleanup, onMount, Show, splitProps } from "solid-js"
import { isServer } from "solid-js/web"
import { createDefaultOptions, styleVariables } from "./pierre"
import { FileDiff } from "@pierre/precision-diffs"
import { getOrCreateWorkerPoolSingleton } from "@pierre/precision-diffs/worker"
import { createEffect, onCleanup, splitProps } from "solid-js"
import { createDefaultOptions, type DiffProps, styleVariables } from "../pierre"
import { workerFactory } from "../pierre/worker"
export type DiffProps<T = {}> = FileDiffOptions<T> & {
preloadedDiff?: PreloadMultiFileDiffResult<T>
before: FileContents
after: FileContents
annotations?: DiffLineAnnotation<T>[]
class?: string
classList?: ComponentProps<"div">["classList"]
}
const workerPool = getOrCreateWorkerPoolSingleton({
poolOptions: {
workerFactory,
// poolSize defaults to 8. More workers = more parallelism but
// also more memory. Too many can actually slow things down.
// poolSize: 8,
},
highlighterOptions: {
theme: "OpenCode",
// Optionally preload languages to avoid lazy-loading delays
// langs: ["typescript", "javascript", "css", "html"],
},
})
// interface ThreadMetadata {
// threadId: string
@@ -21,21 +26,21 @@ export type DiffProps<T = {}> = FileDiffOptions<T> & {
export function Diff<T>(props: DiffProps<T>) {
let container!: HTMLDivElement
let fileDiffRef!: HTMLElement
const [local, others] = splitProps(props, ["before", "after", "class", "classList", "annotations"])
let fileDiffInstance: FileDiff<T> | undefined
const cleanupFunctions: Array<() => void> = []
createEffect(() => {
if (props.preloadedDiff) return
container.innerHTML = ""
if (!fileDiffInstance) {
fileDiffInstance = new FileDiff<T>({
...createDefaultOptions(props.diffStyle),
...others,
...(props.preloadedDiff ?? {}),
})
fileDiffInstance = new FileDiff<T>(
{
...createDefaultOptions(props.diffStyle),
...others,
},
workerPool,
)
}
fileDiffInstance.render({
oldFile: local.before,
@@ -45,60 +50,11 @@ export function Diff<T>(props: DiffProps<T>) {
})
})
onMount(() => {
if (isServer || !props.preloadedDiff) return
fileDiffInstance = new FileDiff<T>({
...createDefaultOptions(props.diffStyle),
...others,
...(props.preloadedDiff ?? {}),
})
// @ts-expect-error - fileContainer is private but needed for SSR hydration
fileDiffInstance.fileContainer = fileDiffRef
fileDiffInstance.hydrate({
oldFile: local.before,
newFile: local.after,
lineAnnotations: local.annotations,
fileContainer: fileDiffRef,
containerWrapper: container,
})
// Hydrate annotation slots with interactive SolidJS components
// if (props.annotations.length > 0 && props.renderAnnotation != null) {
// for (const annotation of props.annotations) {
// const slotName = `annotation-${annotation.side}-${annotation.lineNumber}`;
// const slotElement = fileDiffRef.querySelector(
// `[slot="${slotName}"]`
// ) as HTMLElement;
//
// if (slotElement != null) {
// // Clear the static server-rendered content from the slot
// slotElement.innerHTML = '';
//
// // Mount a fresh SolidJS component into this slot using render().
// // This enables full SolidJS reactivity (signals, effects, etc.)
// const dispose = render(
// () => props.renderAnnotation!(annotation),
// slotElement
// );
// cleanupFunctions.push(dispose);
// }
// }
// }
})
onCleanup(() => {
// Clean up FileDiff event handlers and dispose SolidJS components
fileDiffInstance?.cleanUp()
cleanupFunctions.forEach((dispose) => dispose())
})
return (
<div data-component="diff" style={styleVariables} ref={container}>
<file-diff ref={fileDiffRef} id="ssr-diff">
<Show when={isServer && props.preloadedDiff}>
{(preloadedDiff) => <template shadowrootmode="open" innerHTML={preloadedDiff().prerenderedHTML} />}
</Show>
</file-diff>
</div>
)
return <div data-component="diff" style={styleVariables} ref={container} />
}

View File

@@ -1,4 +1,4 @@
import { Component, createMemo, For, Match, Show, Switch } from "solid-js"
import { Component, createMemo, For, Match, Show, Switch, ValidComponent } from "solid-js"
import { Dynamic } from "solid-js/web"
import {
AssistantMessage,
@@ -13,7 +13,6 @@ import { GenericTool } from "./basic-tool"
import { Card } from "./card"
import { Icon } from "./icon"
import { Checkbox } from "./checkbox"
import { Diff } from "./diff"
import { DiffChanges } from "./diff-changes"
import { Markdown } from "./markdown"
import { getDirectory, getFilename } from "@opencode-ai/util/path"
@@ -23,12 +22,14 @@ import { unwrap } from "solid-js/store"
export interface MessageProps {
message: MessageType
parts: PartType[]
diffComponent: ValidComponent
sanitize?: RegExp
}
export interface MessagePartProps {
part: PartType
message: MessageType
diffComponent: ValidComponent
hideDetails?: boolean
sanitize?: RegExp
}
@@ -53,6 +54,7 @@ export function Message(props: MessageProps) {
message={assistantMessage() as AssistantMessage}
parts={props.parts}
sanitize={props.sanitize}
diffComponent={props.diffComponent}
/>
)}
</Match>
@@ -60,7 +62,12 @@ export function Message(props: MessageProps) {
)
}
export function AssistantMessageDisplay(props: { message: AssistantMessage; parts: PartType[]; sanitize?: RegExp }) {
export function AssistantMessageDisplay(props: {
message: AssistantMessage
parts: PartType[]
sanitize?: RegExp
diffComponent: ValidComponent
}) {
const filteredParts = createMemo(() => {
return props.parts?.filter((x) => {
if (x.type === "reasoning") return false
@@ -68,7 +75,11 @@ export function AssistantMessageDisplay(props: { message: AssistantMessage; part
})
})
return (
<For each={filteredParts()}>{(part) => <Part part={part} message={props.message} sanitize={props.sanitize} />}</For>
<For each={filteredParts()}>
{(part) => (
<Part part={part} message={props.message} sanitize={props.sanitize} diffComponent={props.diffComponent} />
)}
</For>
)
}
@@ -87,7 +98,13 @@ export function Part(props: MessagePartProps) {
const part = createMemo(() => sanitizePart(unwrap(props.part), props.sanitize))
return (
<Show when={component()}>
<Dynamic component={component()} part={part()} message={props.message} hideDetails={props.hideDetails} />
<Dynamic
component={component()}
part={part()}
message={props.message}
diffComponent={props.diffComponent}
hideDetails={props.hideDetails}
/>
</Show>
)
}
@@ -96,6 +113,7 @@ export interface ToolProps {
input: Record<string, any>
metadata: Record<string, any>
tool: string
diffComponent: ValidComponent
output?: string
hideDetails?: boolean
}
@@ -162,6 +180,7 @@ PART_MAPPING["tool"] = function ToolPartDisplay(props) {
component={render}
input={input}
tool={part.tool}
diffComponent={props.diffComponent}
metadata={metadata}
output={part.state.status === "completed" ? part.state.output : undefined}
hideDetails={props.hideDetails}
@@ -361,7 +380,8 @@ ToolRegistry.register({
>
<Show when={props.metadata.filediff}>
<div data-component="edit-content">
<Diff
<Dynamic
component={props.diffComponent}
before={{
name: getFilename(props.metadata.filediff.path),
contents: props.metadata.filediff.before,

View File

@@ -1,15 +1,15 @@
import { Accordion } from "./accordion"
import { Button } from "./button"
import { Diff } from "./diff"
import { DiffChanges } from "./diff-changes"
import { FileIcon } from "./file-icon"
import { Icon } from "./icon"
import { StickyAccordionHeader } from "./sticky-accordion-header"
import { getDirectory, getFilename } from "@opencode-ai/util/path"
import { For, Match, Show, Switch, type JSX } from "solid-js"
import { For, Match, Show, Switch, ValidComponent, type JSX } from "solid-js"
import { createStore } from "solid-js/store"
import { type FileDiff } from "@opencode-ai/sdk"
import { PreloadMultiFileDiffResult } from "@pierre/precision-diffs/ssr"
import { Dynamic } from "solid-js/web"
export interface SessionReviewProps {
split?: boolean
@@ -18,6 +18,7 @@ export interface SessionReviewProps {
classes?: { root?: string; header?: string; container?: string }
actions?: JSX.Element
diffs: (FileDiff & { preloaded?: PreloadMultiFileDiffResult<any> })[]
diffComponent: ValidComponent
}
export const SessionReview = (props: SessionReviewProps) => {
@@ -96,7 +97,8 @@ export const SessionReview = (props: SessionReviewProps) => {
</Accordion.Trigger>
</StickyAccordionHeader>
<Accordion.Content data-slot="session-review-accordion-content">
<Diff
<Dynamic
component={props.diffComponent}
preloadedDiff={diff.preloaded}
diffStyle={props.split ? "split" : "unified"}
before={{

View File

@@ -2,7 +2,18 @@ import { AssistantMessage } from "@opencode-ai/sdk"
import { useData } from "../context"
import { Binary } from "@opencode-ai/util/binary"
import { getDirectory, getFilename } from "@opencode-ai/util/path"
import { createEffect, createMemo, createSignal, For, Match, onMount, ParentProps, Show, Switch } from "solid-js"
import {
createEffect,
createMemo,
createSignal,
For,
Match,
onMount,
ParentProps,
Show,
Switch,
ValidComponent,
} from "solid-js"
import { DiffChanges } from "./diff-changes"
import { Typewriter } from "./typewriter"
import { Message } from "./message-part"
@@ -11,10 +22,10 @@ import { Accordion } from "./accordion"
import { StickyAccordionHeader } from "./sticky-accordion-header"
import { FileIcon } from "./file-icon"
import { Icon } from "./icon"
import { Diff } from "./diff"
import { Card } from "./card"
import { MessageProgress } from "./message-progress"
import { Collapsible } from "./collapsible"
import { Dynamic } from "solid-js/web"
export function SessionTurn(
props: ParentProps<{
@@ -25,6 +36,7 @@ export function SessionTurn(
content?: string
container?: string
}
diffComponent: ValidComponent
}>,
) {
const data = useData()
@@ -117,7 +129,7 @@ export function SessionTurn(
</div>
</div>
<div data-slot="session-turn-message-content">
<Message message={msg()} parts={parts()} sanitize={sanitizer()} />
<Message message={msg()} parts={parts()} sanitize={sanitizer()} diffComponent={props.diffComponent} />
</div>
{/* Summary */}
<Show when={completed()}>
@@ -167,7 +179,8 @@ export function SessionTurn(
</Accordion.Trigger>
</StickyAccordionHeader>
<Accordion.Content data-slot="session-turn-accordion-content">
<Diff
<Dynamic
component={props.diffComponent}
before={{
name: diff.file!,
contents: diff.before!,
@@ -224,10 +237,18 @@ export function SessionTurn(
message={assistantMessage}
parts={parts().filter((p) => p?.id !== last()?.id)}
sanitize={sanitizer()}
diffComponent={props.diffComponent}
/>
)
}
return <Message message={assistantMessage} parts={parts()} sanitize={sanitizer()} />
return (
<Message
message={assistantMessage}
parts={parts()}
sanitize={sanitizer()}
diffComponent={props.diffComponent}
/>
)
}}
</For>
<Show when={error()}>

View File

@@ -1,4 +1,13 @@
import { FileDiffOptions } from "@pierre/precision-diffs"
import { DiffLineAnnotation, FileContents, FileDiffOptions } from "@pierre/precision-diffs"
import { ComponentProps } from "solid-js"
export type DiffProps<T = {}> = FileDiffOptions<T> & {
before: FileContents
after: FileContents
annotations?: DiffLineAnnotation<T>[]
class?: string
classList?: ComponentProps<"div">["classList"]
}
export function createDefaultOptions<T>(style: FileDiffOptions<T>["diffStyle"]) {
return {

View File

@@ -0,0 +1,5 @@
import ShikiWorkerUrl from "@pierre/precision-diffs/worker/shiki-worker.js?worker&url"
export function workerFactory(): Worker {
return new Worker(ShikiWorkerUrl, { type: "module" })
}