Skip to content

[editor] Beta 1.3#749

Draft
amadeus wants to merge 185 commits into
mainfrom
beta-1.3
Draft

[editor] Beta 1.3#749
amadeus wants to merge 185 commits into
mainfrom
beta-1.3

Conversation

@amadeus

@amadeus amadeus commented May 29, 2026

Copy link
Copy Markdown
Member

Used to track editor beta versions. Will be periodically rebased on main.

@vercel

vercel Bot commented May 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
pierre-docs-diffs Ready Ready Preview Jul 18, 2026 4:56am
pierre-docs-diffshub Ready Ready Preview Jul 18, 2026 4:56am
pierre-docs-trees Ready Ready Preview Jul 18, 2026 4:56am
pierrejs-diff-demo Ready Ready Preview Jul 18, 2026 4:56am
pierrejs-docs Ready Ready Preview Jul 18, 2026 4:56am

Request Review

@amadeus amadeus mentioned this pull request May 29, 2026
15 tasks
necolas and others added 28 commits July 11, 2026 12:51
Reproduce:
1. Open an editable diff with a marker close to the lower-right edge of
   the visible code viewport.
2. Hover the marker until its message popup opens.
3. Scroll the editor while keeping the popup open.

Before this change, the marker popup keeps the side chosen at hover time
and its horizontal clamp is based on the code column rather than the
visible scroll viewport, so it can stay clipped or overflow the edge.

Marker popups duplicated the selection-action placement branch, and the
shared popover manager only treated selection-action as the active
popover. Route marker popups through PopoverManager.choosePlacement,
track hysteresis state per popover kind, and expose viewport left/right
bounds to the shared popover CSS clamp.
1. Open an editable file with a marker on a long line.
2. Scroll the code pane horizontally until the marked text sits near the
   sticky line-number gutter.
3. Hover the marked text.

Before this change, the shared popover left clamp could use only the
horizontal scroll viewport plus padding, so marker and selection popovers
could overlap the sticky gutter after horizontal scroll. Include the
current gutter width in the scrolled left clamp and cover the marker case
in the browser fixture.
1. Open the diffs editor with a line containing `変数\tvalue`.
2. Place the caret after `value`, select across the tab, or enable
   wrapping near the tab stop.
3. Observe the caret, selection block, or wrap decision drift left
   because the tab after `変数` is measured from two logical columns
   instead of the glyphs' rendered width.

Measure tabs by accumulating rendered pixel width between tab
characters and advancing to the next pixel tab stop. Keep the ASCII
column fast path for plain ASCII text, and remove the stale tab-to-
spaces expansion helper.

Add coverage with a fake canvas where CJK glyphs are 2ch wide,
including the case where `変数` lands exactly on a tab stop and the tab
advances a full stop.
* feat(diffs): Add edit-session region skeleton for diff hunks

While an editor is attached to a FileDiff, hunk updates now preserve
the current hunk structure instead of recomputing it per keystroke:
each hunk is a frozen region, edits re-diff only the region they land
in, a fully reverted region persists as a context-only hunk, edits
spanning regions merge them, and edits inside an unchanged gap
synthesize a new region. The real recompute runs once on genuine
session exit, so post-session state is unchanged.

This is groundwork for edit mode preserving collapsed unchanged
regions; with expandUnchanged still forced during editing, rendering
behavior is unchanged.

Details:

- New pure module `utils/editSessionHunks.ts` holds the region math
  (window mapping, region re-diff with zero context, expansion-key
  remapping, exit recompute) built on the exported updateDiffHunks
  helpers.
- Line-count edit passes run updateRenderCache with dense, stale
  indexed dirty lines before applyDocumentChange. A new explicit
  `lineCountChangeInFlight` flag threads from the editor through the
  host so the session path defers hunk work to the document rebuild,
  which scans a per-session line snapshot instead of the contaminated
  live array. Deferred background-tokenize passes keep running region
  updates from their (current) changed indexes.
