* chore(deps): bump zod from 4.3.6 to 4.4.3
Bumps [zod](https://github.com/colinhacks/zod) from 4.3.6 to 4.4.3.
- [Release notes](https://github.com/colinhacks/zod/releases)
- [Commits](https://github.com/colinhacks/zod/compare/v4.3.6...v4.4.3)
---
updated-dependencies:
- dependency-name: zod
dependency-version: 4.4.3
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot] <support@github.com>
* fix: Make files.create file param optional in schema for zod 4.4
zod 4.4 changed z.custom() to reject undefined. Since validate runs
before multipart injects the file, validation failed with 400 on all
files.create requests. Mark the field optional and guard in the handler.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Tom Moor <tom@getoutline.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* chore(deps-dev): bump vite-plugin-babel from 1.6.0 to 1.7.3
Bumps [vite-plugin-babel](https://github.com/owlsdepartment/vite-plugin-babel) from 1.6.0 to 1.7.3.
- [Commits](https://github.com/owlsdepartment/vite-plugin-babel/commits)
---
updated-dependencies:
- dependency-name: vite-plugin-babel
dependency-version: 1.7.3
dependency-type: direct:development
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot] <support@github.com>
* fix: Use include option for vite-plugin-babel TS transform
vite-plugin-babel 1.7.0 added an `include` option defaulting to
`/\.jsx?$/` (JS only) that is applied before `filter`, so .ts/.tsx
files were no longer transformed by Babel and reached the parser
with types intact. Switch to the `include` option to match TS files.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Tom Moor <tom@getoutline.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Forces transitive uuid copies (8.3.2 via sequelize/bull, 9.0.1 via
@hocuspocus/*) onto the patched 11.1.1, addressing GHSA-w5hq-g745-h8pq.
11.1.1 is the highest version that is both patched and ships a CommonJS
build, which the require()-based consumers depend on.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* perf: Lazy import mailparser, @fast-csv, and franc deps
Moves heavy dependencies off the startup path into the narrow async code
paths that actually use them, mirroring the mammoth lazy-import change:
- mailparser: only needed for Confluence Word imports (confluenceToHtml)
- @fast-csv/parse: only needed for CSV imports (csvToMarkdown)
- franc / iso-639-3: only needed by the DocumentUpdateText worker task
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* perf: Lazy import jsdom dep
jsdom is one of the heaviest server dependencies but is only needed for
HTML export (ProsemirrorHelper.toHTML) and HTML import
(DocumentConverter.htmlToProsemirror). Move it to a lazy `await import`
inside those methods so its dependency tree stays off the startup path.
Both methods become async; all callers were already in async contexts.
The type-only usage in patchGlobalEnv is now an `import type`.
* fix: Run single process when only the worker service is enabled
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* perf: Improve memory consumption through lazy service loading
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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>