From ffa22052d012bcbc5492dd6b3f8a786ca7795e27 Mon Sep 17 00:00:00 2001 From: Dinesh Pandiyan Date: Fri, 9 Oct 2026 22:57:10 +1100 Subject: [PATCH] Show captured diff comparison and merge base --- apps/web/src/DiffComparison.tsx | 68 +++++++++++ apps/web/src/DiffWorkspace.tsx | 10 +- apps/web/src/diff-comparison.ts | 74 ++++++++++++ apps/web/src/style.css | 47 +++++++- apps/web/test/browser/local-workspace.spec.ts | 109 ++++++++++++++++++ docs/development.md | 6 + internal/httpapi/multi_test.go | 73 ++++++++++++ ...l-CLXnbU8d.js => angular-html-CIDQm-eY.js} | 2 +- ...-ts-DBfR-Atp.js => angular-ts-B4ds-38r.js} | 2 +- .../{apl-9Rn687tQ.js => apl-DVypL-zf.js} | 2 +- .../{astro-BdYFGcnW.js => astro-bGeAO4kW.js} | 2 +- .../{blade-Dv4XuYG7.js => blade-B05c0efC.js} | 2 +- .../assets/{c-BFOYLRr0.js => c-Lwhg3M9H.js} | 2 +- ...{chapel-C8gtur09.js => chapel-l8OWUclx.js} | 2 +- .../{cobol-B11mWRHr.js => cobol-CXL_QUHm.js} | 2 +- ...{coffee-DaQZRolb.js => coffee-BPKrRks8.js} | 2 +- .../{cpp-lH9GYSm7.js => cpp-CZBvJmDV.js} | 2 +- ...rystal-Ctp3VANd.js => crystal-DqA11r91.js} | 2 +- .../{css-EYEy3Xlb.js => css-CSjz1_r9.js} | 2 +- .../{edge-f80K2VIZ.js => edge-4jY7xOVA.js} | 2 +- ...{elixir-ht5CMTKg.js => elixir-DHDpldHz.js} | 2 +- .../{elm-CIXzGuv_.js => elm-Dq_uJWok.js} | 2 +- .../{erb-BIc8Omep.js => erb-BiWzqYP9.js} | 2 +- ...ase-BdIZdJUH.js => git-rebase-CvcB9fwi.js} | 2 +- ...-js-D-2dfiTn.js => glimmer-js-CvIh8nls.js} | 2 +- ...-ts-BHHXO3rp.js => glimmer-ts-DixgNgy9.js} | 2 +- .../{glsl-BYnWR4Hv.js => glsl-Bj5ASeOh.js} | 2 +- ...raphql-ClvnBbMm.js => graphql-DvPZcZ3Z.js} | 2 +- .../{hack-BCf1gOIX.js => hack-Dx4OVy8Y.js} | 2 +- .../{haml-Bmn5-Yjh.js => haml-uzBRzMtN.js} | 2 +- ...ars-C7MKIi0r.js => handlebars-ebqZjP0F.js} | 2 +- .../{html-rbi5PgIL.js => html-ByNaN8du.js} | 2 +- ...lIm4xpX.js => html-derivative-BUjpj2CT.js} | 2 +- .../{http-B2PGq0eK.js => http-Ebg1jP2J.js} | 2 +- .../{hurl-CqQcMAR7.js => hurl-B8kJM_qX.js} | 2 +- ...{index-DuA5D_ic.css => index-DOSf590M.css} | 2 +- .../{index-Bwvh_IHd.js => index-Q78YNw5L.js} | 18 +-- .../{java-BFOUMeCc.js => java-B1C-j4rj.js} | 2 +- ...ipt-CR5GavSE.js => javascript-DR1Zu0uP.js} | 2 +- .../{jinja-BeguzEd0.js => jinja-DgYMb0l-.js} | 2 +- .../{jison-CCx2qNB9.js => jison-CU_jHZUp.js} | 2 +- .../{json-I28f8KUm.js => json-BqQ5gM55.js} | 2 +- .../{jsx-CrD-Ow3U.js => jsx-CxarOCGA.js} | 2 +- .../{julia-DdSuU0oJ.js => julia-m2LoYmnT.js} | 2 +- .../{just-DxOGI1MU.js => just-C7PsypGW.js} | 2 +- .../{latex-BTFSxgVs.js => latex-BPGRZR2m.js} | 2 +- ...{liquid-WYMJQfxi.js => liquid-CFobLHyB.js} | 2 +- .../{lua-NdL_hgi8.js => lua-DKwEQ0ul.js} | 2 +- .../{marko-BXDrtGB_.js => marko-TXv7srVg.js} | 2 +- .../{mdc-DFT9jeFB.js => mdc-CCoPVKBr.js} | 2 +- .../{nginx-BGimcMtc.js => nginx-ChMU-1eL.js} | 2 +- .../{nim-Boc8nMzF.js => nim-BY7xazis.js} | 2 +- .../{org-IUMfWevQ.js => org-CLM9L_US.js} | 2 +- .../{perl-Dlk0_VJm.js => perl-D4JGn8x_.js} | 2 +- .../{php-BCj8cGx_.js => php-CoXTwNxO.js} | 2 +- .../{pug-DVicUvHI.js => pug-CnkF16JR.js} | 2 +- .../{qml-CAGCQzHi.js => qml-CWeOudAl.js} | 2 +- .../assets/{r-DPCSjHhB.js => r-urK57Tc8.js} | 2 +- .../{razor-DaR4FNlZ.js => razor-BUojin67.js} | 2 +- ...{regexp-DD5bv9lN.js => regexp-BVWXhtCh.js} | 2 +- .../{rst-CwnrqHo7.js => rst-N3BFBej3.js} | 2 +- .../{ruby-DLg3GxA3.js => ruby-DBrYwN-B.js} | 2 +- .../{sas-7kC5yJIB.js => sas-CenQTrAR.js} | 2 +- .../{scss-TPfA2Rl9.js => scss-C0brhb5C.js} | 2 +- ...pt-BxUYTJbB.js => shellscript-Dhv6c2FO.js} | 2 +- ...n-D8Detfs1.js => shellsession-BUJsoHfR.js} | 2 +- .../{soy-DgmP-rkN.js => soy-CxdF8i6l.js} | 2 +- .../{sql-WfIMpFBo.js => sql-60gtioXn.js} | 2 +- .../{stata-BoXU_fVr.js => stata-PjrrmGrU.js} | 2 +- ...alql-BZp4t379.js => surrealql-BnlyV8Y0.js} | 2 +- ...{svelte-D0JzGzh-.js => svelte-Du27rKK9.js} | 2 +- .../{templ-BuDeFWw_.js => templ-DxJ0YqmM.js} | 2 +- .../{tex-hxvblT0F.js => tex-D6sKm-3S.js} | 2 +- ...s-tags-Ca4r_vit.js => ts-tags-D3AKe2lF.js} | 2 +- .../{tsx-B01kvZiV.js => tsx-298_zDAE.js} | 2 +- .../{twig-B7e2eWcZ.js => twig-CLceLD0y.js} | 2 +- ...ipt-DeMv-M_d.js => typescript-CYKC_cj1.js} | 2 +- .../{typst-IzvzibIJ.js => typst-CXDgwzIb.js} | 2 +- .../{vue-BC0QlAe5.js => vue-CFv9Xo_c.js} | 2 +- ...-html-B_B1dAIw.js => vue-html-D7Va0JeM.js} | 2 +- ...-vine-CwdEpNh4.js => vue-vine-DRN3y2o_.js} | 2 +- .../{xml-DpeacbYS.js => xml-CtGj0yrM.js} | 2 +- .../{xsl-xmhovwJb.js => xsl-C7HPMskl.js} | 2 +- .../{yaml-CR2Q4Kv2.js => yaml-DFU58VyP.js} | 2 +- internal/webui/dist/index.html | 4 +- packages/api/openapi.yaml | 4 +- packages/api/src/schema.d.ts | 8 +- 87 files changed, 474 insertions(+), 99 deletions(-) create mode 100644 apps/web/src/DiffComparison.tsx create mode 100644 apps/web/src/diff-comparison.ts rename internal/webui/dist/assets/{angular-html-CLXnbU8d.js => angular-html-CIDQm-eY.js} (99%) rename internal/webui/dist/assets/{angular-ts-DBfR-Atp.js => angular-ts-B4ds-38r.js} (99%) rename internal/webui/dist/assets/{apl-9Rn687tQ.js => apl-DVypL-zf.js} (99%) rename internal/webui/dist/assets/{astro-BdYFGcnW.js => astro-bGeAO4kW.js} (98%) rename internal/webui/dist/assets/{blade-Dv4XuYG7.js => blade-B05c0efC.js} (99%) rename internal/webui/dist/assets/{c-BFOYLRr0.js => c-Lwhg3M9H.js} (99%) rename internal/webui/dist/assets/{chapel-C8gtur09.js => chapel-l8OWUclx.js} (99%) rename internal/webui/dist/assets/{cobol-B11mWRHr.js => cobol-CXL_QUHm.js} (99%) rename internal/webui/dist/assets/{coffee-DaQZRolb.js => coffee-BPKrRks8.js} (99%) rename internal/webui/dist/assets/{cpp-lH9GYSm7.js => cpp-CZBvJmDV.js} (99%) rename internal/webui/dist/assets/{crystal-Ctp3VANd.js => crystal-DqA11r91.js} (99%) rename internal/webui/dist/assets/{css-EYEy3Xlb.js => css-CSjz1_r9.js} (99%) rename internal/webui/dist/assets/{edge-f80K2VIZ.js => edge-4jY7xOVA.js} (94%) rename internal/webui/dist/assets/{elixir-ht5CMTKg.js => elixir-DHDpldHz.js} (99%) rename internal/webui/dist/assets/{elm-CIXzGuv_.js => elm-Dq_uJWok.js} (99%) rename internal/webui/dist/assets/{erb-BIc8Omep.js => erb-BiWzqYP9.js} (94%) rename internal/webui/dist/assets/{git-rebase-BdIZdJUH.js => git-rebase-CvcB9fwi.js} (93%) rename internal/webui/dist/assets/{glimmer-js-D-2dfiTn.js => glimmer-js-CvIh8nls.js} (99%) rename internal/webui/dist/assets/{glimmer-ts-BHHXO3rp.js => glimmer-ts-DixgNgy9.js} (99%) rename internal/webui/dist/assets/{glsl-BYnWR4Hv.js => glsl-Bj5ASeOh.js} (96%) rename internal/webui/dist/assets/{graphql-ClvnBbMm.js => graphql-DvPZcZ3Z.js} (98%) rename internal/webui/dist/assets/{hack-BCf1gOIX.js => hack-Dx4OVy8Y.js} (99%) rename internal/webui/dist/assets/{haml-Bmn5-Yjh.js => haml-uzBRzMtN.js} (98%) rename internal/webui/dist/assets/{handlebars-C7MKIi0r.js => handlebars-ebqZjP0F.js} (98%) rename internal/webui/dist/assets/{html-rbi5PgIL.js => html-ByNaN8du.js} (99%) rename internal/webui/dist/assets/{html-derivative-ClIm4xpX.js => html-derivative-BUjpj2CT.js} (92%) rename internal/webui/dist/assets/{http-B2PGq0eK.js => http-Ebg1jP2J.js} (95%) rename internal/webui/dist/assets/{hurl-CqQcMAR7.js => hurl-B8kJM_qX.js} (96%) rename internal/webui/dist/assets/{index-DuA5D_ic.css => index-DOSf590M.css} (66%) rename internal/webui/dist/assets/{index-Bwvh_IHd.js => index-Q78YNw5L.js} (81%) rename internal/webui/dist/assets/{java-BFOUMeCc.js => java-B1C-j4rj.js} (99%) rename internal/webui/dist/assets/{javascript-CR5GavSE.js => javascript-DR1Zu0uP.js} (99%) rename internal/webui/dist/assets/{jinja-BeguzEd0.js => jinja-DgYMb0l-.js} (98%) rename internal/webui/dist/assets/{jison-CCx2qNB9.js => jison-CU_jHZUp.js} (99%) rename internal/webui/dist/assets/{json-I28f8KUm.js => json-BqQ5gM55.js} (97%) rename internal/webui/dist/assets/{jsx-CrD-Ow3U.js => jsx-CxarOCGA.js} (99%) rename internal/webui/dist/assets/{julia-DdSuU0oJ.js => julia-m2LoYmnT.js} (99%) rename internal/webui/dist/assets/{just-DxOGI1MU.js => just-C7PsypGW.js} (97%) rename internal/webui/dist/assets/{latex-BTFSxgVs.js => latex-BPGRZR2m.js} (99%) rename internal/webui/dist/assets/{liquid-WYMJQfxi.js => liquid-CFobLHyB.js} (98%) rename internal/webui/dist/assets/{lua-NdL_hgi8.js => lua-DKwEQ0ul.js} (99%) rename internal/webui/dist/assets/{marko-BXDrtGB_.js => marko-TXv7srVg.js} (99%) rename internal/webui/dist/assets/{mdc-DFT9jeFB.js => mdc-CCoPVKBr.js} (99%) rename internal/webui/dist/assets/{nginx-BGimcMtc.js => nginx-ChMU-1eL.js} (99%) rename internal/webui/dist/assets/{nim-Boc8nMzF.js => nim-BY7xazis.js} (98%) rename internal/webui/dist/assets/{org-IUMfWevQ.js => org-CLM9L_US.js} (96%) rename internal/webui/dist/assets/{perl-Dlk0_VJm.js => perl-D4JGn8x_.js} (99%) rename internal/webui/dist/assets/{php-BCj8cGx_.js => php-CoXTwNxO.js} (99%) rename internal/webui/dist/assets/{pug-DVicUvHI.js => pug-CnkF16JR.js} (99%) rename internal/webui/dist/assets/{qml-CAGCQzHi.js => qml-CWeOudAl.js} (98%) rename internal/webui/dist/assets/{r-DPCSjHhB.js => r-urK57Tc8.js} (99%) rename internal/webui/dist/assets/{razor-DaR4FNlZ.js => razor-BUojin67.js} (99%) rename internal/webui/dist/assets/{regexp-DD5bv9lN.js => regexp-BVWXhtCh.js} (99%) rename internal/webui/dist/assets/{rst-CwnrqHo7.js => rst-N3BFBej3.js} (96%) rename internal/webui/dist/assets/{ruby-DLg3GxA3.js => ruby-DBrYwN-B.js} (98%) rename internal/webui/dist/assets/{sas-7kC5yJIB.js => sas-CenQTrAR.js} (99%) rename internal/webui/dist/assets/{scss-TPfA2Rl9.js => scss-C0brhb5C.js} (99%) rename internal/webui/dist/assets/{shellscript-BxUYTJbB.js => shellscript-Dhv6c2FO.js} (99%) rename internal/webui/dist/assets/{shellsession-D8Detfs1.js => shellsession-BUJsoHfR.js} (91%) rename internal/webui/dist/assets/{soy-DgmP-rkN.js => soy-CxdF8i6l.js} (99%) rename internal/webui/dist/assets/{sql-WfIMpFBo.js => sql-60gtioXn.js} (99%) rename internal/webui/dist/assets/{stata-BoXU_fVr.js => stata-PjrrmGrU.js} (99%) rename internal/webui/dist/assets/{surrealql-BZp4t379.js => surrealql-BnlyV8Y0.js} (99%) rename internal/webui/dist/assets/{svelte-D0JzGzh-.js => svelte-Du27rKK9.js} (99%) rename internal/webui/dist/assets/{templ-BuDeFWw_.js => templ-DxJ0YqmM.js} (99%) rename internal/webui/dist/assets/{tex-hxvblT0F.js => tex-D6sKm-3S.js} (98%) rename internal/webui/dist/assets/{ts-tags-Ca4r_vit.js => ts-tags-D3AKe2lF.js} (98%) rename internal/webui/dist/assets/{tsx-B01kvZiV.js => tsx-298_zDAE.js} (99%) rename internal/webui/dist/assets/{twig-B7e2eWcZ.js => twig-CLceLD0y.js} (98%) rename internal/webui/dist/assets/{typescript-DeMv-M_d.js => typescript-CYKC_cj1.js} (99%) rename internal/webui/dist/assets/{typst-IzvzibIJ.js => typst-CXDgwzIb.js} (99%) rename internal/webui/dist/assets/{vue-BC0QlAe5.js => vue-CFv9Xo_c.js} (98%) rename internal/webui/dist/assets/{vue-html-B_B1dAIw.js => vue-html-D7Va0JeM.js} (99%) rename internal/webui/dist/assets/{vue-vine-CwdEpNh4.js => vue-vine-DRN3y2o_.js} (99%) rename internal/webui/dist/assets/{xml-DpeacbYS.js => xml-CtGj0yrM.js} (97%) rename internal/webui/dist/assets/{xsl-xmhovwJb.js => xsl-C7HPMskl.js} (95%) rename internal/webui/dist/assets/{yaml-CR2Q4Kv2.js => yaml-DFU58VyP.js} (99%) diff --git a/apps/web/src/DiffComparison.tsx b/apps/web/src/DiffComparison.tsx new file mode 100644 index 00000000..aa11ebcd --- /dev/null +++ b/apps/web/src/DiffComparison.tsx @@ -0,0 +1,68 @@ +import { Popover } from "@base-ui/react/popover"; +import { InfoIcon, XIcon } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { useDiffSource } from "./app-state.tsx"; +import { diffComparison } from "./diff-comparison.ts"; +import { useSession } from "./session-context.tsx"; + +export function DiffComparison() { + const session = useSession(); + const { mode, repository } = useDiffSource(); + const comparison = diffComparison(session, mode, repository?.head); + return ( + + + } + > + {comparison.summary} + Comparison + + + + +
+ Diff comparison + + } + > + +
+ {comparison.description} + {comparison.details.length > 0 && ( +
+ {comparison.details.map(({ label, value }) => ( +
+
{label}
+
{value}
+
+ ))} +
+ )} +
+
+
+
+ ); +} diff --git a/apps/web/src/DiffWorkspace.tsx b/apps/web/src/DiffWorkspace.tsx index f26057c7..aaea1af7 100644 --- a/apps/web/src/DiffWorkspace.tsx +++ b/apps/web/src/DiffWorkspace.tsx @@ -27,6 +27,7 @@ import { import { useAppearance } from "./appearance-context.tsx"; import { CopyPathButton } from "./CopyPathButton.tsx"; import { DiffToolbar } from "./DiffToolbar.tsx"; +import { DiffComparison } from "./DiffComparison.tsx"; import { themesFor } from "./display-options.ts"; import { DraftComment, ReviewCommentCard } from "./review.tsx"; import type { CommentAnnotation } from "./review-model.ts"; @@ -610,13 +611,6 @@ export function DiffWorkspace() { ], ); - const scopeDescription = piped - ? "Command output · fixed snapshot" - : mode === "staged" - ? "HEAD → index" - : mode === "unstaged" - ? "Index → working tree, including untracked files" - : "HEAD → working tree, including untracked files"; const emptyTitle = !repository ? diff.notice ? "Cannot load changes" @@ -765,7 +759,7 @@ export function DiffWorkspace() {