- A same-line-count gap edit changes the rendered row set, which the
  debounced refresh cannot express; the host escalates to a deferred
  full re-render, and VirtualizedFileDiff also invalidates its layout
  caches.
- The editor detach closure now reports whether it is a virtualized
  recycle or a genuine session end. Session hunks and a dirty marker
  on the shared FileDiffMetadata survive recycling; CodeView finishes
  sessions whose instance was already released, and FileDiff.render
  self-heals session-shaped metadata reused after a teardown.

* feat(diffs): Make the editor DOM layer collapse-aware

Prepare the editor for diffs whose collapsed unchanged regions stay
collapsed during editing: rendered rows can then be a sparse subset of
document lines, which several editor code paths assumed were dense.
With expansion still forced during editing, behavior is unchanged.

- New visibility oracle: `isAdditionLineRenderable` in
  virtualDiffLayout derives from hunks plus expansion state (the same
  inputs as iterateOverDiff), exposed to the editor as the optional
  `DiffsEditableComponent.isLineRenderable`. FileDiff implements it;
  plain files leave it unset and every line stays renderable.
- `#rerender` filters dirty lines through the oracle, scans the row
  patch loop from child index 0 (a sparse row can sit at a smaller
  index than its dense position, and overshooting appended duplicate
  rows), and only creates new rows beyond the last rendered row.
- `#applyChange` only widens the render window toward a caret whose
  line can actually gain a row; `#scrollToLine` refuses to park its
  retry state on a collapsed line, which previously became a
  non-terminating scroll loop against separator geometry.
- `__syncRenderView` stamps hunk separator rows contenteditable=false
  in both diff styles, alongside annotation and deleted rows.
- `FileDiff.syncRenderViewToEditor` converts the stored render range
  (rendered-row units for the windowed AST pipeline) into the
  document-line units the editor consumes; the two only coincide while
  everything renders. The RenderRange type documents both readings.

* feat(diffs): Add fold-skip caret motion and reveal-on-jump for collapsed regions

Give the editor code-fold semantics over a diff's collapsed unchanged
regions, ahead of edit mode keeping them collapsed:

- Arrow up/down (and shift-extended selection) skip over a collapsed
  region atomically to the nearest renderable line, staying put when
  nothing renders in that direction. The skip target comes from a
  single hunk-metadata walk (getNearestRenderableAdditionLine), not a
  per-line probe across the gap.
- Jumps that must land inside a collapsed region — setSelections,
  search-match navigation, cmd+d/find-next, cmd+Home/End, undo/redo
  caret restores — expand the region minimally first via the new
  `revealLine` host API: one deterministic bump from the nearest gap
  edge sized to reach the target plus the normal expansion step,
  routed through expandHunk so CodeView's deferred expansion staging
  applies. Search counts still include hidden matches; navigating to
  one reveals it.
- VirtualizedFileDiff's line-visibility oracle now accounts for staged
  pendingExpansions so the caret-scroll retry can park while a reveal
  waits for the next layout consume.

Behavior is unchanged while editing still forces expandUnchanged.

* feat(diffs): Keep hunk separator expansion interactive during editing

Separator expand buttons keep working while an editor is attached:
their pointer wiring was already independent of the edit-mode gutter
lockdown and only unreachable because no separators rendered during
editing. With collapse live mid-session, a click reveals the gap, the
revealed context lines are immediately editable (a gap edit
synthesizes a session region), and the expansion re-render rebuilds
its AST from diff.additionLines — which the session paths keep current
every pass, so live edits survive the render-cache clear.

Covered by jsdom tests on the standalone collapse-during-edit vector:
a real separator button click mid-edit, then typing and undoing inside
the newly revealed context.

* feat(diffs): Stop forcing expandUnchanged while editing a diff

Entering edit mode on a diff no longer expands every collapsed
unchanged region. Collapsed regions stay collapsed: the caret skips
over them like code folds, jumps that must land inside one expand it
minimally, separator expand buttons keep working, and the layout no
longer jumps when editing starts or ends. Edits re-diff within the
current hunk regions and a genuine session end restores the normal
recompute (all built up in the preceding commits).

