diff --git a/packages/ui/src/components/session-review.css b/packages/ui/src/components/session-review.css index 30bfe3b712a9..35bee10484d7 100644 --- a/packages/ui/src/components/session-review.css +++ b/packages/ui/src/components/session-review.css @@ -170,6 +170,20 @@ color: var(--icon-diff-modified-base); } + [data-slot="session-review-large-diff-badge"] { + font-family: var(--font-family-sans); + font-size: var(--font-size-small); + font-weight: var(--font-weight-medium); + color: var(--text-weak); + } + + [data-slot="session-review-large-diff-warning"] { + font-family: var(--font-family-sans); + font-size: var(--font-size-small); + font-weight: var(--font-weight-medium); + color: var(--text-warning, var(--icon-diff-modified-base)); + } + [data-slot="session-review-file-container"] { padding: 0; } diff --git a/packages/ui/src/components/session-review.tsx b/packages/ui/src/components/session-review.tsx index fe2475548ea4..fe706633deb3 100644 --- a/packages/ui/src/components/session-review.tsx +++ b/packages/ui/src/components/session-review.tsx @@ -10,7 +10,7 @@ import { useDiffComponent } from "../context/diff" import { useI18n } from "../context/i18n" import { getDirectory, getFilename } from "@opencode-ai/util/path" import { checksum } from "@opencode-ai/util/encode" -import { createEffect, createMemo, createSignal, For, Match, Show, Switch, type JSX } from "solid-js" +import { createEffect, createMemo, createSignal, For, Match, onCleanup, Show, Switch, type JSX } from "solid-js" import { createStore } from "solid-js/store" import { type FileContent, type FileDiff } from "@opencode-ai/sdk/v2" import { PreloadMultiFileDiffResult } from "@pierre/diffs/ssr" @@ -170,6 +170,16 @@ function markerTop(wrapper: HTMLElement, marker: HTMLElement) { return rect.top - wrapperRect.top + Math.max(0, (rect.height - 20) / 2) } +const LARGE_DIFF_THRESHOLD = 500 + +function diffLines(diff: FileDiff) { + return (diff.additions ?? 0) + (diff.deletions ?? 0) +} + +function isLargeDiff(diff: FileDiff) { + return diffLines(diff) > LARGE_DIFF_THRESHOLD +} + export const SessionReview = (props: SessionReviewProps) => { let scroll: HTMLDivElement | undefined let focusToken = 0 @@ -177,25 +187,67 @@ export const SessionReview = (props: SessionReviewProps) => { const diffComponent = useDiffComponent() const anchors = new Map() const [store, setStore] = createStore({ - open: props.diffs.length > 10 ? [] : props.diffs.map((d) => d.file), + open: props.diffs.length > 10 ? [] : props.diffs.filter((d) => !isLargeDiff(d)).map((d) => d.file), }) const [selection, setSelection] = createSignal(null) const [commenting, setCommenting] = createSignal(null) const [opened, setOpened] = createSignal(null) + const [confirmed, setConfirmed] = createSignal>(new Set()) + let confirmTimer: ReturnType | undefined + onCleanup(() => { + clearTimeout(confirmTimer) + const current = open() + if (!current || current.length === 0) return + const large = largeDiffs() + if (large.size === 0) return + const filtered = current.filter((f) => !large.has(f)) + if (filtered.length === current.length) return + props.onOpenChange?.(filtered) + }) const open = () => props.open ?? store.open const diffStyle = () => props.diffStyle ?? (props.split ? "split" : "unified") const hasDiffs = () => props.diffs.length > 0 - const handleChange = (open: string[]) => { - props.onOpenChange?.(open) + const largeDiffs = createMemo(() => { + const map = new Map() + for (const d of props.diffs) { + if (isLargeDiff(d)) map.set(d.file, diffLines(d)) + } + return map + }) + + const handleChange = (next: string[]) => { + const current = open() ?? [] + const added = next.filter((f) => !current.includes(f)) + const needsConfirm = added.filter((f) => largeDiffs().has(f) && !confirmed().has(f)) + + if (needsConfirm.length > 0) { + clearTimeout(confirmTimer) + setConfirmed((prev: Set) => { + const s = new Set(prev) + for (const f of needsConfirm) s.add(f) + return s + }) + confirmTimer = setTimeout(() => setConfirmed(new Set()), 3000) + const filtered = next.filter((f) => !needsConfirm.includes(f)) + if (filtered.length !== current.length || filtered.some((f, i) => f !== current[i])) { + props.onOpenChange?.(filtered) + if (props.open !== undefined) return + setStore("open", filtered) + } + return + } + + clearTimeout(confirmTimer) + props.onOpenChange?.(next) if (props.open !== undefined) return - setStore("open", open) + setStore("open", next) } const handleExpandOrCollapseAll = () => { - const next = open().length > 0 ? [] : props.diffs.map((d) => d.file) + const next = open().length > 0 ? [] : props.diffs.filter((d) => !isLargeDiff(d)).map((d) => d.file) handleChange(next) } @@ -537,6 +589,20 @@ export const SessionReview = (props: SessionReviewProps) => {
+ + + {i18n.t("ui.sessionReview.largeDiff.clickToExpand", { + lines: String(diffLines(diff)), + })} + + + + + {i18n.t("ui.sessionReview.largeDiff.label", { + lines: String(diffLines(diff)), + })} + + diff --git a/packages/ui/src/i18n/ar.ts b/packages/ui/src/i18n/ar.ts index 7ee17e2e0102..d82d05685e12 100644 --- a/packages/ui/src/i18n/ar.ts +++ b/packages/ui/src/i18n/ar.ts @@ -8,6 +8,8 @@ export const dict = { "ui.sessionReview.change.added": "مضاف", "ui.sessionReview.change.removed": "محذوف", "ui.sessionReview.change.modified": "معدل", + "ui.sessionReview.largeDiff.label": "{{lines}} سطر", + "ui.sessionReview.largeDiff.clickToExpand": "{{lines}} سطر — انقر مرة أخرى للتوسيع", "ui.lineComment.label.prefix": "تعليق على ", "ui.lineComment.label.suffix": "", diff --git a/packages/ui/src/i18n/br.ts b/packages/ui/src/i18n/br.ts index 6d7449d8457d..494d7efbf823 100644 --- a/packages/ui/src/i18n/br.ts +++ b/packages/ui/src/i18n/br.ts @@ -8,6 +8,8 @@ export const dict = { "ui.sessionReview.change.added": "Adicionado", "ui.sessionReview.change.removed": "Removido", "ui.sessionReview.change.modified": "Modificado", + "ui.sessionReview.largeDiff.label": "{{lines}} linhas", + "ui.sessionReview.largeDiff.clickToExpand": "{{lines}} linhas — clique novamente para expandir", "ui.lineComment.label.prefix": "Comentar em ", "ui.lineComment.label.suffix": "", diff --git a/packages/ui/src/i18n/bs.ts b/packages/ui/src/i18n/bs.ts index 24e4c12068ee..42aaca7f2dc0 100644 --- a/packages/ui/src/i18n/bs.ts +++ b/packages/ui/src/i18n/bs.ts @@ -12,6 +12,8 @@ export const dict = { "ui.sessionReview.change.added": "Dodano", "ui.sessionReview.change.removed": "Uklonjeno", "ui.sessionReview.change.modified": "Izmijenjeno", + "ui.sessionReview.largeDiff.label": "{{lines}} linija", + "ui.sessionReview.largeDiff.clickToExpand": "{{lines}} linija — kliknite ponovo za proširenje", "ui.lineComment.label.prefix": "Komentar na ", "ui.lineComment.label.suffix": "", diff --git a/packages/ui/src/i18n/da.ts b/packages/ui/src/i18n/da.ts index 218f3b26a494..af78d69fd33c 100644 --- a/packages/ui/src/i18n/da.ts +++ b/packages/ui/src/i18n/da.ts @@ -9,6 +9,8 @@ export const dict = { "ui.sessionReview.change.added": "Tilføjet", "ui.sessionReview.change.removed": "Fjernet", "ui.sessionReview.change.modified": "Ændret", + "ui.sessionReview.largeDiff.label": "{{lines}} linjer", + "ui.sessionReview.largeDiff.clickToExpand": "{{lines}} linjer — klik igen for at udvide", "ui.lineComment.label.prefix": "Kommenter på ", "ui.lineComment.label.suffix": "", "ui.lineComment.editorLabel.prefix": "Kommenterer på ", diff --git a/packages/ui/src/i18n/de.ts b/packages/ui/src/i18n/de.ts index 921a12c99675..a6d1406d877d 100644 --- a/packages/ui/src/i18n/de.ts +++ b/packages/ui/src/i18n/de.ts @@ -13,6 +13,8 @@ export const dict = { "ui.sessionReview.change.added": "Hinzugefügt", "ui.sessionReview.change.removed": "Entfernt", "ui.sessionReview.change.modified": "Geändert", + "ui.sessionReview.largeDiff.label": "{{lines}} Zeilen", + "ui.sessionReview.largeDiff.clickToExpand": "{{lines}} Zeilen — erneut klicken zum Aufklappen", "ui.lineComment.label.prefix": "Kommentar zu ", "ui.lineComment.label.suffix": "", "ui.lineComment.editorLabel.prefix": "Kommentiere ", diff --git a/packages/ui/src/i18n/en.ts b/packages/ui/src/i18n/en.ts index 631bc660a65d..d8ba733befde 100644 --- a/packages/ui/src/i18n/en.ts +++ b/packages/ui/src/i18n/en.ts @@ -8,6 +8,8 @@ export const dict = { "ui.sessionReview.change.added": "Added", "ui.sessionReview.change.removed": "Removed", "ui.sessionReview.change.modified": "Modified", + "ui.sessionReview.largeDiff.label": "{{lines}} lines", + "ui.sessionReview.largeDiff.clickToExpand": "{{lines}} lines — click again to expand", "ui.lineComment.label.prefix": "Comment on ", "ui.lineComment.label.suffix": "", diff --git a/packages/ui/src/i18n/es.ts b/packages/ui/src/i18n/es.ts index 4fd921b606b1..203b1fcece3e 100644 --- a/packages/ui/src/i18n/es.ts +++ b/packages/ui/src/i18n/es.ts @@ -8,6 +8,8 @@ export const dict = { "ui.sessionReview.change.added": "Añadido", "ui.sessionReview.change.removed": "Eliminado", "ui.sessionReview.change.modified": "Modificado", + "ui.sessionReview.largeDiff.label": "{{lines}} líneas", + "ui.sessionReview.largeDiff.clickToExpand": "{{lines}} líneas — clic de nuevo para expandir", "ui.lineComment.label.prefix": "Comentar en ", "ui.lineComment.label.suffix": "", diff --git a/packages/ui/src/i18n/fr.ts b/packages/ui/src/i18n/fr.ts index 537d01bba941..44b70c9d339d 100644 --- a/packages/ui/src/i18n/fr.ts +++ b/packages/ui/src/i18n/fr.ts @@ -8,6 +8,8 @@ export const dict = { "ui.sessionReview.change.added": "Ajouté", "ui.sessionReview.change.removed": "Supprimé", "ui.sessionReview.change.modified": "Modifié", + "ui.sessionReview.largeDiff.label": "{{lines}} lignes", + "ui.sessionReview.largeDiff.clickToExpand": "{{lines}} lignes — cliquez à nouveau pour développer", "ui.lineComment.label.prefix": "Commenter sur ", "ui.lineComment.label.suffix": "", diff --git a/packages/ui/src/i18n/ja.ts b/packages/ui/src/i18n/ja.ts index 6086070bdb2c..0f1d65e9e288 100644 --- a/packages/ui/src/i18n/ja.ts +++ b/packages/ui/src/i18n/ja.ts @@ -9,6 +9,8 @@ export const dict = { "ui.sessionReview.change.added": "追加", "ui.sessionReview.change.removed": "削除", "ui.sessionReview.change.modified": "変更", + "ui.sessionReview.largeDiff.label": "{{lines}} 行", + "ui.sessionReview.largeDiff.clickToExpand": "{{lines}} 行 — もう一度クリックで展開", "ui.lineComment.label.prefix": "", "ui.lineComment.label.suffix": "へのコメント", "ui.lineComment.editorLabel.prefix": "", diff --git a/packages/ui/src/i18n/ko.ts b/packages/ui/src/i18n/ko.ts index fd394dbb7b52..6cd8935f130f 100644 --- a/packages/ui/src/i18n/ko.ts +++ b/packages/ui/src/i18n/ko.ts @@ -8,6 +8,8 @@ export const dict = { "ui.sessionReview.change.added": "추가됨", "ui.sessionReview.change.removed": "삭제됨", "ui.sessionReview.change.modified": "수정됨", + "ui.sessionReview.largeDiff.label": "{{lines}}줄", + "ui.sessionReview.largeDiff.clickToExpand": "{{lines}}줄 — 다시 클릭하여 펼치기", "ui.lineComment.label.prefix": "", "ui.lineComment.label.suffix": "에 댓글 달기", diff --git a/packages/ui/src/i18n/no.ts b/packages/ui/src/i18n/no.ts index dcb353614d30..68d87f5d3707 100644 --- a/packages/ui/src/i18n/no.ts +++ b/packages/ui/src/i18n/no.ts @@ -11,6 +11,8 @@ export const dict: Record = { "ui.sessionReview.change.added": "Lagt til", "ui.sessionReview.change.removed": "Fjernet", "ui.sessionReview.change.modified": "Endret", + "ui.sessionReview.largeDiff.label": "{{lines}} linjer", + "ui.sessionReview.largeDiff.clickToExpand": "{{lines}} linjer — klikk igjen for å utvide", "ui.lineComment.label.prefix": "Kommenter på ", "ui.lineComment.label.suffix": "", diff --git a/packages/ui/src/i18n/pl.ts b/packages/ui/src/i18n/pl.ts index fb10debbb92d..3437b402289b 100644 --- a/packages/ui/src/i18n/pl.ts +++ b/packages/ui/src/i18n/pl.ts @@ -9,6 +9,8 @@ export const dict = { "ui.sessionReview.change.added": "Dodano", "ui.sessionReview.change.removed": "Usunięto", "ui.sessionReview.change.modified": "Zmodyfikowano", + "ui.sessionReview.largeDiff.label": "{{lines}} linii", + "ui.sessionReview.largeDiff.clickToExpand": "{{lines}} linii — kliknij ponownie, aby rozwinąć", "ui.lineComment.label.prefix": "Komentarz do ", "ui.lineComment.label.suffix": "", "ui.lineComment.editorLabel.prefix": "Komentowanie: ", diff --git a/packages/ui/src/i18n/ru.ts b/packages/ui/src/i18n/ru.ts index 417fe0ce8bfe..2dba51338ce9 100644 --- a/packages/ui/src/i18n/ru.ts +++ b/packages/ui/src/i18n/ru.ts @@ -9,6 +9,8 @@ export const dict = { "ui.sessionReview.change.added": "Добавлено", "ui.sessionReview.change.removed": "Удалено", "ui.sessionReview.change.modified": "Изменено", + "ui.sessionReview.largeDiff.label": "{{lines}} строк", + "ui.sessionReview.largeDiff.clickToExpand": "{{lines}} строк — нажмите ещё раз для раскрытия", "ui.lineComment.label.prefix": "Комментарий к ", "ui.lineComment.label.suffix": "", "ui.lineComment.editorLabel.prefix": "Комментирование: ", diff --git a/packages/ui/src/i18n/th.ts b/packages/ui/src/i18n/th.ts index 68bb0d733d99..5b1f6d8395a0 100644 --- a/packages/ui/src/i18n/th.ts +++ b/packages/ui/src/i18n/th.ts @@ -8,6 +8,8 @@ export const dict = { "ui.sessionReview.change.added": "เพิ่ม", "ui.sessionReview.change.removed": "ลบ", "ui.sessionReview.change.modified": "แก้ไข", + "ui.sessionReview.largeDiff.label": "{{lines}} บรรทัด", + "ui.sessionReview.largeDiff.clickToExpand": "{{lines}} บรรทัด — คลิกอีกครั้งเพื่อขยาย", "ui.lineComment.label.prefix": "แสดงความคิดเห็นบน ", "ui.lineComment.label.suffix": "", diff --git a/packages/ui/src/i18n/zh.ts b/packages/ui/src/i18n/zh.ts index 53beeb1e4f0f..f5c91e2c12d2 100644 --- a/packages/ui/src/i18n/zh.ts +++ b/packages/ui/src/i18n/zh.ts @@ -12,6 +12,8 @@ export const dict = { "ui.sessionReview.change.added": "已添加", "ui.sessionReview.change.removed": "已移除", "ui.sessionReview.change.modified": "已修改", + "ui.sessionReview.largeDiff.label": "{{lines}} 行", + "ui.sessionReview.largeDiff.clickToExpand": "{{lines}} 行 — 再次点击展开", "ui.lineComment.label.prefix": "评论 ", "ui.lineComment.label.suffix": "", diff --git a/packages/ui/src/i18n/zht.ts b/packages/ui/src/i18n/zht.ts index 1449b0530ac1..8aee1a22afca 100644 --- a/packages/ui/src/i18n/zht.ts +++ b/packages/ui/src/i18n/zht.ts @@ -12,6 +12,8 @@ export const dict = { "ui.sessionReview.change.added": "已新增", "ui.sessionReview.change.removed": "已移除", "ui.sessionReview.change.modified": "已修改", + "ui.sessionReview.largeDiff.label": "{{lines}} 行", + "ui.sessionReview.largeDiff.clickToExpand": "{{lines}} 行 — 再次點擊展開", "ui.lineComment.label.prefix": "評論 ", "ui.lineComment.label.suffix": "",