Re-frame the rendered SVG viewBox from a getBBox() measurement taken in
the visible editor rather than the hidden render element, where the
measurement is unreliable on high-DPI/RDP sessions. Bump the cache
namespace so previously mis-sized diagrams are re-rendered.
Returning the unfurl promises without awaiting them inside the try
block meant rejections (e.g. "Entity not found: Issue") escaped the
catch and were reported to error tracking. Await them so they are
caught and returned as a handled { error } result.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix: Remove unused grid snapping from element resizing
Horizontal resizing snapped widths to a 5% grid, which is no longer
desired. Replace the only remaining use of the gridSnap prop (the
minimum-width clamp) with a named constant and drop the prop entirely.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: Remove resize lag by disabling size transition while dragging
The width/height CSS transition on resizable elements existed to smooth
the discrete jumps from grid snapping. With pixel-by-pixel resizing the
element perpetually animates toward a target ~150ms in the future, so it
visibly trails the cursor. Disable the transition while actively dragging
and restore it afterwards so snap-back and collaborative size changes
still animate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: Constrain image resizing to editor edge instead of snapping to natural size
When dragging an element past the editor bounds, the full-width sentinel
forced the width to the natural size. For images narrower than the editor
this snapped them back to their (smaller) natural width at the boundary.
Only use the natural-width sentinel when the image is genuinely wider than
the editor; otherwise constrain to the editor edge.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* PR feedback
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
The FileOperation import association was fetched for every non-public
document but only used when sourceMetadata is present. Move the lookup
inside that branch to eliminate an N+1 query for documents that are not
imports, benefiting every endpoint that presents documents.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
The attachment cleanup loop used findAllInBatches, which advances an
OFFSET each iteration. Because the callback deletes each batch, the
remaining rows shift backwards and the advancing offset skips over them,
leaving attachments that still reference the team. team.destroy() then
failed with attachments_teamId_fkey.
Page from offset 0 until no attachments remain, and remove the now
redundant per-user attachment delete so the loop is the single
authoritative cleanup.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix: Allow service worker to load on custom domains
Add explicit worker-src 'self' so the service worker can register on
team custom domains. Without it, browsers fall back to script-src which
only lists env.URL and env.CDN_URL, blocking /static/sw.js on hosts
like docs.getoutline.com.
* fix: Switch worker-src approach to script-src 'self' for type safety
The @types/koa-helmet definitions don't include workerSrc. Add 'self'
to script-src instead — worker-src falls back to script-src per spec,
and 'self' matches the document origin on custom domains.
* fix: Properly add worker-src directive without script-src widening
Extract the CSP directives to a local variable so workerSrc can be
included despite koa-helmet's outdated type definitions missing it
(the underlying helmet supports it). Also drop @types/koa-helmet
since the package now ships its own (equivalent) types.
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: Normalize IP addresses to avoid validation errors on audit columns
Koa's `ctx.request.ip` can yield values that fail Sequelize's `isIP`
validation (X-Forwarded-For chains, IPv6 zone identifiers, "unknown"
from misconfigured proxies). This drops the IP metadata silently
instead of raising a 500 on Event/User writes.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* test: Cover IP normalization on User setters
Reviewer feedback. Also switches the column-options `set` to TypeScript
get/set accessors — the original approach was shadowed by the class
field declaration and never actually fired, which the new tests would
have caught.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
The onerror handlers in FileHelper passed the raw DOM Event to reject,
which Sentry surfaced as "Event captured as promise rejection" with no
stack. Reject with an Error and revoke the blob URL on failure.
* 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>
The Notion API can return transient 5xx errors during imports. Retry these
up to 8 times with exponential backoff, tracked separately from the existing
timeout/rate-limit retry budget.
When a user query produces a pg-tsquery output ending in mixed `&` and `\`
characters (e.g. `"plugins"&\`), stripping them with separate single-char
regexes in a fixed order could leave a dangling `&` operator, causing
Postgres to reject the query with "no operand in tsquery".
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Attachments whose key ends in a trailing slash have no filename
component and cause yazl to throw, aborting the entire export. Skip
them with a warning and continue the export instead.
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Guard against null collaboratorIds when persisting collaborative
updates; the DB column has no default and can be NULL on older rows.
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* perf: Add missing indexes on foreign keys referencing documents
Cascade deletes on the documents table were forced into sequential scans
on file_operations, share_subscriptions, and access_requests because
their documentId columns lacked a usable single-column index.
Also fixes lint-staged to skip oxlint when every staged file matches an
.oxlintrc.json ignore pattern, since oxlint exits 1 in that case.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Handle oxlint no-files exit instead of mirroring ignorePatterns
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>