mirror of
https://github.com/sst/opencode.git
synced 2025-12-23 10:11:41 +00:00
feat(share): SSR'd diffs
This commit is contained in:
parent
57bd47a446
commit
15facd8cfd
13 changed files with 200 additions and 131 deletions
5
bun.lock
5
bun.lock
|
|
@ -166,6 +166,7 @@
|
|||
"dependencies": {
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@opencode-ai/util": "workspace:*",
|
||||
"@pierre/precision-diffs": "catalog:",
|
||||
"@solidjs/meta": "catalog:",
|
||||
"@solidjs/router": "catalog:",
|
||||
"@solidjs/start": "catalog:",
|
||||
|
|
@ -436,7 +437,7 @@
|
|||
"@hono/zod-validator": "0.4.2",
|
||||
"@kobalte/core": "0.13.11",
|
||||
"@openauthjs/openauth": "0.0.0-20250322224806",
|
||||
"@pierre/precision-diffs": "0.5.4",
|
||||
"@pierre/precision-diffs": "0.5.5",
|
||||
"@solidjs/meta": "0.29.4",
|
||||
"@solidjs/router": "0.15.4",
|
||||
"@solidjs/start": "https://pkg.pr.new/@solidjs/start@dfb2020",
|
||||
|
|
@ -1211,7 +1212,7 @@
|
|||
|
||||
"@petamoriken/float16": ["@petamoriken/float16@3.9.3", "", {}, "sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g=="],
|
||||
|
||||
"@pierre/precision-diffs": ["@pierre/precision-diffs@0.5.4", "", { "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-sfKCxApl+FcqvlZ4EenZtPfmCMi0w8VZBpRsCtSpgpCczmp7YI2sS/sxWokT/ieCJjyFiynb5rHcnAB7GZbXMQ=="],
|
||||
"@pierre/precision-diffs": ["@pierre/precision-diffs@0.5.5", "", { "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-mmDHEWWQ6fmXY5qRNHqodzOxHPwLqVNbbnO/MOpXteOTjd0nVIGy5IcaNwU2WSxhxQRwaUepKyx5+wwPcZLEmw=="],
|
||||
|
||||
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
|
||||
|
||||
|
|
|
|||
|
|
@ -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.4",
|
||||
"@pierre/precision-diffs": "0.5.5",
|
||||
"@tailwindcss/vite": "4.1.11",
|
||||
"diff": "8.0.2",
|
||||
"ai": "5.0.97",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
"dependencies": {
|
||||
"@opencode-ai/util": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@pierre/precision-diffs": "catalog:",
|
||||
"@solidjs/router": "catalog:",
|
||||
"@solidjs/start": "catalog:",
|
||||
"@solidjs/meta": "catalog:",
|
||||
|
|
|
|||
|
|
@ -3,21 +3,19 @@ import { FileRoutes } from "@solidjs/start/router"
|
|||
import { Fonts } from "@opencode-ai/ui/fonts"
|
||||
import { MetaProvider } from "@solidjs/meta"
|
||||
import { MarkedProvider } from "@opencode-ai/ui/context/marked"
|
||||
import "./app.css"
|
||||
import { Suspense } from "solid-js"
|
||||
import "./app.css"
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Router
|
||||
root={(props) => (
|
||||
<Suspense>
|
||||
<MarkedProvider>
|
||||
<MetaProvider>
|
||||
<Fonts />
|
||||
{props.children}
|
||||
</MetaProvider>
|
||||
</MarkedProvider>
|
||||
</Suspense>
|
||||
<MarkedProvider>
|
||||
<MetaProvider>
|
||||
<Fonts />
|
||||
<Suspense>{props.children}</Suspense>
|
||||
</MetaProvider>
|
||||
</MarkedProvider>
|
||||
)}
|
||||
>
|
||||
<FileRoutes />
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import { FileDiff, Message, Model, Part, Session, SessionStatus, UserMessage } f
|
|||
import { SessionTurn } from "@opencode-ai/ui/session-turn"
|
||||
import { SessionReview } from "@opencode-ai/ui/session-review"
|
||||
import { DataProvider } from "@opencode-ai/ui/context"
|
||||
import { createAsync, query, RouteDefinition, useParams } from "@solidjs/router"
|
||||
import { createEffect, createMemo, ErrorBoundary, For, Match, Show, Suspense, Switch } from "solid-js"
|
||||
import { createAsync, query, useParams } from "@solidjs/router"
|
||||
import { createEffect, createMemo, ErrorBoundary, For, Match, Show, Switch } from "solid-js"
|
||||
import { Share } from "~/core/share"
|
||||
import { Logo, Mark } from "@opencode-ai/ui/logo"
|
||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||
|
|
@ -16,6 +16,7 @@ import { createStore } from "solid-js/store"
|
|||
import z from "zod"
|
||||
import NotFound from "../[...404]"
|
||||
import { Tabs } from "@opencode-ai/ui/tabs"
|
||||
import { HunkData, preloadMultiFileDiff, PreloadMultiFileDiffResult } from "@pierre/precision-diffs/ssr"
|
||||
|
||||
const SessionDataMissingError = NamedError.create(
|
||||
"SessionDataMissingError",
|
||||
|
|
@ -36,6 +37,9 @@ const getData = query(async (shareID) => {
|
|||
session_diff: {
|
||||
[sessionID: string]: FileDiff[]
|
||||
}
|
||||
session_diff_preload: {
|
||||
[sessionID: string]: PreloadMultiFileDiffResult<any>[]
|
||||
}
|
||||
session_status: {
|
||||
[sessionID: string]: SessionStatus
|
||||
}
|
||||
|
|
@ -54,6 +58,9 @@ const getData = query(async (shareID) => {
|
|||
session_diff: {
|
||||
[share.sessionID]: [],
|
||||
},
|
||||
session_diff_preload: {
|
||||
[share.sessionID]: [],
|
||||
},
|
||||
session_status: {
|
||||
[share.sessionID]: {
|
||||
type: "idle",
|
||||
|
|
@ -70,6 +77,29 @@ const getData = query(async (shareID) => {
|
|||
break
|
||||
case "session_diff":
|
||||
result.session_diff[share.sessionID] = item.data
|
||||
result.session_diff_preload[share.sessionID] = await Promise.all(
|
||||
item.data.map(async (diff) =>
|
||||
preloadMultiFileDiff<any>({
|
||||
oldFile: { name: diff.file, contents: diff.before },
|
||||
newFile: { name: diff.file, contents: diff.after },
|
||||
options: {
|
||||
theme: "OpenCode",
|
||||
themeType: "system",
|
||||
disableLineNumbers: false,
|
||||
overflow: "wrap",
|
||||
diffStyle: "unified",
|
||||
diffIndicators: "bars",
|
||||
disableBackground: false,
|
||||
expansionLineCount: 20,
|
||||
lineDiffType: "word-alt",
|
||||
maxLineDiffLength: 1000,
|
||||
maxLineLengthForHighlighting: 1000,
|
||||
disableFileHeader: true,
|
||||
},
|
||||
// annotations,
|
||||
}),
|
||||
),
|
||||
)
|
||||
break
|
||||
case "message":
|
||||
result.message[item.data.sessionID] = result.message[item.data.sessionID] ?? []
|
||||
|
|
@ -141,7 +171,14 @@ export default function () {
|
|||
const provider = createMemo(() => activeMessage()?.model?.providerID)
|
||||
const modelID = createMemo(() => activeMessage()?.model?.modelID)
|
||||
const model = createMemo(() => data().model[data().sessionID]?.find((m) => m.id === modelID()))
|
||||
const diffs = createMemo(() => data().session_diff[data().sessionID] ?? [])
|
||||
const diffs = createMemo(() => {
|
||||
const diffs = data().session_diff[data().sessionID] ?? []
|
||||
const preloaded = data().session_diff_preload[data().sessionID] ?? []
|
||||
return diffs.map((diff) => ({
|
||||
...diff,
|
||||
preloaded: preloaded.find((d) => d.newFile.name === diff.file),
|
||||
}))
|
||||
})
|
||||
|
||||
const title = () => (
|
||||
<div class="flex flex-col gap-4 shrink-0">
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
[data-slot="diff-changes-additions"] {
|
||||
font-family: var(--font-family-mono);
|
||||
font-feature-settings: var(--font-family-mono--font-feature-settings);
|
||||
font-size: var(--font-size-small);
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-regular);
|
||||
|
|
@ -17,6 +18,7 @@
|
|||
|
||||
[data-slot="diff-changes-deletions"] {
|
||||
font-family: var(--font-family-mono);
|
||||
font-feature-settings: var(--font-family-mono--font-feature-settings);
|
||||
font-size: var(--font-size-small);
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-regular);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,9 @@
|
|||
width: var(--pjs-column-content-width);
|
||||
left: var(--pjs-column-number-width);
|
||||
padding-left: 8px;
|
||||
user-select: none;
|
||||
cursor: default;
|
||||
text-align: left;
|
||||
|
||||
[data-slot="diff-hunk-separator-content-span"] {
|
||||
mix-blend-mode: var(--text-mix-blend-mode);
|
||||
|
|
|
|||
|
|
@ -1,13 +1,10 @@
|
|||
import {
|
||||
type FileContents,
|
||||
FileDiff,
|
||||
type DiffLineAnnotation,
|
||||
type HunkData,
|
||||
FileDiffOptions,
|
||||
} from "@pierre/precision-diffs"
|
||||
import { ComponentProps, createEffect, splitProps } from "solid-js"
|
||||
import { type FileContents, FileDiff, type DiffLineAnnotation, FileDiffOptions } from "@pierre/precision-diffs"
|
||||
import { PreloadMultiFileDiffResult } from "@pierre/precision-diffs/ssr"
|
||||
import { ComponentProps, createEffect, onCleanup, onMount, splitProps } from "solid-js"
|
||||
import { isServer } from "solid-js/web"
|
||||
|
||||
export type DiffProps<T = {}> = FileDiffOptions<T> & {
|
||||
preloadedDiff?: PreloadMultiFileDiffResult<T>
|
||||
before: FileContents
|
||||
after: FileContents
|
||||
annotations?: DiffLineAnnotation<T>[]
|
||||
|
|
@ -21,116 +18,69 @@ 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"])
|
||||
|
||||
// const lineAnnotations: DiffLineAnnotation<ThreadMetadata>[] = [
|
||||
// {
|
||||
// side: "additions",
|
||||
// // The line number specified for an annotation is the visual line number
|
||||
// // you see in the number column of a diff
|
||||
// lineNumber: 16,
|
||||
// metadata: { threadId: "68b329da9893e34099c7d8ad5cb9c940" },
|
||||
// },
|
||||
// ]
|
||||
let fileDiffInstance: FileDiff<T> | undefined
|
||||
const cleanupFunctions: Array<() => void> = []
|
||||
|
||||
// If you ever want to update the options for an instance, simple call
|
||||
// 'setOptions' with the new options. Bear in mind, this does NOT merge
|
||||
// existing properties, it's a full replace
|
||||
// instance.setOptions({
|
||||
// ...instance.options,
|
||||
// theme: "pierre-dark",
|
||||
// themes: undefined,
|
||||
// })
|
||||
//
|
||||
|
||||
// When ready to render, simply call .render with old/new file, optional
|
||||
// annotations and a container element to hold the diff
|
||||
createEffect(() => {
|
||||
const instance = new FileDiff<T>({
|
||||
// Create FileDiff instance and connect to existing server-rendered DOM.
|
||||
// Don't call hydrate() - that would re-render content and cause duplication.
|
||||
// Instead, just set the fileContainer reference to attach event handlers.
|
||||
if (props.preloadedDiff) return
|
||||
container.innerHTML = ""
|
||||
if (!fileDiffInstance) {
|
||||
fileDiffInstance = new FileDiff<T>({
|
||||
theme: "OpenCode",
|
||||
themeType: "system",
|
||||
disableLineNumbers: false,
|
||||
overflow: "wrap",
|
||||
diffStyle: "unified",
|
||||
diffIndicators: "bars",
|
||||
disableBackground: false,
|
||||
expansionLineCount: 20,
|
||||
lineDiffType: "word-alt",
|
||||
maxLineDiffLength: 1000,
|
||||
maxLineLengthForHighlighting: 1000,
|
||||
disableFileHeader: true,
|
||||
// You can optionally pass a render function for rendering out line
|
||||
// annotations. Just return the dom node to render
|
||||
// renderAnnotation(annotation: DiffLineAnnotation<T>): HTMLElement {
|
||||
// // Despite the diff itself being rendered in the shadow dom,
|
||||
// // annotations are inserted via the web components 'slots' api and you
|
||||
// // can use all your normal normal css and styling for them
|
||||
// const element = document.createElement("div")
|
||||
// element.innerText = annotation.metadata.threadId
|
||||
// return element
|
||||
// },
|
||||
...others,
|
||||
...(props.preloadedDiff ?? {}),
|
||||
})
|
||||
}
|
||||
fileDiffInstance?.render({
|
||||
oldFile: local.before,
|
||||
newFile: local.after,
|
||||
lineAnnotations: local.annotations,
|
||||
containerWrapper: container,
|
||||
})
|
||||
})
|
||||
|
||||
onMount(() => {
|
||||
if (isServer) return
|
||||
|
||||
fileDiffInstance = new FileDiff<T>({
|
||||
theme: "OpenCode",
|
||||
// When using the 'themes' prop, 'themeType' allows you to force 'dark'
|
||||
// or 'light' theme, or inherit from the OS ('system') theme.
|
||||
themeType: "system",
|
||||
// Disable the line numbers for your diffs, generally not recommended
|
||||
disableLineNumbers: false,
|
||||
// Whether code should 'wrap' with long lines or 'scroll'.
|
||||
overflow: "wrap",
|
||||
// Normally you shouldn't need this prop, but if you don't provide a
|
||||
// valid filename or your file doesn't have an extension you may want to
|
||||
// override the automatic detection. You can specify that language here:
|
||||
// https://shiki.style/languages
|
||||
// lang?: SupportedLanguages;
|
||||
// 'diffStyle' controls whether the diff is presented side by side or
|
||||
// in a unified (single column) view
|
||||
diffStyle: "unified",
|
||||
// Line decorators to help highlight changes.
|
||||
// 'bars' (default):
|
||||
// Shows some red-ish or green-ish (theme dependent) bars on the left
|
||||
// edge of relevant lines
|
||||
//
|
||||
// 'classic':
|
||||
// shows '+' characters on additions and '-' characters on deletions
|
||||
//
|
||||
// 'none':
|
||||
// No special diff indicators are shown
|
||||
diffIndicators: "bars",
|
||||
// By default green-ish or red-ish background are shown on added and
|
||||
// deleted lines respectively. Disable that feature here
|
||||
disableBackground: false,
|
||||
// Diffs are split up into hunks, this setting customizes what to show
|
||||
// between each hunk.
|
||||
//
|
||||
// 'line-info' (default):
|
||||
// Shows a bar that tells you how many lines are collapsed. If you are
|
||||
// using the oldFile/newFile API then you can click those bars to
|
||||
// expand the content between them
|
||||
//
|
||||
// 'metadata':
|
||||
// Shows the content you'd see in a normal patch file, usually in some
|
||||
// format like '@@ -60,6 +60,22 @@'. You cannot use these to expand
|
||||
// hidden content
|
||||
//
|
||||
// 'simple':
|
||||
// Just a subtle bar separator between each hunk
|
||||
// hunkSeparators: "line-info",
|
||||
hunkSeparators(hunkData: HunkData) {
|
||||
const fragment = document.createDocumentFragment()
|
||||
const numCol = document.createElement("div")
|
||||
numCol.innerHTML = `<svg data-slot="diff-hunk-separator-line-number-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M8.97978 14.0204L8.62623 13.6668L9.33334 12.9597L9.68689 13.3133L9.33333 13.6668L8.97978 14.0204ZM12 16.3335L12.3535 16.6871L12 17.0406L11.6464 16.687L12 16.3335ZM14.3131 13.3133L14.6667 12.9597L15.3738 13.6668L15.0202 14.0204L14.6667 13.6668L14.3131 13.3133ZM12.5 16.0002V16.5002H11.5V16.0002H12H12.5ZM9.33333 13.6668L9.68689 13.3133L12.3535 15.9799L12 16.3335L11.6464 16.687L8.97978 14.0204L9.33333 13.6668ZM12 16.3335L11.6464 15.9799L14.3131 13.3133L14.6667 13.6668L15.0202 14.0204L12.3535 16.6871L12 16.3335ZM6.5 8.00016V7.50016H8.5V8.00016V8.50016H6.5V8.00016ZM9.5 8.00016V7.50016H11.5V8.00016V8.50016H9.5V8.00016ZM12.5 8.00016V7.50016H14.5V8.00016V8.50016H12.5V8.00016ZM15.5 8.00016V7.50016H17.5V8.00016V8.50016H15.5V8.00016ZM12 10.5002H12.5V16.0002H12H11.5V10.5002H12Z" fill="currentColor"/></svg> `
|
||||
numCol.dataset["slot"] = "diff-hunk-separator-line-number"
|
||||
fragment.appendChild(numCol)
|
||||
const contentCol = document.createElement("div")
|
||||
contentCol.dataset["slot"] = "diff-hunk-separator-content"
|
||||
const span = document.createElement("span")
|
||||
span.dataset["slot"] = "diff-hunk-separator-content-span"
|
||||
span.textContent = `${hunkData.lines} unmodified lines`
|
||||
contentCol.appendChild(span)
|
||||
fragment.appendChild(contentCol)
|
||||
return fragment
|
||||
},
|
||||
// On lines that have both additions and deletions, we can run a
|
||||
// separate diff check to mark parts of the lines that change.
|
||||
// 'none':
|
||||
// Do not show these secondary highlights
|
||||
//
|
||||
// 'char':
|
||||
// Show changes at a per character granularity
|
||||
//
|
||||
// 'word':
|
||||
// Show changes but rounded up to word boundaries
|
||||
//
|
||||
// 'word-alt' (default):
|
||||
// Similar to 'word', however we attempt to minimize single character
|
||||
// gaps between highlighted changes
|
||||
expansionLineCount: 20,
|
||||
lineDiffType: "word-alt",
|
||||
// If lines exceed these character lengths then we won't perform the
|
||||
// line lineDiffType check
|
||||
maxLineDiffLength: 1000,
|
||||
// If any line in the diff exceeds this value then we won't attempt to
|
||||
// syntax highlight the diff
|
||||
maxLineLengthForHighlighting: 1000,
|
||||
// Enabling this property will hide the file header with file name and
|
||||
// diff stats.
|
||||
disableFileHeader: true,
|
||||
// You can optionally pass a render function for rendering out line
|
||||
// annotations. Just return the dom node to render
|
||||
|
|
@ -143,15 +93,39 @@ export function Diff<T>(props: DiffProps<T>) {
|
|||
// return element
|
||||
// },
|
||||
...others,
|
||||
...(props.preloadedDiff ?? {}),
|
||||
})
|
||||
// @ts-expect-error - fileContainer is private but needed for SSR hydration
|
||||
fileDiffInstance.fileContainer = fileDiffRef
|
||||
|
||||
container.innerHTML = ""
|
||||
instance.render({
|
||||
oldFile: local.before,
|
||||
newFile: local.after,
|
||||
lineAnnotations: local.annotations,
|
||||
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 (
|
||||
|
|
@ -168,6 +142,26 @@ export function Diff<T>(props: DiffProps<T>) {
|
|||
"--pjs-min-number-column-width": "4ch",
|
||||
}}
|
||||
ref={container}
|
||||
/>
|
||||
>
|
||||
<file-diff ref={fileDiffRef} id="ssr-diff">
|
||||
{/* Only render on server - client hydrates the existing content */}
|
||||
{isServer && props.preloadedDiff && (
|
||||
<>
|
||||
{/* Declarative Shadow DOM - browsers parse this and create a shadow root */}
|
||||
<template shadowrootmode="open">
|
||||
<div innerHTML={props.preloadedDiff!.prerenderedHTML} />
|
||||
</template>
|
||||
{/* Render static annotation slots on server.
|
||||
Client will clear these and mount interactive components. */}
|
||||
{/* <For each={props.annotations}> */}
|
||||
{/* {(annotation) => { */}
|
||||
{/* const slotName = `annotation-${annotation.side}-${annotation.lineNumber}` */}
|
||||
{/* return <div slot={slotName}>{props.renderAnnotation?.(annotation)}</div> */}
|
||||
{/* }} */}
|
||||
{/* </For> */}
|
||||
</>
|
||||
)}
|
||||
</file-diff>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,17 @@
|
|||
background-color: var(--background-stronger) !important;
|
||||
}
|
||||
|
||||
[data-slot="accordion-item"] {
|
||||
[data-slot="accordion-content"] {
|
||||
display: none;
|
||||
}
|
||||
&[data-expanded] {
|
||||
[data-slot="accordion-content"] {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="session-review-trigger-content"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
|
|||
|
|
@ -9,13 +9,14 @@ import { getDirectory, getFilename } from "@opencode-ai/util/path"
|
|||
import { For, Match, Show, Switch, type JSX, splitProps } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { type FileDiff } from "@opencode-ai/sdk"
|
||||
import { PreloadMultiFileDiffResult } from "@pierre/precision-diffs/ssr"
|
||||
|
||||
export interface SessionReviewProps {
|
||||
split?: boolean
|
||||
class?: string
|
||||
classList?: Record<string, boolean | undefined>
|
||||
actions?: JSX.Element
|
||||
diffs: FileDiff[]
|
||||
diffs: (FileDiff & { preloaded?: PreloadMultiFileDiffResult<any> })[]
|
||||
}
|
||||
|
||||
export const SessionReview = (props: SessionReviewProps) => {
|
||||
|
|
@ -38,7 +39,7 @@ export const SessionReview = (props: SessionReviewProps) => {
|
|||
}
|
||||
}
|
||||
|
||||
const [split, rest] = splitProps(props, ["class", "classList"])
|
||||
const [split] = splitProps(props, ["class", "classList"])
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -63,7 +64,7 @@ export const SessionReview = (props: SessionReviewProps) => {
|
|||
<Accordion multiple value={store.open} onChange={handleChange}>
|
||||
<For each={props.diffs}>
|
||||
{(diff) => (
|
||||
<Accordion.Item value={diff.file}>
|
||||
<Accordion.Item forceMount value={diff.file} data-slot="session-review-accordion-item">
|
||||
<StickyAccordionHeader>
|
||||
<Accordion.Trigger>
|
||||
<div data-slot="session-review-trigger-content">
|
||||
|
|
@ -83,8 +84,9 @@ export const SessionReview = (props: SessionReviewProps) => {
|
|||
</div>
|
||||
</Accordion.Trigger>
|
||||
</StickyAccordionHeader>
|
||||
<Accordion.Content>
|
||||
<Accordion.Content data-slot="session-review-accordion-content">
|
||||
<Diff
|
||||
preloadedDiff={diff.preloaded}
|
||||
diffStyle={props.split ? "split" : "unified"}
|
||||
before={{
|
||||
name: diff.file!,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import type { Message, Session, Part, FileDiff, SessionStatus } from "@opencode-ai/sdk"
|
||||
import { createSimpleContext } from "./helper"
|
||||
import { PreloadMultiFileDiffResult } from "@pierre/precision-diffs/ssr"
|
||||
|
||||
type Data = {
|
||||
session: Session[]
|
||||
|
|
@ -9,6 +10,9 @@ type Data = {
|
|||
session_diff: {
|
||||
[sessionID: string]: FileDiff[]
|
||||
}
|
||||
session_diff_preload?: {
|
||||
[sessionID: string]: PreloadMultiFileDiffResult<any>[]
|
||||
}
|
||||
message: {
|
||||
[sessionID: string]: Message[]
|
||||
}
|
||||
|
|
|
|||
14
packages/ui/src/custom-elements.d.ts
vendored
Normal file
14
packages/ui/src/custom-elements.d.ts
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* TypeScript declaration for the <file-diff> custom element.
|
||||
* This tells TypeScript that <file-diff> is a valid JSX element in SolidJS.
|
||||
* Required for using the precision-diffs web component in .tsx files.
|
||||
*/
|
||||
declare module 'solid-js' {
|
||||
namespace JSX {
|
||||
interface IntrinsicElements {
|
||||
'file-diff': HTMLAttributes<HTMLElement>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
|
|
@ -76,6 +76,7 @@
|
|||
|
||||
.text-12-mono {
|
||||
font-family: var(--font-family-mono);
|
||||
font-feature-settings: var(--font-feature-settings-mono);
|
||||
font-size: var(--font-size-small);
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-regular);
|
||||
|
|
@ -103,6 +104,7 @@
|
|||
|
||||
.text-14-mono {
|
||||
font-family: var(--font-family-mono);
|
||||
font-feature-settings: var(--font-feature-settings-mono);
|
||||
font-size: var(--font-size-base);
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-regular);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue