Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

Bug: Word diff marks disappear on the new side while the editor is attached

Open
#1,210 5 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
55/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
electron, typescript
Domain
desktop, frontend

Research direction

Start at onDeferTokenize and FileDiff.updateRenderCache, where the report says editor token batches rebuild cached rows on the new side. Trace how diff span marks are represented when editor tokens update the render cache, then reproduce with the supplied VirtualizedFileDiff example using Highlights and Shiki. Done when the new side keeps its word marks through editor attachment and theme changes, with existing marks preserved on both sides.

Written by the indexing model from the issue text.

Description

bug
Prerequisites
Describe the issue

Hi! We're using the diff editor in an Electron app, and we keep losing the word diff marks (data-diff-span) on the new side while the editor is attached. The old side always keeps them, the syntax colors stay, and ending the edit session brings them back.

You can see it on https://diffs.com/edit: click "diff", then "split". The new side loses its marks, and switching back to unified doesn't bring them back.

We tried a lot of combinations, and it comes down to these triggers:

  • Highlights, attaching the editor. The marks go right away with a Virtualizer, or at the next re-render without one (a diffStyle or overflow change, for example). That's the diffs.com case.
  • Shiki or Highlights, a theme change while editing. Either the page's color scheme flips (themeType: 'system') or theme is changed (with a single theme name this one didn't happen every time). setThemeType() doesn't trigger it.

With no editor attached, nothing is lost in any of the cases we tried.

I think it's one cause. In both cases the editor's tokenizer runs a background pass over the whole document (after attach for Highlights, after setTheme for both), and each batch goes through onDeferTokenize to FileDiff.updateRenderCache. That rebuilds the new side's cached rows from the editor's tokens, which have no data-diff-span. Later renders reuse that cache, so the marks stay gone. With a Virtualizer, the editor also repaints the visible rows straight away, which is why it shows at once there.

Expected: the word marks stay while the editor is attached, on both sides.

Reproduction
<style>body { color-scheme: dark } body.light { color-scheme: light }</style>
<div id="scroller" style="height: 2000px; overflow: auto"><div id="diff"></div></div>
import { Virtualizer, VirtualizedFileDiff } from '@pierre/diffs';
import { Editor } from '@pierre/diffs/edit';

const PREFERRED_HIGHLIGHTER = 'highlights'; // or 'shiki-js'
const code = (changed: boolean) =>
  Array.from({ length: 30 }, (_, i) => `export function f${i}(x: number): number {\n  return ${changed && i === 15 ? 'y' : 'x'} + ${i};\n}`).join('\n') +
  `\nconst s = 'foo again${changed ? '170' : ''}';\n`;

const host = document.getElementById('diff')!;
const virtualizer = new Virtualizer();
virtualizer.setup(document.getElementById('scroller')!);
const view = new VirtualizedFileDiff(
  { theme: { dark: 'pierre-dark', light: 'pierre-light' }, preferredHighlighter: PREFERRED_HIGHLIGHTER, lineDiffType: 'word' },
  virtualizer,
);
view.render({ fileContainer: host, oldFile: { name: 'example.ts', contents: code(false) }, newFile: { name: 'example.ts', contents: code(true) } });

const marks = () => {
  const rows = [...host.shadowRoot!.querySelectorAll('[data-diff-span]')].map((mark) => mark.closest('[data-line-type]')?.getAttribute('data-line-type'));
  return `old side ${rows.filter((t) => t === 'change-deletion').length}, new side ${rows.filter((t) => t === 'change-addition').length}`;
};
const settle = () => new Promise((resolve) => setTimeout(resolve, 1000));

await settle();
console.log('1. rendered, read-only:', marks());
const editor = new Editor('file-diff');
const endEditing = editor.edit(view);
await settle();
console.log('2. editor attached:', marks());
document.body.classList.add('light'); // the page's color scheme turns light
await settle();
console.log('3. color scheme flipped while editing:', marks());
endEditing();
await settle();
console.log('4. editing ended:', marks());

What it logs:

highlights:
1. rendered, read-only: old side 2, new side 2
2. editor attached: old side 2, new side 0
3. color scheme flipped while editing: old side 2, new side 0
4. editing ended: old side 2, new side 2

shiki-js:
1. rendered, read-only: old side 2, new side 2
2. editor attached: old side 2, new side 2
3. color scheme flipped while editing: old side 2, new side 0
4. editing ended: old side 2, new side 2

To load themes at all, I had to patch @pierre/theming locally (#1207).

Tested in headless Chromium 156, with @pierre/diffs 1.6.0, @pierre/highlights 0.1.3 and shiki 4.5.0. The theme change case also happens on 1.5.1. We haven't tried the React components, CodeView, worker pools, or Firefox and Safari.

What browser(s) are you seeing the problem on?

Chrome

What version of @pierre/diffs are you using?

1.6.0

Dominant language
TypeScript
Stars
6.2k
Forks
226
Avg merge
1d 10h
Merged PRs (30d)
36

Getting set up

This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from pierrecomputer/pierre

All issues in pierrecomputer/pierre

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.