* 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.
- Use transient `$rtl` prop on Meta styled component so it isn't forwarded to the DOM
- Wrap ActionButton in observer so action visibility checks read MobX computed values inside a reactive context
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* chore: Vendor request-filtering-agent
* fix: honor fetch timeout and undefined allow list in proxy pre-flight
Default allowIPAddressList to [] so an unset ALLOWED_PRIVATE_IP_ADDRESSES
env var doesn't overwrite the agent's default and crash on .length, and
race the pre-flight DNS lookup against the request's abort signal so the
configured fetch timeout applies to slow DNS resolution.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
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>
* Allow viewers to create and access API keys
Still guarded by their view permissions
* Drop Member role gate from apiKeys routes
Lets viewers reach the createApiKey/listApiKeys/delete policy checks now
that the policy itself permits them. Updates CleanupDemotedUserTask to
retain viewer keys and adds coverage for viewer create + guest reject.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Tighten apiKeys test assertions and broaden viewer coverage
- Use not.toBeNull() instead of toBeTruthy() for retention check
- Add viewer coverage for apiKeys.list and apiKeys.delete
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* fix: Open notifications in a bottom drawer on mobile
Match the mobile context menu pattern by rendering the notifications
panel as a Vaul bottom drawer below the tablet breakpoint, while
keeping the existing Radix popover on desktop.
* fix: Notification drawer opens at correct height on mobile
Skip the height animation while bounds is unmeasured to avoid a
feedback loop between framer-motion's animation toward 0 and the
ResizeObserver re-targeting it. Eagerly import Notifications so first
paint has real content for the initial measurement, and bump its
minHeight to 75vh on mobile to match other bottom drawers.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Increase emoji picker cell size on mobile
Mobile uses a 40px button with a 32px emoji glyph (vs. 32px / 24px on
desktop), so roughly 8 emojis fit across a typical phone screen for
easier touch targeting.
https://claude.ai/code/session_017Rrv75Rc6eZ7eb2iNpZxpu
* tweaks
---------
Co-authored-by: Claude <noreply@anthropic.com>
@emotion/stylis compiles top-level `::highlight(name)` rules inside a
styled component as a compound selector `.parent::highlight(name)`,
which only matches highlights on the editor container element itself.
Chrome applies these leniently, but Firefox correctly requires the
originating element to contain the highlighted text. Prefixing with
`&` forces a descendant combinator so descendant elements containing
the highlighted text are matched.
Closes#12270
* chore(test): drop no-op per-test Redis flushall
The afterEach created a fresh ioredis-mock client and flushed it, which
doesn't clear state held by clients elsewhere in the test. Removing the
hook saves a few ms across thousands of test cases.
* Cache Jest transform cache
Drop the dedicated setup job that blocked every other job for ~60s,
extract the install steps into a reusable composite action, drop the
unnecessary bundle-size dependency on types, and switch test-server
sharding to Jest's native --shard flag.
* fix: relative path returned from MCP
* fix: MCP create_attachment uploadUrl and size validation
Make uploadUrl absolute against team.url so MCP clients can resolve it
without a base, tighten the size schema to match the REST endpoint
(int, nonnegative, finite), and stub cookies on the MCP API context so
LocalStorage's CSRF-aware getPresignedPost works for Bearer-authed
MCP requests. Adds tests covering the success path, persistence, size
limits, schema rejections, and read-only scope enforcement.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
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>
* feat: Add delete_document and delete_collection MCP tools
Adds MCP tools for deleting (or archiving) documents and collections.
Refactors Document#delete into destroyWithCtx and extracts collection
archive logic into Collection#archiveWithCtx so the same code paths can
be shared between the REST API and MCP entry points.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix: Wrap MCP delete tools in DB transaction
Ensures delete/archive of documents and collections via MCP is atomic
and that row locks (transaction.LOCK.UPDATE) inside *WithCtx methods
actually apply, matching the pattern used by move_document.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* docs: Clarify delete_collection MCP tool description
Reflects that collection deletion only soft-deletes non-archived
documents via the BeforeDestroy hook.
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>
Replaces `as any` casts when constructing OAuth2Server Request/Response
with explicit objects containing the fields the library actually
consumes, and switches BaseStorage's manual header spread to a
node-fetch `Headers` instance to avoid the no-misused-spread warning.
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* feat: Add breadcrumb to MCP responses
* test: Update MCP test expectations for new response envelope
Tests were reading the old flat document shape; update them to read
through the new { document, breadcrumb } envelope.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* perf: Batch collection lookups when building breadcrumbs
Add getBreadcrumbsForDocuments helper that loads all referenced
collections in one query (with the user's memberships) and resolves
breadcrumbs from the per-collection cached documentStructure. Use it
in list_documents and move_document, replacing the per-document
Collection.findByPk that produced an N+1 query pattern.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* test: Add coverage for getBreadcrumbsForDocuments and parallelize doc + breadcrumb loads
Run presentDocument and getDocumentBreadcrumb concurrently in fetch,
create_document, and update_document so the breadcrumb lookup no
longer adds latency on top of the presenter.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* chore: resolve no-explicit-any lint warnings in plugins
Replaces uses of `any` in the plugins directory with concrete types,
`unknown`, or structured type assertions, addressing the remaining
typescript-eslint(no-explicit-any) warnings flagged by oxlint.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* chore: address review feedback in GitLabIssueProvider
Drop trailing semicolon from log string and add early return in
`destroyNamespace` when neither `user_id` nor `full_path` is present
to avoid an unnecessary full-scan transaction.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* fix: should change lastModifiedById when republishing an already published document
* test: Use same-team creator in republish attribution test
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* fix: Api keys with global read scope not being saved correctly
* refactor: Hoist global scopes Set to module level
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* fix: centralize sidebar expansion state to eliminate O(N²) tree traversals
Each DocumentLink previously traversed the full collection tree independently
to determine whether to auto-expand (pathToDocument / descendants), which is
O(N) per row and quadratic overall. With thousands of documents this makes
the sidebar unusable.
Replaces per-node expansion state with a single MobX-backed
SidebarExpansionState per tree root. The ObservableSet ensures only the
toggled node re-renders. Alt-click cascade, auto-expand on navigation,
and drag-to-reparent expansion all go through the same centralized state
instead of the per-node SidebarDisclosureContext relay.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: move SidebarExpansionContext alongside other sidebar contexts
Rename hooks/useSidebarExpansion.ts to components/SidebarExpansionContext.ts
to match the convention of SidebarContext.ts and SidebarDisclosureContext.ts.
The context is now the default export with hooks as named exports.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: scope sidebar expansion to its own tree and restore alt-click cascade
`useSidebarExpansionState` was unconditionally adding the active document
id to every per-tree expansion set, which made `SharedWithMeLink` auto-
expand whenever the user navigated anywhere in the matching sidebar
context. `computeAncestorPath` now includes the target when found and
returns empty when absent, so the hook only expands ids that actually
belong to its tree.
Also restores alt-click cascade for `StarredDocumentLink` and
`SharedWithMeLink`: the parents still broadcast disclosure events but
`DocumentLink` no longer listens, so nested children weren't expanded.
`StarredDocumentLink` now subscribes via `useSidebarDisclosure` (mirroring
`CollectionLinkChildren`), and `SharedWithMeLink` calls
`expansion.expandAll`/`collapseAll` directly on alt-click.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix: collapse expanded nodes when children are removed and deduplicate shared expansion provider
Restores the effect that collapses a node in the expansion state when it
no longer has children, preventing the reorder drop logic from treating
leaf nodes as expanded containers. Also removes the redundant
SidebarExpansionContext.Provider from SharedCollectionLink since the
parent SharedSidebar already provides one.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: Suspended users should not be included in cached member count for groups
* fix: Defer CounterCache hook registration until model is initialized
The previous test-only no-op hid a timing bug where setImmediate could
fire before the Sequelize instance had registered the related model,
causing "Model not initialized" failures. Poll until the model is
ready, and unref the pending immediate so it does not keep the event
loop alive in environments where the database is never initialized.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* perf: Reduce overhead of group member count invalidation
Select only the groupId column with raw queries and de-duplicate before
issuing Redis deletes, avoiding loading full GroupUser rows into memory
when a user belongs to many groups.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* chore: unref Redis healthcheck interval
Don't keep the Node event loop alive solely for the periodic ping; the
event loop should drain on its own when the application is shutting
down or a Jest worker is finishing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* refactor: Centralize counter cache key in RedisPrefixHelper
Avoid duplicating the "count:<Model>:<relation>:<id>" string between
the CounterCache decorator and the User suspension hook by routing
both through a single getCounterCacheKey helper.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix: Walk to parent transaction when scheduling cache invalidation
Nested savepoints commit independently of their outer transaction, so
afterCommit callbacks attached to the inner transaction may run after
the outer rolls back, or never run at all. Match the pattern used in
Collection, Event, and base/Model and walk to the parent transaction
so the cache invalidation fires after the real outer commit.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>