- Editor.edit drops expandUnchanged from its option normalization —
  from the destructuring too, so the setOptions fallback (which
  replaces options wholesale) can no longer strip a host's own
  expandUnchanged when another forced option triggers it.
- CodeView's edit-forced option facade stops serving
  expandUnchanged: true for edited diff items; the key rejoins the
  plain pass-through options.
- The React contentEditable option merge stops injecting
  expandUnchanged: true.
- Editors need full file contents, so attaching to a partial diff now
  triggers loadDiffFiles hydration directly (previously the forced
  expansion render did it as a side effect).
- The genuine session-end recompute now drives layout invalidation
  explicitly (estimated-height reset plus a virtualizer height
  reconcile); the expandUnchanged option flip that used to invalidate
  layout at exit is gone. CodeView applies the same invalidation when
  it reaps a session whose instance was released.
- Docs: the Editor guide describes the fold behavior, and the demo SSR
  preloads drop the baked-in expandUnchanged (mockData.generated.ts
  regenerated).

* feat(diffs): Preserve expansion state best-effort across session exit

Expanding collapsed context while editing (separator clicks or
reveal-on-jump) no longer resets when the session ends: expanded gap
edges are snapshotted as old-side line ranges before the exit
recompute — old-side coordinates are the ones edits cannot move — and
remapped onto the recomputed hunks afterward. A preserved range that
still touches a gap's start or end edge restores that edge's
expansion; ranges whose gap disappeared just collapse.

The exit flow consolidates into FileDiff.completeEditSession
(marker-guarded, idempotent), shared by the live editor-detach path
and CodeView's reap of sessions whose instance virtualization already
released. The virtualized escalation now reports the layout change to
its virtualizer so item tops re-derive from the changed heights.

* test(diffs): Add browser E2E coverage for editing with collapsed regions

New edit-collapsed fixture: a 60-line diff whose unchanged gap
collapses, driven in both split and unified view (the unified content
column rebuilds via innerHTML on edits, a named regression risk).
Cases: the gap stays collapsed on edit entry, a reverted hunk persists
as context until session exit and then collapses away, arrow keys
fold-skip the gap, search navigation reveals a hidden match, a
separator expand click mid-edit keeps the viewport and accepts typing
into the revealed context, a programmatic replace into the gap
materializes a rendered hunk without scrolling, and undo/redo
round-trip with collapse live.

* Add a new HTML editable example to /playground

This is useful for different types of diff editing scenarios

* feat(diffs): semantic diffs, at home

Basically an attempt at doing better line matching on stuff

* review(fix): Fix the arrow keys issue brought up by codex

* fix(bug): Fix a strange edge cases with diffs that include intermixed blank lines

Unclear if this will create some knock on effects that make it harder to
manage in the future...
* [diffs/edit] Add missed commands

* fix

* fix LANGUAGE_COMMENT_CONFIGS
* [diffs/edit] docs: Add "Compared with Monaco Editor and CodeMirror" section

* Small content updates

* fix(docs): Prerender the edit mode demo on the server

Load the Editor docs page: the live edit mode demo starts blank and
its highlighted code only pops in once the client attaches the
editor, so the surface visibly flashes after the page loads.

The demo rendered <File> with no prerendered HTML, so the server
only emitted an empty container and all highlighting happened client
side. Preload the demo file with preloadFile and hydrate <File> from
the resulting prerenderedHTML instead. The preload options mirror the
state the editor enforces in contentEditable mode (token transformer
on; gutter, line selection, and hover off) so the SSR markup matches
the post-attach render and doesn't re-highlight. The demo now ships
highlighted in the initial HTML.

* Remove comparison, add paragraph near top for purpose

---------

Co-authored-by: Mark Otto <markdotto@gmail.com>
fix issue with selection action, add another selection action for edges
* [diffs/edit] Add legacy-editor test suites harvested from monaco and CodeMirror

