Skip to content

feat(composer): replace Apply view diff with @pierre/diffs#2394

Open
caezium wants to merge 5 commits into
logancyang:masterfrom
caezium:feat/diff-improvements
Open

feat(composer): replace Apply view diff with @pierre/diffs#2394
caezium wants to merge 5 commits into
logancyang:masterfrom
caezium:feat/diff-improvements

Conversation

@caezium

@caezium caezium commented May 12, 2026

Copy link
Copy Markdown

Summary

Swap the bespoke blocks-based diff in the Apply view for @pierre/diffs (Shiki-backed), themed against Obsidian's CSS variables so it inherits the user's editor look.

The previous renderer rebuilt its own line/word diff UI from scratch (~600 lines of SideBySideBlock / SplitBlock / per-block accept-reject state). Pierre handles all of that — including markdown-aware syntax highlighting via Shiki — and the result reads as native Obsidian text rather than a separate pierre-dark pane.

Before / After

Before After
before-1 after-1
before-2 after-2
before-3 after-3
before-4 after-4
no split view after-5

What changed

New renderer (src/components/composer/PierreRenderer.tsx):

  • Wraps Pierre's PatchDiff with createPatch + a custom Shiki theme ("obsidian") registered once at module load.
  • The theme maps every syntax token (token-keyword, token-string, token-link, …) to an Obsidian text variable (--text-normal, --text-accent, --text-muted), so code blocks, headings, links, and bold inside the diff render in the user's editor colors — not Pierre's stock red/orange/cyan.
  • disableWorkerPool (Obsidian's renderer process is single-Electron-window, no need to spawn Shiki workers).
  • overflow: "wrap" so long prose lines wrap instead of horizontally truncating in side-by-side.

CSS theming (src/styles/tailwind.css → .copilot-pierre-view):

  • Overrides --diffs-{addition,deletion,modified}-color-override to --text-normal — changed-line text stays the normal Obsidian color; the diff signal comes from background wash + inline emphasis spans only.
  • Comprehensive --diffs-* overrides for backgrounds (--background-modifier-success-rgb / error-rgb at 0.22 alpha for line wash, 0.55 for inline emphasis), gutters, line numbers, hover, selection, font family, line-height, and decoration bar.
  • Container border + header strip themed to --background-secondary.

ApplyView.tsx simplification:

  • Drops ~600 lines of SideBySideBlock / SplitBlock / per-block accept-reject. Pierre doesn't model per-block decisions, so accept writes newText verbatim — same UX as the previous "accept all" path.
  • The existing viewMode toggle (split / side-by-side) now drives Pierre's diffStyle prop and persists via the diffViewMode setting.
  • Floating Accept/Reject pill is centered within the pane (full-width absolute row + tw-flex tw-justify-center, pointer-events split so empty space doesn't swallow clicks on the diff). The previous tw-fixed + tw-left-1/2 anchored to the viewport and put the pill's left edge — not its center — at the 50% line.

Misc:

  • Drop the dead ApplyViewState.simple prop (set by editFile but never read by any consumer).
  • Add a __mocks__/pierre-diffs-react.js stub + two moduleNameMapper entries in jest.config.js — Pierre uses ESM subpath exports that ts-jest's resolver doesn't follow, and tests touching the ApplyView → PierreRenderer import chain (e.g. ComposerTools.test.ts) would otherwise crash at module load. Tests don't assert on Pierre rendering, so the stub returns an empty <div>.

Tradeoffs

  • Bundle size.
  • Per-block accept/reject is gone. Pierre is a code-review-style renderer, not an interactive merge UI. Accept writes the full proposed text. In practice the prior per-block UI in the composer was rarely used — the model typically proposes one cohesive change and the user accepts or rejects the whole thing.

@zeroliu

zeroliu commented May 12, 2026

Copy link
Copy Markdown
Collaborator

This is so amazing. Huge upgrade from our bespoke UI. Using the design tokens is a huge win! The code is well organized. I will consider using it to help with the v4 diff view as well. @logancyang can we assign agent review and consider merging it when the review looks good? I manually went through the change and it LGTM.

Fantastic work @caezium! 🥳

@logancyang

Copy link
Copy Markdown
Owner

@caezium Thanks for the PR! Great stuff!

I've tried a bit and seems the pure additions are not highlighted green, only the edits are red/green. Is this something we can customize? For example, these line additions in the first image are hard to see since only the line number is green but not the text.
SCR-20260512-krma

SCR-20260512-krpt

@wenzhengjiang

Copy link
Copy Markdown
Collaborator

@caezium Thank you! LGTM.

@logancyang I can see the highlights for pure additions, but I agree they should be more visible. @caezium can you make them stand out more?

Screenshot 2026-05-13 at 9 53 24

caezium added a commit to caezium/obsidian-copilot that referenced this pull request May 13, 2026
Pure-add and pure-delete rows in the Apply view were too subtle on most
themes — the line wash was sourced from --background-modifier-success/
error-rgb at 22% alpha, which is the theme's tuned subtle-affordance
color and washed out almost entirely on dark themes. Reviewer feedback
on PR logancyang#2394 flagged that pure-addition rows showed only a colored gutter
bar while the line body looked uncolored.

Changes:
- Reroute the line wash to the vivid --color-{green,red}-rgb palette
  tokens and bump alpha 0.22 → 0.40. Gives an unambiguous colored band
  across the whole row at a glance, comparable to GitHub's intensity.
- Override --diffs-{addition,deletion}-color-override to --color-green /
  --color-red so the text in pure-changed rows reads in color too. The
  --diffs-modified-color-override stays at --text-normal so paired-edit
  rows (which already have inline-emphasis highlights) keep neutral text
  underneath their token-level wash.
- Inline emphasis stays at 0.55 alpha — still ~1.4x the new line wash,
  so changed tokens punch through the surrounding row on paired edits.
@caezium caezium force-pushed the feat/diff-improvements branch from 299c761 to 9ea603e Compare May 13, 2026 03:43
@caezium

caezium commented May 13, 2026

Copy link
Copy Markdown
Author

https://diffs.com/ has more info on this

I pinned the Accept/Reject bar so it stays put when you scroll, tidy up the header, and changed pure additions and deletions colors.
Also forgot to push this but just did: collapsible per-line accept/reject if you want to override individual lines before applying. One click to accept the whole thing, one more click to pick lines.

To tweak the diff look later: all the colors live in src/styles/tailwind.css under .copilot-pierre-view as CSS variable overrides (--diffs-bg-addition-override, --diffs-bg-deletion-override, text colors, gutter colors, etc.).

Renders across light/dark mode and a few different themes:

idk why in light mode copilot looks like this, maybe custom css on my setup

@wenzhengjiang wenzhengjiang left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! @caezium Can you resolve the merge conflicts?

@logancyang

logancyang commented May 17, 2026

Copy link
Copy Markdown
Owner

@caezium looks great! BTW I've updated the merge requirements to enforce signed commits for security reasons. Could you please follow the CONTRIBUTING.md instructions and set it up if you haven't already? Also please resolve the merge conflicts and I will merge. Brilliant work!

@EL-File4138

Copy link
Copy Markdown

Would like to chime in to point out that due to transitively pulling in Shiki’s full bundled language registry plus inlined Oniguruma WASM as a dependency, this change causes the plugin to be massively bloated by ~10 MB - a 3x increase over the whole functionality of Copilot for only a better edit preview experience. Would definitely recommend considering swapping out the rendering backend to avoid such unnecessary bloat.

@caezium caezium force-pushed the feat/diff-improvements branch from 608cc64 to b8cb69f Compare May 21, 2026 13:37
@caezium

caezium commented May 22, 2026

Copy link
Copy Markdown
Author

@logancyang Rebased on master and re-signed all 4 commits with SSH.

@caezium

caezium commented May 22, 2026

Copy link
Copy Markdown
Author

@EL-File4138 @pierre/diffs/dist/highlighter/languages/resolveLanguage.js doing a static import { bundledLanguages } from "shiki", which makes esbuild pull in the entire @shikijs/langs tree (~9.8MB on its own). Fix is pre-registering markdown via Pierre's registerCustomLanguage. Should drop main.js from ~15MB to ~3–5MB

@caezium

caezium commented May 22, 2026

Copy link
Copy Markdown
Author

and also: to anyone wanting to push the diff UI further: https://diffs.com/ has live demos of features this PR is not using yet, the package exposes a lot more than what's wired up here. Per-line accept/reject (hover-revealed), hunk-level controls, custom gutter buttons, scroll-sync between split panes - are all available, you can choose how you want it to look like

@caezium caezium force-pushed the feat/diff-improvements branch from b8cb69f to 5285171 Compare May 29, 2026 06:09
caezium added 4 commits June 2, 2026 03:47
Swap the bespoke blocks-based diff in ApplyView for @pierre/diffs'
PatchDiff component, themed to render in Obsidian's CSS variables.

Renderer:
- New PierreRenderer wraps PatchDiff with createPatch + a custom Shiki
  theme ("obsidian") that maps every syntax token to Obsidian text
  variables, so markdown headings, bold, links, and code render in the
  user's editor colors instead of Pierre's stock red/orange/cyan palette.
- disableWorkerPool — Obsidian's renderer process already runs in a
  single Electron window; spawning Shiki workers there is wasted.
- overflow: "wrap" so long prose lines don't horizontally truncate in
  side-by-side view.

CSS theming (`.copilot-pierre-view` in tailwind.css):
- Override --diffs-{addition,deletion,modified}-color-override to
  --text-normal so changed-line TEXT reads as normal Obsidian text;
  diff signal comes from background wash + inline emphasis only.
- Comprehensive --diffs-* overrides for bg, gutter, font, line-height,
  hover, selection, line numbers, decoration bar, Shiki token colors.
- Container border + header strip themed to --background-secondary.

ApplyView:
- Drops ~600 lines of SideBySideBlock / SplitBlock / per-block
  accept-reject state — Pierre doesn't model per-block decisions, so
  accept writes newText verbatim.
- Existing viewMode toggle (split / side-by-side) drives Pierre's
  diffStyle prop and persists via the diffViewMode setting.
- Floating Accept/Reject pill is centered within the pane via a
  full-width absolute row + flex-justify-center (instead of viewport-
  anchored tw-fixed + tw-left-1/2, which put the pill's left edge —
  not its center — at the 50% line and drifted off-center with
  sidebars open). Pointer-events split so empty space alongside the
  pill doesn't swallow clicks on the diff below.

Misc cleanup:
- Drop dead `ApplyViewState.simple` prop — was set by editFile but
  never read by any consumer.
- Jest moduleNameMapper + __mocks__/pierre-diffs-react.js stub so
  ts-jest can resolve Pierre's ESM subpath exports in tests (tests
  don't exercise Pierre rendering, just need the import to compile).
The floating bar scrolled offscreen because the React root div had no
height — set it to fill the leaf via inline style. Header gets a soft
secondary band, mono path, and +N/−M line counts.
Wash sourced from --background-modifier-{success,error}-rgb at 0.22 was
a tuned-subtle theme color that washed out on most dark themes. Switch
to the vivid --color-{green,red} palette at 0.40 alpha, and override
--diffs-{addition,deletion}-color-override to green/red so the text in
pure-changed rows reads in color too.
Collapsible per-line decisions panel beneath the Pierre diff — click the
header to override individual added/removed lines before applying. The
common case (accept everything) stays one click via the floating bar.
@caezium caezium force-pushed the feat/diff-improvements branch from 5285171 to 189a14f Compare June 2, 2026 10:47
Upstream added stricter ESLint rules between this PR's base and current
master:

- `no-restricted-syntax` now bans bare `createRoot` from
  `react-dom/client`; standalone React roots must use `createPluginRoot`
  so `useApp()` is available in descendants (PR logancyang#2466).
- `obsidianmd/no-static-styles-assignment` bans `element.style.cssText`;
  use Tailwind classes instead.
- `@typescript-eslint/no-unsafe-member-access` now flags `.message` on
  `unknown`/`any` caught errors — narrow with `instanceof Error` first.
- `@typescript-eslint/no-unnecessary-type-assertion` flags `as Decision`
  in spots where TS already infers the literal correctly.

Net effect on the diff renderer: no behavior change, just imports and
small reshuffles.
@caezium

caezium commented Jun 7, 2026

Copy link
Copy Markdown
Author

@logancyang

@logancyang

Copy link
Copy Markdown
Owner

@caezium thanks for following up on this. We are heads down on V4 preview right now and the Copilot plugin is going to be revamped. We will revisit this PR once v4 is ready. Thanks again for the contribution!

@logancyang logancyang added the hold Blocked by other dependencies label Jun 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

hold Blocked by other dependencies

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants