* 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>
* fix: Before/After creation options appear in menu when no permission on parent
closes#12624
* fix: Manager of root document sees non-functional Before/After create options
Before/After only gated on the team-level createDocument ability, so a user
with document-level manager access (but no collection access) saw the options
yet hit a backend rejection. Gate on the actual sibling location instead,
mirroring authorizeDocumentCreate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
The position prop is omitted at several call sites (App, Settings,
Shared sidebars) and relied on the top default for inset-titlebar
padding. Make the prop optional and restore the default so the lint
rule stays satisfied without changing runtime behavior.
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>
* Allow dragging documents from list views into the sidebar
Previously the react-dnd provider was scoped to the sidebar, so only
sidebar rows could be dragged. This lifts the DndProvider up to the
authenticated layout so both the main content and the sidebar share a
single drag-and-drop context, and makes DocumentListItem a drag source.
Now any document in search results or paginated lists (Home, Drafts,
Collections, etc.) can be dragged into the sidebar to move it between
collections, reparent it under another document, star it, or archive it
— reusing the existing sidebar drop targets.
* Make the whole Starred section a drop target to star documents
Previously the only "create star" drop targets in the Starred section
were the thin cursors between items, so dragging a document onto the
section header or a starred row showed the drop cursor but did nothing.
Wrap the section in a catch-all drop target (mirroring the Archive
section) so dropping anywhere in Starred stars the document, while the
precise inter-item cursors still control ordering. A didDrop guard on
useDropToCreateStar prevents the catch-all from double-starring when a
nested cursor already handled the drop, and the hover highlight uses a
shallow isOver check so it only lights up when not over a nested target.
* Let document list drag ghost follow the cursor
The sidebar drag placeholder tethers the ghost near its starting x so it
stays aligned with the sidebar during reordering. When a drag starts out
in the main content (a document list item), that clamp pinned the ghost
to a narrow band, making it look stuck in a small area.
Thread a constrainToSidebar flag through the drag item (true for sidebar
drags, false for document list drags) and let the placeholder follow the
cursor freely when the drag originated outside the sidebar.
* Clarify constrainToSidebar JSDoc to match placeholder behavior
The placeholder treats an unset flag as tethered (constrainToSidebar
!== false), so external drags must set it explicitly to false rather
than leaving it unset. Update the comment to reflect that.
* css
* Render filter options as drawer popover on mobile
Filter options on the search page (and other FilterOptions consumers)
previously rendered as a Radix dropdown on all viewports. On mobile this
now renders as a bottom-sheet Drawer, matching the popover style already
used by context menus.
https://claude.ai/code/session_01MSjTD67PWfGbwgNA5FFoSH
* Fix filter drawer search input overlapping first option on mobile
The Input wrapper uses flex: 0 (a 0% basis), which collapsed the search
input inside the drawer's flex column so its content painted over the
first list item. Use flex: none to retain the input's natural height.
---------
Co-authored-by: Claude <noreply@anthropic.com>
Collection title/icon editing was gated by `isEditRoute && separateEditMode`,
which meant that in the default inline editing mode (separateEditMode off) the
title and icon were never editable inline — even though the collection
description was. This diverged from documents and from the collection
description editor.
Align the Header editing gate with documents (DataLoader) and the Overview
description editor: `isEditRoute || !separateEditMode`, so title and icon are
seamlessly editable inline whenever the user has update permission.
Co-authored-by: Claude <noreply@anthropic.com>
When /oauthClients.info returns an AuthorizationError, ApiClient logs
the user out and clears auth.team. The subsequent re-render of the
Authorize component hit the strict useCurrentTeam() and threw before
the error UI could render. Make the inner hook tolerate a missing team
and fold it into the existing error branch.
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* fix: Allow reordering subdocuments with document-only access
When a user has "Manage" (or any move-eligible) permission on a parent
document but no access to its collection, the sidebar drop cursors were
hidden because they gated on collection.isManualSort, and the move
handler bailed out because it built the payload from collection.id.
Fall back to the document's own collectionId and the move policy so the
reorder UX works for sourced document memberships.
* fix: Structure not refetched
parentDocumentId not provided
* 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: Update JSON importer to use zip streaming, new importer flow
* chore: Drop teamId from import urlId collision check and remove unused internal-id scaffolding
urlId is globally unique on Document/Collection so the team scope was wrong.
Also removes leftover internal-id generation in JSONAPIImportTask that was
never used in task input/output.
* Restore classes used upstream
* Initial plan
* Support double-click submit in document explorer
* Remove test
* Fix double-click submit in document explorer
Single click now sets the selection instead of toggling it, so the two
clicks preceding a dblclick no longer flicker the selection on/off.
Submit handlers accept the node directly to avoid the stale-state race
across the click sequence, and button onClick handlers are wrapped so
the synthetic MouseEvent isn't passed in as the path argument.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* PR feedback
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Tom Moor <tom@getoutline.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>