Ports behavioral edge-case scenarios from microsoft/vscode (MIT) and
CodeMirror 6 (MIT) test suites into two new bun:test suites under
packages/diffs/test/. 139 tests, including 22 test.failing entries that
encode real bugs found during the port (frozen EditStack entries vs
non-history edits, undo coalescing, surrogate-pair edit boundaries,
CRLF line metadata, and others). Test-only change; fixes land separately.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [diffs/edit] Add atom-legacy-tests suite

Third legacy-editor harvest, from atom/text-buffer and atom/superstring
(both MIT): 56 tests covering search/replace semantics (regex capture
expansion, zero-width-match termination, replace-past-own-output,
anchored patterns on CRLF), selection remapping under partial-overlap
replacements, soft-wrap boundary behavior, history invariant fuzzing,
and malformed-position handling. Includes 8 test.failing entries
encoding newly found bugs, the worst being: an edit with a NaN position
component silently replaces the entire document, and soft-wrap vertical
caret motion can land inside a surrogate pair.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [diffs/edit] Guard ICU-dependent word-segmentation pins with skipIf

Intl.Segmenter's isWordLike classification varies across ICU builds
(underscore joining, bare-digit and Han word-ness differ between
engines/platforms). Two segmenter-side expansion pins now probe the
runtime's segmentation of their own fixture and skip visibly on
divergent ICUs instead of failing. Both tests still execute on our dev
and CI environments, which agree with the pinned segmentation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [diffs/edit] Extend ICU skipIf guards to all segmenter-side CJK pins

Completes the previous commit: the Chinese, Japanese, and Latin-Katakana
double-click expansion pins depend on the same ICU-variable isWordLike
classification (dictionary-based CJK segmentation is the most
engine-dependent of all), so they get the same runtime probe treatment
via a shared wordLikeSegments helper. Delete-word tests are untouched —
the regex classifier is deterministic.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [diffs/edit] Consolidate editor-derived tests into the main suite

Per team feedback: the three editor-named test directories are gone and
all 195 tests now live in the existing per-domain files
(editorPieceTable, editorTextDocument, editorApplyEdits, editorEditStack,
editorSelection, editorSearchPanel, editorWrapCaretPosition).
Migration was audited name-by-name before deletion: every test present
exactly once, all 30 test.failing known-bug pins and 5 ICU skipIf guards
preserved. Comments no longer reference other editors; the conventions,
known-bug inventory, coverage-gap additions, and a single provenance
note moved into test/README.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [diffs/edit] Pin block-indent blank-line bug as test.failing

resolveIndentEdits inserts the indent unit at column 0 of every line a
multi-line selection touches, including empty and whitespace-only lines,
injecting trailing whitespace on lines the user never typed on. Outdent
round-trips it away, bounding the damage to the indented state. Brings
the pinned known-bug count to 31.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [diffs/edit] Drop the wiped-region graceful-undo pin

We don't model programmatic edits as a class distinct from local edits,
so the scenario's premise (a dead history entry after an untracked edit
replaced its region) doesn't match the intended design. The remaining
five history-across-untracked-edits pins stand; pinned known-bug count
returns to 30.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [diffs/edit] Reframe history-mixing pins around programmatic==local equivalence

