Sync upstream main (August 18) - #140
Merged
Merged
Conversation
…mics#4969) * Fix delayed autofocus cleanup Co-Authored-By: Codex GPT-5 (unknown context) <noreply@openai.com> * Fix hidden-thought cursor tests Co-Authored-By: Codex GPT-5 (unknown context) <noreply@openai.com> --------- Co-authored-by: Codex GPT-5 (unknown context) <noreply@openai.com>
…speeds (cybersemics#4854) The test relied on thought b being created in a later millisecond than a, c, and d, but the reducers run synchronously, so on a fast machine all four share a timestamp and Created sort falls back to alphabetical order, putting b first. Advance fake timers between the two creation steps so the intended order is guaranteed. Co-authored-by: Claude Fable 5 (200K context) <noreply@anthropic.com>
…4927) New Subthought (next) declared multicursor: { disallow: true }, so selecting several thoughts and invoking it only produced an error alert. The action is per-cursor and composes fine when executed once per selected thought, so declare multicursor: { preventSetCursor: true, clearMulticursor: true } and let the standard multicursor loop drive it. Each selected thought gets its own new empty uncle, rather than collapsing the selection to one insertion per sibling group. preventSetCursor keeps the caret on the empty thought created for the last selected thought, ready to type, which is the command's single-cursor postcondition; the default cursor restore would pull it back to the original thought. clearMulticursor drops the now-stale selection, matching the single-cursor behavior where newThought's own setCursor clears it. canExecute additionally accepts hasMulticursor, so the command is enabled when thoughts are selected but the cursor is missing or on a root thought. A selected root thought has no parent to insert at; the loop's per-iteration setCursor clears the selection, so the per-iteration canExecute check fails for it and it is skipped without blocking the rest of the run. Add a store test suite covering the multiselect behavior: one new empty uncle per selected sibling, selections spanning different parents and depths, a selected root thought skipped rather than blocking, the caret left on the last created empty thought with the multicursor cleared, the single-selection case, and a single undo reverting the whole run. Co-authored-by: Claude Fable 5 (unknown context) <noreply@anthropic.com>
New Grandchild declared multicursor: { disallow: true }, so selecting several
thoughts and invoking it only produced an error alert. The action is per-cursor
and composes fine when executed once per selected thought, so declare
preventSetCursor and clearMulticursor and let the standard multicursor loop
drive it. Each selected thought gets its own new empty grandchild appended
inside its first subthought, rather than the selection collapsing to a single
insertion.
Both options are load-bearing. The action sets the cursor to the new empty
grandchild with the keyboard open, ready to type, so the loop's default cursor
restore would move the caret back to the originally selected thought.
The selection of parent thoughts is stale once the caret is in a new empty
thought, so it is cleared rather than restored.
Add a store test suite covering the multiselect behavior: one new empty
grandchild per selected thought, appending after the first subthought's
existing children, selections at different depths, a selected childless
thought skipped without blocking the rest, the caret left in the last created
grandchild with the multicursor cleared, the single-selection Command Center
flow, and the whole run reverting on a single undo.
Co-authored-by: Claude Fable 5 (unknown context) <noreply@anthropic.com>
New Thought declared multicursor: { filter: 'last-sibling' }, so a multiselect
collapsed to one insertion per sibling group and created a single new thought.
Remove the filter so the standard multicursor loop runs newThought once per
selected thought, creating a new empty sibling after each of them.
clearMulticursor and preventSetCursor are retained and are both load-bearing
for the new semantics: each newThought sets the cursor to the thought it
creates, so preventSetCursor leaves the caret in the last created empty thought
ready to type instead of restoring the pre-command cursor, and clearMulticursor
drops the now-stale selection.
Convert the existing last-sibling test to the new per-selected-thought
semantics and extend the suite: a new empty sibling after each selected thought,
selections spanning parents and depths, a single selected thought, the cursor
landing on the last created thought with the multicursor cleared, single-cursor
behavior unchanged, and a single undo reverting every creation.
Co-authored-by: Claude Fable 5 (unknown context) <noreply@anthropic.com>
New Thought (above) declared multicursor: { filter: 'first-sibling', ... }, so a
multiselect collapsed each sibling group to a single insertion and selecting
several thoughts created far fewer thoughts than were selected. Remove the
filter so the standard multicursor loop creates a new empty thought above each
selected thought, which is what a creation command on a multiselect should do.
exec is newThought({ insertBefore: true }), which inserts a sibling before the
cursor thought. Unlike the subthought commands, selected siblings share an
insertion parent, which is exactly what first-sibling collapsed; each selected
thought now gets its own new thought instead. Insertion positions are computed
per iteration against the already-mutated tree, so the new thoughts interleave
with the selection rather than bunching up.
clearMulticursor and preventSetCursor are both retained and were verified to be
load-bearing under the new semantics: without preventSetCursor the loop restores
the original cursor instead of leaving the caret in the empty thought just
created, and without clearMulticursor the loop re-adds a multicursor on each
originally selected thought, leaving a stale selection behind the new caret.
Convert the existing multicursor test, which pinned the first-sibling collapse,
to the new per-selected-thought semantics, and add coverage for selections
spanning different parents and depths, the resulting cursor position, the
cleared multicursor, and reverting every creation on a single undo.
Co-authored-by: Claude Fable 5 (unknown context) <noreply@anthropic.com>
Bind Context declared multicursor: { disallow: true }, so selecting several
thoughts and invoking it only produced the error alert "Cannot bind multiple
thoughts." Declare multicursor: true and let the standard multicursor loop drive
it once per selected context.
Contexts selected in different context views each get their own binding, which is
the case the disallow declaration blocked outright. Contexts selected within the
same context view overwrite each other, since =bindContextCommand holds a single
context — the last one in document order wins, exactly as if the command were
invoked on each context in turn. Selected thoughts that are not contexts of an
active context view are silent no-ops rather than blockers.
Neither preventSetCursor nor clearMulticursor is needed: nothing moves, and
recomputePath already returns context view paths unchanged (cybersemics#4752), so the
default restore returns the cursor and the selection to the contexts they started
on.
Add a store test suite covering the multiselect behavior: bindings across two
context views, last-wins within one view, equivalence with sequential
single-cursor invocation, both toggle interactions with an existing binding, the
single-selection Command Center flow, the inactive-context-view no-op, skipped
descendants, the restored cursor and multicursors, and a single undo reverting
the whole run.
Co-authored-by: Claude Fable 5 (unknown context) <noreply@anthropic.com>
Extract declared multicursor: { disallow: true }, so selecting several thoughts
and invoking it only produced the error alert "Cannot extract multiple thoughts."
Declare multicursor: false instead, so the command executes on state.cursor and
the selected thoughts stay selected.
Extract takes its input from the browser text selection, of which the document
has exactly one. The extractThought action slices state.cursor's value at that
selection's character offsets, so the offsets are only meaningful for the thought
that owns the selection. A per-cursor loop applies them to unrelated strings:
with "bravo" selected in "alpha bravo" and "charlie delta" and "echo" also
selected, multicursor: true yields "charlita" with the child "e del", and "echo"
with an empty child.
The previous disallow declaration corrupted values the same way whenever exactly
one thought was selected and it was not the thought being edited, since disallow
moved the cursor onto the selected thought and then executed with the edited
thought's offsets.
Add a multicursor test suite covering extraction from the edited thought with
several thoughts selected and with a different thought selected, the preserved
cursor and multiselect, the no-text-selected alert, and a single undo. Add
beforeEach(initStore) to the file so the store is cleared between tests, which
the createTestApp/cleanupTestApp pair alone did not do.
Co-authored-by: Claude Fable 5 (unknown context) <noreply@anthropic.com>
…#4926) Co-authored-by: Claude Fable 5 (unknown context) <noreply@anthropic.com> Co-authored-by: Claude Opus 5 (unknown context) <noreply@anthropic.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
…emics#4938) When thoughts are selected, the cursorBack reducer now replaces the selection with the parent of each selected thought (deduplicated by path) instead of moving the hidden cursor, mirroring cursorForward (cybersemics#4898). Root-level thoughts contribute no parent; if all selected thoughts are at the root, the selection is left unchanged. Expansion is recalculated so the newly selected parents collapse. The command's dispatch gate now also fires when a multicursor exists without a cursor (reachable on touch via long press), and the scroll-to-top convenience is suppressed while thoughts are selected. Desktop Escape still clears the selection before the reducer is reached. Fixes cybersemics#3526 Co-authored-by: Claude Fable 5 (unknown context) <noreply@anthropic.com>
Co-authored-by: Claude Fable 5 (unknown context) <noreply@anthropic.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Resolve TreeCRDT root expectations and include the cursorless Generate Thought test correction from cybersemics#4978. Co-Authored-By: Codex GPT-5 (unknown context) <noreply@openai.com>
Construct the cursorless multiselect directly instead of relying on cursorBack, whose multicursor behavior changed before the test merged.\n\nCo-Authored-By: Codex GPT-5 (unknown context) <noreply@openai.com>
Co-authored-by: Claude Fable 5 (unknown context) <noreply@anthropic.com> Co-authored-by: GitHub Copilot CLI unknown (unknown context) <223556219+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Migrate LottieAnimation to the lottie-react v3 API following https://lottiereact.com/docs/migration: - default Player import → named Lottie import - animationData prop → src - onComplete prop → subscriptions.complete - setSpeed effect → reactive speed prop - webdriver skip path: getDuration(true) + goToAndStop in a conditionally-called useLayoutEffect → seek({ percent: 100 }) in a ready subscription, since v3 loads asynchronously and LottieHandle no longer exposes frame counts Verified in the running app: toolbar icons play to completion, fire complete, and unmount back to the static SVG; the webdriver path seeks straight to the last frame and completes immediately. Co-authored-by: Claude Fable 5 (unknown context) <noreply@anthropic.com>
…mics#4973) No code changes required: the e2e iOS tests only use jest-style matchers (toBe, toEqual), which are unaffected by the v6 element matcher changes. Peer dependencies (@wdio/* ^9.0.0) already satisfied. Co-authored-by: Claude Fable 5 (unknown context) <noreply@anthropic.com>
…s#4975) The effect that calls `onResize({ height: null })` was written as a componentWillUnmount, with a comment asserting its dependencies "should be memoized and not change for the life of the component". That is not true of `onResize`: TreeNode memoizes it on `cliff`, which changes whenever a thought's position in the tree changes. React runs the cleanup before every such re-run, so a thought released its tracked size while it was still mounted. Instrumenting the cleanup and adding a sibling (which changes the preceding thought's cliff) shows the cleanup firing on a live thought, followed by `removeSize` and an immediate `setSize` for the same key: CLEANUP-FIRED |_fHZM6U06I_ej stillInDOM= true removeSize |_fHZM6U06I_ej setSize |_fHZM6U06I_ej 20 The re-add happens because `updateSize`'s dependencies are a superset, so its effect body restores the entry in the same passive-effect flush. The visible behavior is therefore unchanged today. What this removes is the churn — an in-place `delete` on the `sizes` state object followed by a rebuild — and the trap of a cleanup that claims to be unmount-only while running mid-life. Route the call through `useFreshCallback` so the effect's dependency is stable and the cleanup runs only on unmount, while still invoking the latest `onResize`. Re-running the instrumented trace confirms the mid-life fire is gone and all thoughts still release their size on teardown. Co-authored-by: Claude Opus 5 (unknown context) <noreply@anthropic.com>
…emics#4977) useDragLeave shares a module-level hoverCount across every drop target, and debounces a clear of state.hoveringPath when it reaches zero. The effect treated every run as a hover transition: any run that was not a false→true change of isDeepHovering fell into the `else` branch and decremented the shared count. That branch is reached on mount, so any thought mounting mid-drag decremented the count — dropping it to zero and blanking the drop indicator while a target was still hovered. Its cleanup was commented "Cleanup on unmount" but listed four dependencies, so React ran it before every re-run rather than only on unmount. It also never decremented, so a drop target unmounted mid-drag leaked its count and hoveringPath was never cleared. Guard the count on `isDeepHovering !== isCountedRef.current` so only a real enter or leave adjusts it, and move the release into its own effect with empty deps, where it decrements this target's contribution. The empty deps are what make it an unmount handler. Adds hook tests for the two reproduced failures (an unrelated thought mounting mid-hover; a hovered target unmounting), a guard that the count still reaches zero on leave, and documents the counting rule in docs/drag-and-drop.md. Co-authored-by: Claude Opus 5 (unknown context) <noreply@anthropic.com>
Co-authored-by: patbyte <PATBYTE_NOREPLY_EMAIL>
Co-Authored-By: Codex GPT-5 (unknown context) <noreply@openai.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Merge cybersemics/em
mainthroughebdfdef83into Antonovmain. This brings in 20 upstream commits, including the multiselect command series, the delayed-autofocus virtualization fix, the latest New Subthought behavior, VirtualThought/drag-hover fixes, emoji paste handling, and dependency maintenance.Merging this PR will update the head of cybersemics/em#4325 and clear its current upstream merge conflicts.
Conflict resolution
swapParent.ts: keep upstream's deterministic fake-timer test withtry/finallycleanup.newThoughtAbove.ts: keep upstream's expanded per-selection behavior and tests.repeat.ts: adapt the new raw export expectation from Yjs's literal__ROOT__to TreeCRDT's${HOME_TOKEN}.generateThought.ts: retain Antonov's awaitedinitStore()while accepting the cursorless setup correction now merged upstream in cybersemics/em#4978.newUncle.ts: express expected empty thoughts without trailing source whitespace.newSubthought.ts: keep upstream's expanded seven-test per-selection behavior; its expectations already use TreeCRDT's${HOME_TOKEN}.useDragLeave.ts: await Antonov's asynchronousinitStore()in the new upstream tests so TreeCRDT memory storage is initialized before dispatching imports.The upstream
lottie-reactandexpect-webdriveriopackage/lockfile updates are included. Antonov's TreeCRDT dependencies, including@treecrdt/wa-sqlite0.4.2, remain intact, and Yjs remains removed.