* Add "+:emoji:" reaction shorthand to comment form
Typing a comment that consists solely of a leading "+" followed by a
single emoji now adds that emoji as a reaction to the comment above,
instead of posting a new reply — mirroring the Slack shorthand.
https://claude.ai/code/session_01RSiUiEFLBaRF6YBfPNPiX6
* Move parseReactionShorthand into editor/lib/emoji
https://claude.ai/code/session_01RSiUiEFLBaRF6YBfPNPiX6
* Open emoji menu when colon is preceded by a plus
The suggestion menu's trigger boundary excluded "+", so typing "+:" never
opened the emoji menu — preventing the "+:emoji:" reaction shorthand from
being typed. Add a configurable `precededBy` option to the Suggestion
extension and set it to "+" for the emoji menu.
https://claude.ai/code/session_01RSiUiEFLBaRF6YBfPNPiX6
* Always allow "+" before suggestion trigger
Simplify by adding "+" to the trigger boundary for all suggestion menus
rather than making it a per-menu option. This lets the "+:emoji:" reaction
shorthand open the emoji menu.
https://claude.ai/code/session_01RSiUiEFLBaRF6YBfPNPiX6
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Fix editor find freezing on long documents
In-document search (Ctrl+F) blocked the UI for several seconds while
typing in long documents. Two compounding causes:
- The find command ran a full-document search and highlight rebuild on
every keystroke. Debounce it so typing stays responsive; the input
value still updates immediately and pending searches are flushed when
navigating between matches.
- search() de-duplicated matches with an O(n) scan of all prior results
per match, making a common term that matches many times quadratic.
Track seen positions in a Set for constant-time lookups.
* Skip redundant search highlight rebuilds, lower debounce to 100ms
The highlight plugin rebuilt every match's DOM range via domAtPos on
every editor view update while a search was active, forcing synchronous
layout on cursor moves, selection changes, and collaboration cursors.
Track the built ranges and, when the result set is unchanged, only
rebuild when they are actually stale — a referenced node has detached or
some matches were not yet resolved to ranges. isConnected checks are
cheap property reads with no layout, versus domAtPos which forces
reflow, so this is strictly less work than before and skips entirely in
the common case where all matches are resolved and connected.
Also lower the find debounce from 250ms to 100ms for snappier feedback.
* Shorten highlight rebuild comment
* PR feedback
---------
Co-authored-by: Claude <noreply@anthropic.com>
* wip
* Mobile support
* Address review feedback on inline menu
- Mark selection-restore transaction as not added to history
- Only open desktop inline menu when an anchor is available
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* refactor: introduce declarative menu registry for selection toolbar
Replace the hard-coded if-else chain in SelectionToolbar with a
priority-based menu registry system. Extensions can now declare
selection toolbar menus via `selectionToolbarMenus()`, following the
same pattern as `commands()` and `keys()`.
Key changes:
- Add SelectionContext interface computed once per toolbar render
- Add SelectionToolbarMenuDescriptor for declarative menu registration
- Add selectionToolbarMenus() to Extension base class
- Add buildSelectionContext() utility to eliminate repeated state queries
- ExtensionManager collects and sorts menus from all extensions
- SelectionToolbarExtension registers all 10 existing menus
- All menu functions now accept SelectionContext instead of raw state
- SelectionToolbar uses registry lookup instead of if-else chain
https://claude.ai/code/session_01MRyFysrGM9d8NhbAs7nrtU
* refactor: import t directly from i18next in menu functions
Remove the `t: TFunction` parameter from all menu functions and the
`SelectionToolbarMenuDescriptor.getItems` signature. Each menu file
now imports `t` directly from i18next, matching the pattern used
throughout the rest of the codebase (e.g. Image.tsx, Link.tsx).
https://claude.ai/code/session_01MRyFysrGM9d8NhbAs7nrtU
* refactor: move divider menu into HorizontalRule node extension
The divider selection toolbar menu is now declared via
selectionToolbarMenus() on the HorizontalRule node class, co-locating
the menu with the node that owns it. Delete the standalone
app/editor/menus/divider.tsx file and remove the entry from
SelectionToolbarExtension.
This is the first menu migrated from the centralized toolbar extension
to an individual node extension, demonstrating the pattern for the
remaining menus.
https://claude.ai/code/session_01MRyFysrGM9d8NhbAs7nrtU
* refactor: check readOnly in matches predicate for divider menu
https://claude.ai/code/session_01MRyFysrGM9d8NhbAs7nrtU
---------
Co-authored-by: Claude <noreply@anthropic.com>
The MutationObserver callback could throw an uncaught RangeError when
posAtDOM returned a position outside the document, since the existing
try/catch only wrapped the observer setup, not the async callback.
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* chore: Replace lodash with es-toolkit
Migrate all direct lodash imports to es-toolkit/compat for a smaller,
faster, lodash-compatible utility library. Transitive lodash usage from
other packages remains unchanged.
* fix: Restore isPlainObject semantics in CanCan policy
The lodash migration aliased `isObject` to `lodash/isPlainObject` and
the codemod incorrectly mapped the local name to es-toolkit's `isObject`,
which also returns true for arrays and functions. This caused condition
objects in policy definitions to be skipped, breaking authorization
checks across the codebase.
* fix: Restore unicode-aware length counting in validators
es-toolkit/compat's size() returns string.length, while lodash's _.size()
counts unicode code points. Switch to [...value].length to preserve the
previous behavior so multi-byte characters like emoji count as one.
Plumbed `dictionary` props through editor components, menus, extensions,
and nodes. Replaces with `useTranslation()` in React contexts and direct
`t` imports from i18next elsewhere.
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* chore: Update editor generics
* fix: Address PR review on editor generics
- Restore null-guard on Link click handler so anchors aren't inert when no onClickLink is provided
- Mark onClickLink optional in LinkOptions and openLink command to match runtime
- Remove dead `collapsed` option from HeadingOptions
- Make ToggleBlock dictionary optional and restore optional-chained access for server-side schema instantiation
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* chore: promote no-explicit-any from warn to error and resolve violations
Upgrades the oxlint rule severity and removes all 40 existing
`no-explicit-any` warnings across the codebase. Most call sites gained
proper types (SharedEditor refs, JSONNode/JSONMark for ProseMirror JSON
walking, DocumentsStore, dd-trace `Span` parameter inference, prosemirror
Fragment public API in place of internal `(fragment as any).content`).
A few load-bearing `any` uses were preserved with scoped disable
comments where changing the type would cascade widely (Sequelize JSONB
columns on `Event`, the `withTracing` higher-order function generic,
`Extension.options` consumed by many subclasses, dd-trace's `req`
patching).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds missing stable dependencies (e.g. `t`, prop callbacks, store refs,
`setFocusedCommentId`) and removes unnecessary ones across hooks where the
fix is straightforward. For the two MobX-observed `.orderedData` deps in
`History.tsx`, keeps the original deps and silences the false positive
with `eslint-disable-next-line` so the memos still recompute when the
underlying observable arrays change.
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* chore: resolve remaining unbound-method lint warnings
Apply targeted fixes per call pattern: arrow wrappers when passing a
method as a callback, arrow-function class fields when the method
doesn't depend on `this`, and `.bind()` when capturing for later
invocation.
Also replaces the rfc6902 hasOwnProperty re-export with a small wrapper
function so callers don't reference an unbound prototype method.
* chore: memoize history.goBack callbacks
Stable identity prevents Button re-renders and avoids re-subscribing
the global keydown handler in RegisterKeyDown when the parent renders.
* chore: clear mechanical lint warnings
Drops 44 oxlint warnings (559 → 515) by fixing easy mechanical rules
across the codebase: no-useless-escape, no-duplicate-type-constituents,
no-redundant-type-constituents, no-unused-expressions,
no-meaningless-void-operator, require-array-sort-compare, await-thenable.
* chore: drop callback parameter from useCallback deps
The `open` argument is a parameter of the callback, not a closed-over
variable, so it doesn't belong in the deps array.
* chore: promote cleared lint rules to errors
Promotes the rules cleared in this PR from warn to error so future
violations fail the lint:
- no-unused-expressions
- typescript/await-thenable
- typescript/no-duplicate-type-constituents
- typescript/no-meaningless-void-operator
- typescript/require-array-sort-compare
Removes the override that suppressed no-useless-escape on source
files (the global rule is already error) and fixes the 21 escape
violations that this exposed in regex character classes and template
literals.
* chore: address PR review feedback
- usePinnedDocuments: simplify UrlId to plain string instead of the
intersection trick.
- PlantUML embed: move - to end of character class so it's a literal
hyphen rather than a range operator.
- checkboxes: type token params as Token | undefined to match the
actual call sites that pass tokens[index - 2] etc.
* A11y improvements
* fix: Accessibility improvements for sidebar, layout, and emoji icons
- Add role="main" to content area and role="contentinfo" to right sidebar
- Add aria-expanded to sidebar Disclosure toggle button
- Add nav landmark with aria-label to shared sidebar navigation
- Render SidebarLink as button instead of div when no link target
- Hide decorative emoji icons from screen readers (aria-hidden)
- Add aria-hidden to EmojiIcon SVG element
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: Restore PopoverTrigger in FindAndReplace, add role to span
PopoverAnchor broke the find/replace popover. Revert to PopoverTrigger
and instead add role="button" and aria-label to the span so ARIA
attributes from Radix are valid on the element.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: Sidebar button styling
* fix: Use semantic list elements for References document list
Change the References list container from div to ul and wrap each
ReferenceListItem in an li element for proper screen reader semantics.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: Address PR review feedback for accessibility changes
- Heading buttons: switch from mousedown to click for keyboard access
- Heading fold: add aria-expanded attribute
- FindAndReplace: use real button element instead of span with role
- SidebarLink: branch render to avoid passing NavLink props to button
- Right sidebar: use role=complementary instead of contentinfo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: Use translation hook for FindAndReplace, revert anchor click handler
- Use t() for aria-label in FindAndReplace button
- Revert heading anchor from click back to mousedown to avoid side effects
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: Add ts-expect-error for styled NavLink overload mismatch
The spread props on the NavLink branch cause a TypeScript overload
mismatch that was previously suppressed. Re-add the suppression.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* chore: Address code quality findings
* Round 2, quality findings
* fix: Add fallback for MediaQueryList.addEventListener in test env
The jsdom test environment doesn't implement addEventListener on
MediaQueryList. Prefer addEventListener but fall back to the
deprecated addListener when unavailable.
* Use CSS highlights instead of editor decorations when available
* Fix scroll target for non-HTML elements and refresh highlights on toggle fold
- Use `Element` instead of `HTMLElement` for scroll target so SVG/MathML
elements are handled correctly
- Bump highlight generation on toggle fold/unfold transactions so
newly visible matches get proper highlight ranges
- Cache decoration getter result to avoid redundant mapping
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When navigating to a document from search results, the search term is
highlighted via FindAndReplace but the popover is not open, so there was
no way to dismiss the highlights. This adds an Escape key binding to the
FindAndReplace extension that clears highlights when active.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Adds group sync from external authentication providers, allowing team group memberships to be automatically managed based on provider data on sign-in in the future.