Design decision: edits applied with updateHistory=false are not a
semantic class distinct from local edits — a mixed sequence must behave
exactly like the same sequence applied all-tracked. The old pins
asserted rebasing semantics (untracked edits surviving undo); all are
flipped to equivalence assertions whose expected values mirror an
all-tracked reference run: undo-to-exhaustion restores the original
text, redo-to-exhaustion the final text. The wiped-region test returns
under the new model, and the coalescing-across-an-untracked-edit pin
flipped from passing to failing because its outcome also deviates from
the all-tracked timeline. Cluster: 7 failing pins; suite total 32.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* sick

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ion (#986)

Math.min/max pass NaN straight through and fractional indexes break the
integer-keyed line-offset lookup, so a NaN (or fractional) line/character
resolved to a NaN offset and the degenerate edit range swallowed the
entire document — an insert with one malformed component silently erased
everything else. normalizePosition now sanitizes before clamping: NaN
and -Infinity act as 0, fractions floor, +Infinity clamps to the max.
Every position-taking API funnels through it (offsetAt, resolveEdit),
so all edit paths are covered. Flips the two malformed-position pins to
permanent regression tests with exact expectations; pinned known-bug
count drops to 30.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Refactor edit resolution logic to improve surrogate pair handling

* fix codex
* [diffs/edit] Fix soft-wrap vertical caret motion

* fix
* Rename popup to popover to be more consistent

* fix codex

* fix e2e testing

---------

Co-authored-by: Je Xia <i@jex.me>
* fix typo

* fix editStack coalescing bugs.
* fix(diffs): Keep editor active line separate from selected lines

Moving the caret in edit mode reused the selected-lines state, which could
replace an existing line selection and disturb its callbacks or gutter
utility.

Track the editor active line independently and derive one visual highlight
where selected lines take precedence. Preserve both states across diff row
refreshes so each retains its latest value.

* fix(diffs): Preserve interactions in edit mode

Entering Edit mode with line selection, gutter utilities, or hover
highlighting enabled currently turns those interactions off and clears
selected lines.

Preserve the configured interaction options while enabling only the
token transformer required by the editor. Keep editor active-line
styling separate from selected-line styling, protect gutter utility
gestures from editor selection handlers, and exercise the behavior
across playground views.

* docs(diffs): Remove obsolete editor option overrides

Update editor documentation and live examples to stop disabling gutter
utilities, line selection, and hover highlighting in edit mode.

Keep useTokenTransformer enabled in prerendered examples because the
editor still requires it, and refresh the generated homepage fixture.

* fix(diffs): Keep text and line selections independent

Dragging a gutter utility now updates the line range and selection
lifecycle even when ordinary line selection is disabled. Ordinary gutter
drags remain disabled, and callbacks retain the completed gesture range
when utility code changes the current selection.

Preserve editor text selections during gutter-owned gestures and render
the cursor line independently from selected diff lines. Retry incomplete
renders, avoid rewriting unchanged ranges, and keep both states visible
across supported themes.

* docs(diffs): Document gutter utility selection callbacks

Clarify that gutter utility gestures emit selection lifecycle callbacks
even when ordinary line selection is disabled.

* fix(docs): Respect editor active lines in Agent UI

Added and deleted gutter numbers kept their base colors on the editor
cursor line because the Agent UI override excluded the obsolete
`data-active` attribute.

Exclude `data-editor-active-line` instead and update the generated demo
snapshot so the editor active-line styling can apply.

* Fix playground with new selection states

* fix(diffs): Compose editor active lines with diff states

Select a line in edit mode and move the cursor onto it: the theme current-line background can obscure the selection and addition or deletion color.

Resolve decoration, diff, selection, editor active, and hover colors as ordered layers. Derive active emphasis from the resolved semantic color, retain theme borders as a fallback cue, and prevent gutter numbers from receiving the selection mix twice.

* fix(diffs): Flush line state after virtualized rerenders

Edit inside a collapsed diff region, then move the caret or update the
line selection: virtualized views can keep showing the previous line
state after rebuilding their rows.

Flush deferred selected and active-line writes after virtualized
managers bind to the rebuilt DOM, and cover the region-changing render
path.
* [diffs/edit] improve picectable performance

50 latency in milliseconds; two warmups and nine measured samples per workload. Lower is better.

| Workload | Baseline | Round 1 | Round 2 | Round 3 | Improvement |
|---|---:|---:|---:|---:|---:|
| Construct large document | 0.549 | 0.503 | 0.548 | 0.501 | +8.7% |
| Sequential typing | 1.649 | 1.547 | 1.706 | 1.555 | +5.7% |
| Scattered inserts | 3.726 | 3.481 | 3.120 | 3.406 | +8.6% |
| Append after fragmentation | 1.003 | 0.995 | 0.629 | 0.784 | +21.9% |
| Batch replacements | 3.302 | 3.139 | 3.042 | 3.254 | +1.5% |
| Position round-trips | 11.630 | 11.497 | 10.759 | 12.414 | −6.7% |
| Fragmented slices | 1.721 | 1.467 | 1.586 | 1.688 | +1.9% |
| Whole-word search | 7.783 | 7.355 | 7.464 | 2.081 | +73.3% |
| **Combined p50** | **31.364** | **29.983** | **28.854** | **25.683** | **+18.1%** |

* another improvement

* another improvement
@socket-security

socket-security Bot commented Jul 18, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​pierre/​icons@​0.7.1771007897100

View full report

* First follow up task

An attempt to ensure the edited diff better matches the fully parsed
diff at the end. It's not perfect, and I think it can still break in
certain ways, but I think it's a bit better.

Will need more testing.

I am a little worried about this code, it touches a lot of things, but
it does appear to fix things... It's one of those things that I don't
fully understand, which scares me a little.

* [diffs] Normalize zero-count hunk sides in merge conflict parser

createHunkBuilder seeds each side's start as "next line to consume"
(lines.length + 1), but getHunkSideStartBoundary assumes the unified
N,0 convention where a zero-count side's start names the line the hunk
applies after. A whole-file conflict with an empty side (no context
anywhere) finalized with that start unnormalized, inventing a phantom
collapsedBefore row and inflating split/unified totals by one.

Normalize each zero-count side down by one in finalizeActiveHunk so
emitted hunks match what parsePatchFiles produces from real git
patches (headers now read +0,0/-0,0 like git). Also corrects the
benchmark checksum documented in the file header, which was stale
before this branch (the parser output hasn't changed: parent, HEAD,
and this fix all produce 31314920).

Addresses codex review feedback on #978.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Quick sidequest to help prevent UnresolvedFile's from being editable

* [diffs/edit] Fix stale highlighting after edit-session line-count edits

The cached per-line addition HAST is looked up by line index, but
applyDocumentChange only truncated/appended it — a mid-document insert
or delete never shifted the surviving entries, so rows hidden behind
collapsed context rendered another line's stale tokens once the edit
session ended and they became visible (a duplicated line in the diff).

- realignAdditionHastLines shifts the cached entries to their new
  indexes on a line-count edit (common prefix/suffix preserved, the
  changed window plain-filled for the editor's next tokenize pass).
- refreshHighlightedResult re-highlights the final diff in the
  background at genuine session exit, so plain-filled and shifted lines
  regain color. The current result keeps rendering until the fresh one
  lands, so no interim paint drops highlighting (no un-highlighted
  flash). Called from completeEditSession and the self-heal backstop.
- applyDocumentChange now throws on a partial diff instead of
  half-applying the line rewrite, matching updateRenderCache's guard.

Adds regression tests covering the realign shift and the exit
re-highlight restoring colors without an interim un-highlighted render.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [diffs] Keep bottom-of-run anchor when a blank slide stops at a hunk edge

slideBlankBoundaryBlocksUp moves a pure blank insert/delete to the top
of its blank run so the change anchors to the content above it (the
caret line after an Enter). When the run outruns the diff context, the
slide was instead stopped by the hunk's leading edge — a context-window
cut, not the run's top — parking the block at an arbitrary mid-run
position that abuts a hunk separator and anchors to nothing.

Make the slide all-or-nothing: skip it when it would consume the hunk's
entire leading context, keeping the diff library's bottom-of-run anchor,
which sits against the content below the run (and matches what plain
git diff shows for the same files). Slides that come to rest beneath
in-hunk content are unchanged. The rule runs inside the shared canonical
parse, so mid-session and exit renderings agree by construction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* PR feedback fix

* Fix playground to not force initial re-renders

* Defensive diffs fix for initialization highlighting flash

* Fix commenting in playground

* only use server rendered code on initial render

* Actually server render the query string params

* Fix css hydration bug with annotations

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants