Skip to content

feat: dispatch document editors through a registry#3932

Open
Crash-- wants to merge 8 commits into
masterfrom
feat/editor-registry
Open

feat: dispatch document editors through a registry#3932
Crash-- wants to merge 8 commits into
masterfrom
feat/editor-registry

Conversation

@Crash--

@Crash-- Crash-- commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add an editor registry as the single source of truth for document dispatch: each entry holds its slug, kind, enablement, file and shortcut matchers, route builder, and add-menu create gating.
  • Make computeFileType, computeApp and computePath read from the registry instead of per-editor branches.
  • Fold Docs and Grist (bridge documents opened through /bridge/<app>) into the registry next to the in-app editors, tagged with a kind field so only in-app editors mount routes and are used as in-app redirect targets, while bridge documents keep their /bridge link.
  • Generate the add-menu "create a document" entries from the registry: each editor declares create.isAvailable, so the registry owns which editors offer a create entry and when it shows; the toolbar keeps only the rendering.
  • Route an Excalidraw file shared as a shared-drive root (seen by the recipient as a .url shortcut) to its editor, and redirect editor documents from the shared-drive root-file viewer so direct links and reloads reach the editor too.

Note on the repeated slug keys between registry.ts and the slug maps in routes.tsx (ROUTE_FACTORIES) and CreateDocumentItems.jsx (CREATE_ITEM_RENDERERS): registry.ts holds only data and pure dispatch logic with no JSX, because it is imported by computeFileType/computePath and runs on every file row of every listing. The slug maps keep the React components out of that hot path. Pulling ExcalidrawView, OnlyOfficeView or the create buttons into the registry would bundle them into every listing and make the pure dispatch model depend on the view layer. So the keys repeat on purpose: the registry is the model, and those maps are thin view-layer adapters keyed back to it.

Summary by CodeRabbit

  • Refactor

    • Centralized editor/bridge routing and document-type detection through a single registry, enabling consistent in-app redirects (including shared-drive root files) and cleaner, context-aware route generation.
    • Updated the document “Create” menu to be driven by the registry, showing only supported editor options based on device, upload capability, and feature-flag/permission rules.
  • Tests

    • Added/expanded coverage for editor selection, shortcut target resolution, route generation, redirect behavior, and create-menu filtering across multiple document types and flag states.

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR adds a centralized editor registry in src/modules/views/editor/registry.ts and uses it to drive file matching, shortcut resolution, route construction, create-menu availability, and editor route mounting. Navigation helpers now resolve editor types, apps, and paths through registry lookups. Shared-drive root-file viewing can redirect to an in-app editor route after the file loads. The create-document menu now renders items from the registry. AppRoute now renders shared editor routes through getEditorRoutes().

Possibly related PRs

Suggested reviewers

  • JF-Cozy
  • lethemanh
  • doubleface
  • rezk2ll
  • zatteo
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main change: moving document editor dispatch to a registry.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/editor-registry

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

codescene-delta-analysis[bot]

This comment was marked as outdated.

codescene-delta-analysis[bot]

This comment was marked as outdated.

Comment thread src/modules/views/editor/registry.js Outdated
Comment thread src/modules/views/editor/routes.jsx Outdated
@zatteo

zatteo commented Jun 18, 2026

Copy link
Copy Markdown
Member

Very very promising refactoring I can't wait to see a new editor added in 10 lines 🎉

codescene-delta-analysis[bot]

This comment was marked as outdated.

@bundlemon

bundlemon Bot commented Jun 18, 2026

Copy link
Copy Markdown

BundleMon

Files updated (2)
Status Path Size Limits
static/js/main.(hash).js
54.14KB (+71B +0.13%) -
static/js/public.(hash).js
25.79KB (-51B -0.19%) -
Unchanged files (18)
Status Path Size Limits
static/js/cozy.(hash).js
929.08KB -
static/resource/(hash).js
336.09KB -
services/qualificationMigration.js
283.39KB -
services/dacc.js
263.13KB -
static/js/lib-react.(hash).js
43.88KB -
static/css/cozy.(hash).css
30.13KB -
static/js/lib-polyfill.(hash).js
22.77KB -
static/js/lib-router.(hash).js
21.86KB -
static/js/(chunkId).(hash).js
14.5KB -
static/css/main.(hash).css
13.56KB -
static/js/intents.(hash).js
9KB -
static/js/async/(chunkId).(hash).js
7.5KB -
manifest.webapp
5.96KB -
static/css/public.(hash).css
2.34KB -
index.html
784B -
public/index.html
705B -
intents/index.html
654B -
assets/manifest.json
185B -

Total files change +15B 0%

Groups updated (1)
Status Path Size Limits
**/*.js
6.02MB (+536B +0.01%) -
Unchanged groups (2)
Status Path Size Limits
**/*.{png,svg,ico}
2.16MB -
**/*.css
77.42KB -

Final result: ✅

View report in BundleMon website ➡️


Current branch size history | Target branch size history

codescene-delta-analysis[bot]

This comment was marked as outdated.

@Crash--
Crash-- force-pushed the feat/editor-registry branch from 0e2c7ba to 3cd5049 Compare June 21, 2026 05:47
codescene-delta-analysis[bot]

This comment was marked as outdated.

@Crash--
Crash-- marked this pull request as ready for review June 21, 2026 06:01
@Crash-- Crash-- added the e2e Run the E2E suite on this PR label Jun 21, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/modules/views/editor/routes.jsx (1)

29-31: ⚡ Quick win

Guard missing route factories before invocation.

Line 31 directly calls ROUTE_FACTORIES[slug](). If a new editor is added to EDITORS without updating this map, routing crashes at runtime. Add an explicit invariant (or safe skip) here.

Proposed change
 export const getEditorRoutes = () => (
   <>
-    {EDITORS.map(({ slug, flag: editorFlag }) =>
-      editorFlag === null || flag(editorFlag) ? (
-        <React.Fragment key={slug}>{ROUTE_FACTORIES[slug]()}</React.Fragment>
-      ) : null
-    )}
+    {EDITORS.map(({ slug, flag: editorFlag }) => {
+      const shouldMount = editorFlag === null || flag(editorFlag)
+      if (!shouldMount) return null
+
+      const buildRoutes = ROUTE_FACTORIES[slug]
+      if (!buildRoutes) {
+        throw new Error(`Missing route factory for editor slug "${slug}"`)
+      }
+
+      return <React.Fragment key={slug}>{buildRoutes()}</React.Fragment>
+    })}
   </>
 )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/views/editor/routes.jsx` around lines 29 - 31, The code in the
EDITORS map directly invokes ROUTE_FACTORIES[slug]() without verifying that the
factory exists in the ROUTE_FACTORIES map. If a slug is added to EDITORS without
a corresponding entry in ROUTE_FACTORIES, this will cause a runtime crash when
attempting to call undefined as a function. Add a guard condition before
invoking ROUTE_FACTORIES[slug]() to check that the factory exists, either by
using an invariant assertion or by conditionally rendering the route only when
ROUTE_FACTORIES[slug] is defined, ensuring all editors in EDITORS have matching
factories before their routes are created.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/modules/views/editor/registry.js`:
- Around line 88-94: The `findEditorForFile` function unconditionally invokes
`editor.isEnabled(context)` on line 92, but the EditorDescriptor documentation
indicates that `isEnabled` is optional. Add a guard check to verify that
`editor.isEnabled` exists and is a function before calling it, similar to the
existing guard for `editor.matchesFile`. This same issue also exists in another
resolver around line 108 that should be fixed identically.

---

Nitpick comments:
In `@src/modules/views/editor/routes.jsx`:
- Around line 29-31: The code in the EDITORS map directly invokes
ROUTE_FACTORIES[slug]() without verifying that the factory exists in the
ROUTE_FACTORIES map. If a slug is added to EDITORS without a corresponding entry
in ROUTE_FACTORIES, this will cause a runtime crash when attempting to call
undefined as a function. Add a guard condition before invoking
ROUTE_FACTORIES[slug]() to check that the factory exists, either by using an
invariant assertion or by conditionally rendering the route only when
ROUTE_FACTORIES[slug] is defined, ensuring all editors in EDITORS have matching
factories before their routes are created.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6bbc2475-ba51-4f70-8ec0-a55d9d7df49d

📥 Commits

Reviewing files that changed from the base of the PR and between bb252b9 and 3cd5049.

📒 Files selected for processing (10)
  • src/modules/navigation/AppRoute.jsx
  • src/modules/navigation/hooks/helpers.spec.js
  • src/modules/navigation/hooks/helpers.ts
  • src/modules/navigation/hooks/useFileLink.tsx
  • src/modules/views/SharedDrive/FilesViewerSharedDriveRootFile.jsx
  • src/modules/views/SharedDrive/FilesViewerSharedDriveRootFile.spec.jsx
  • src/modules/views/editor/registry.js
  • src/modules/views/editor/registry.spec.js
  • src/modules/views/editor/routes.jsx
  • src/modules/views/editor/routes.spec.jsx

Comment thread src/modules/views/editor/registry.js Outdated
Comment on lines +88 to +94
export const findEditorForFile = (file, context = {}) =>
EDITORS.find(
editor =>
Boolean(editor.matchesFile) &&
editor.isEnabled(context) &&
editor.matchesFile(file)
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Guard optional isEnabled before invocation (Line 92 and Line 108).

EditorDescriptor.isEnabled is documented as optional, but both resolvers call it unconditionally. A descriptor with matchesFile/matchesShortcutTarget and no isEnabled will throw at runtime.

Proposed fix
 export const findEditorForFile = (file, context = {}) =>
   EDITORS.find(
     editor =>
       Boolean(editor.matchesFile) &&
-      editor.isEnabled(context) &&
+      (editor.isEnabled?.(context) ?? true) &&
       editor.matchesFile(file)
   )

 export const findEditorForShortcutTarget = (target, context = {}) =>
   EDITORS.find(
     editor =>
       Boolean(editor.matchesShortcutTarget) &&
-      editor.isEnabled(context) &&
+      (editor.isEnabled?.(context) ?? true) &&
       editor.matchesShortcutTarget(target)
   )

Also applies to: 104-110

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/views/editor/registry.js` around lines 88 - 94, The
`findEditorForFile` function unconditionally invokes `editor.isEnabled(context)`
on line 92, but the EditorDescriptor documentation indicates that `isEnabled` is
optional. Add a guard check to verify that `editor.isEnabled` exists and is a
function before calling it, similar to the existing guard for
`editor.matchesFile`. This same issue also exists in another resolver around
line 108 that should be fixed identically.

@Crash--
Crash-- force-pushed the feat/editor-registry branch from 3cd5049 to a317e4c Compare June 22, 2026 08:33
codescene-delta-analysis[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/modules/views/SharedDrive/FilesViewerSharedDriveRootFile.spec.jsx (1)

91-126: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Strengthen redirect tests by asserting resolver call contract and no-navigation fallback

At Line 91 and Line 111, consider asserting findEditorForFile is called with the resolved file and { isDesktop: true }, and in the bridge case assert no navigate: marker is rendered. This locks in the breakpoint-aware dispatch contract and prevents false positives if redirect logic regresses.

Suggested test hardening
   it('redirects an editor document to its editor', () => {
+    const fetchedFile = {
+      _id: 'canonical-id',
+      id: 'canonical-id',
+      name: 'Drawing.excalidraw'
+    }
     mockFindEditorForFile.mockReturnValue({
       slug: 'excalidraw',
       kind: 'editor',
       makeRoute: file => `/excalidraw/drive-1/${file._id}`
     })
     renderRootFileViewer({
-      fetchedFile: {
-        _id: 'canonical-id',
-        id: 'canonical-id',
-        name: 'Drawing.excalidraw'
-      }
+      fetchedFile
     })

+    expect(mockFindEditorForFile).toHaveBeenCalledWith(fetchedFile, {
+      isDesktop: true
+    })
     expect(
       screen.getByText('navigate:/excalidraw/drive-1/canonical-id')
     ).toBeInTheDocument()
     expect(mockFilesViewer).not.toHaveBeenCalled()
   })

   it('does not redirect a bridge document (it has no in-app route)', () => {
+    const fetchedFile = {
+      _id: 'canonical-id',
+      id: 'canonical-id',
+      name: 'Budget.grist'
+    }
     mockFindEditorForFile.mockReturnValue({
       slug: 'grist',
       kind: 'bridge',
       makeRoute: file => `/bridge/grist/${file.metadata.externalId}`
     })
     renderRootFileViewer({
-      fetchedFile: {
-        _id: 'canonical-id',
-        id: 'canonical-id',
-        name: 'Budget.grist'
-      }
+      fetchedFile
     })

+    expect(mockFindEditorForFile).toHaveBeenCalledWith(fetchedFile, {
+      isDesktop: true
+    })
+    expect(screen.queryByText(/^navigate:/)).not.toBeInTheDocument()
     expect(screen.getByText('files-viewer')).toBeInTheDocument()
   })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/views/SharedDrive/FilesViewerSharedDriveRootFile.spec.jsx` around
lines 91 - 126, The two tests need additional assertions to strengthen the
contract verification. In the first test that redirects an editor document
(starting with "redirects an editor document to its editor"), add an assertion
that mockFindEditorForFile was called with the fetchedFile object and an options
object containing isDesktop: true to verify the dispatch contract. In the second
test that handles bridge documents (starting with "does not redirect a bridge
document"), add an assertion that verifies no navigate: marker is rendered to
ensure the fallback behavior is correct and prevent regressions if redirect
logic changes.
src/modules/navigation/hooks/helpers.spec.js (1)

14-22: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider consistent mock patterns across helper modules.

The Excalidraw mock uses jest.requireActual to preserve unmodified exports while the OnlyOffice mock replaces the entire module. This inconsistency could cause subtle issues if the OnlyOffice helpers module exports additional functions (like shouldBeOpenedByOnlyOffice) that the registry uses.

♻️ Suggested consistent pattern
 jest.mock('modules/views/OnlyOffice/helpers', () => ({
+  ...jest.requireActual('modules/views/OnlyOffice/helpers'),
   makeOnlyOfficeFileRoute: jest.fn(),
   isOfficeEnabled: jest.fn()
 }))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/navigation/hooks/helpers.spec.js` around lines 14 - 22, The
OnlyOffice helpers mock is replacing the entire module without preserving actual
exports, while the Excalidraw helpers mock uses jest.requireActual to preserve
unmodified exports. Update the jest.mock call for
'modules/views/OnlyOffice/helpers' to use the same pattern as the Excalidraw
mock by adding jest.requireActual with the spread operator at the beginning of
the mock object. This ensures all actual exports (including any additional
functions like shouldBeOpenedByOnlyOffice) are available in the mocked module
while only the specified functions (makeOnlyOfficeFileRoute and isOfficeEnabled)
are overridden with jest.fn().
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/modules/navigation/hooks/helpers.spec.js`:
- Around line 14-22: The OnlyOffice helpers mock is replacing the entire module
without preserving actual exports, while the Excalidraw helpers mock uses
jest.requireActual to preserve unmodified exports. Update the jest.mock call for
'modules/views/OnlyOffice/helpers' to use the same pattern as the Excalidraw
mock by adding jest.requireActual with the spread operator at the beginning of
the mock object. This ensures all actual exports (including any additional
functions like shouldBeOpenedByOnlyOffice) are available in the mocked module
while only the specified functions (makeOnlyOfficeFileRoute and isOfficeEnabled)
are overridden with jest.fn().

In `@src/modules/views/SharedDrive/FilesViewerSharedDriveRootFile.spec.jsx`:
- Around line 91-126: The two tests need additional assertions to strengthen the
contract verification. In the first test that redirects an editor document
(starting with "redirects an editor document to its editor"), add an assertion
that mockFindEditorForFile was called with the fetchedFile object and an options
object containing isDesktop: true to verify the dispatch contract. In the second
test that handles bridge documents (starting with "does not redirect a bridge
document"), add an assertion that verifies no navigate: marker is rendered to
ensure the fallback behavior is correct and prevent regressions if redirect
logic changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 58e829cb-e4c8-4700-b779-d6e5d764acbc

📥 Commits

Reviewing files that changed from the base of the PR and between 3cd5049 and a317e4c.

📒 Files selected for processing (12)
  • src/modules/drive/Toolbar/components/CreateDocumentItems.jsx
  • src/modules/drive/Toolbar/components/CreateDocumentItems.spec.jsx
  • src/modules/navigation/AppRoute.jsx
  • src/modules/navigation/hooks/helpers.spec.js
  • src/modules/navigation/hooks/helpers.ts
  • src/modules/navigation/hooks/useFileLink.tsx
  • src/modules/views/SharedDrive/FilesViewerSharedDriveRootFile.jsx
  • src/modules/views/SharedDrive/FilesViewerSharedDriveRootFile.spec.jsx
  • src/modules/views/editor/registry.js
  • src/modules/views/editor/registry.spec.js
  • src/modules/views/editor/routes.jsx
  • src/modules/views/editor/routes.spec.jsx
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/modules/views/editor/routes.spec.jsx
  • src/modules/views/SharedDrive/FilesViewerSharedDriveRootFile.jsx
  • src/modules/views/editor/routes.jsx
  • src/modules/views/editor/registry.js
  • src/modules/navigation/AppRoute.jsx
  • src/modules/navigation/hooks/useFileLink.tsx

@Crash--
Crash-- force-pushed the feat/editor-registry branch from a317e4c to 28f7db3 Compare June 22, 2026 09:00
codescene-delta-analysis[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/modules/views/editor/registry.spec.js (1)

180-194: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Assert the exact feature-flag keys in Docs/Grist create tests.

These tests currently validate only boolean outcomes; they won’t catch a typo/regression in the flag name.

Suggested test hardening
   it('offers Docs only privately and behind its flag', () => {
     mockFlag.mockReturnValue(true)
     expect(canCreate('docs', { isPublic: false })).toBe(true)
+    expect(mockFlag).toHaveBeenCalledWith('drive.lasuitedocs.enabled')
     expect(canCreate('docs', { isPublic: true })).toBe(false)
     mockFlag.mockReturnValue(false)
     expect(canCreate('docs', { isPublic: false })).toBe(false)
   })

   it('offers Grist only privately and behind its flag', () => {
     mockFlag.mockReturnValue(true)
     expect(canCreate('grist', { isPublic: false })).toBe(true)
+    expect(mockFlag).toHaveBeenCalledWith('drive.grist.enabled')
     expect(canCreate('grist', { isPublic: true })).toBe(false)
     mockFlag.mockReturnValue(false)
     expect(canCreate('grist', { isPublic: false })).toBe(false)
   })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/views/editor/registry.spec.js` around lines 180 - 194, In both
test cases "offers Docs only privately and behind its flag" and "offers Grist
only privately and behind its flag", add assertions to verify that mockFlag is
being called with the exact correct feature-flag key names. For the docs test,
verify mockFlag is called with the docs flag key; for the grist test, verify
mockFlag is called with the grist flag key. This will catch any typos or
regressions in the flag names instead of only validating the boolean outcomes of
canCreate.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/modules/views/editor/registry.spec.js`:
- Around line 180-194: In both test cases "offers Docs only privately and behind
its flag" and "offers Grist only privately and behind its flag", add assertions
to verify that mockFlag is being called with the exact correct feature-flag key
names. For the docs test, verify mockFlag is called with the docs flag key; for
the grist test, verify mockFlag is called with the grist flag key. This will
catch any typos or regressions in the flag names instead of only validating the
boolean outcomes of canCreate.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: df145f21-7a90-4856-a0e9-c6e074315cef

📥 Commits

Reviewing files that changed from the base of the PR and between a317e4c and 28f7db3.

📒 Files selected for processing (4)
  • src/modules/drive/Toolbar/components/CreateDocumentItems.jsx
  • src/modules/drive/Toolbar/components/CreateDocumentItems.spec.jsx
  • src/modules/views/editor/registry.js
  • src/modules/views/editor/registry.spec.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/modules/drive/Toolbar/components/CreateDocumentItems.jsx

codescene-delta-analysis[bot]

This comment was marked as outdated.

@Crash--
Crash-- force-pushed the feat/editor-registry branch from f993c4a to dcfaf0e Compare June 26, 2026 15:26
codescene-delta-analysis[bot]

This comment was marked as outdated.

Crash-- added 3 commits June 29, 2026 16:42
Editor dispatch (Excalidraw, OnlyOffice) was duplicated across
computeFileType and computePath. Introduce a single editor registry
as the source of truth so a new editor type is wired in one place.

As a result, an Excalidraw file shared as a shared-drive root, which
the recipient sees as a .url shortcut, now resolves to the Excalidraw
editor instead of the generic shared-drive root-file viewer.
A file shared as a shared-drive root is materialized on the recipient
as a .url shortcut, and direct links or reloads land on the generic
root-file viewer without going through list-level dispatch. That viewer
has no inline Excalidraw viewer, so a shared drawing never opened.

Once the real file is resolved, redirect documents that match an editor
in the registry to their editor.
Editor routes were mounted in two places in AppRoute (OnlyOffice
unconditionally, Excalidraw and PDF behind their flags). Move the list
of editors and their flag gating into a single registry so AppRoute
mounts them with one call and a new editor is added in one place.

No behavior change: the same routes are mounted under the same flags.
Crash-- added 5 commits June 29, 2026 16:42
Each editor descriptor now owns its enablement: isEnabled reads
cozy-flags directly instead of receiving precomputed booleans, and the
route layer derives slug and flag from the same EDITORS list. computeFileType
takes isDesktop (forwarded to the device-aware OnlyOffice check) rather
than the enablement booleans. PDF is listed as a route-only editor.

No behavior change: enablement resolves through the same helpers as before.
Fold the Docs and Grist handles into the editor registry so file-type,
app and path dispatch read from one list instead of dedicated branches
in computeFileType/computeApp/computePath. Each descriptor declares its
kind ('editor' for in-app editors, 'bridge' for documents opened through
/bridge/<app>) and bridge documents build their own route, so the route
layer mounts nothing for them and the shared-drive root-file viewer never
redirects to a non-existent in-app route.
…stry

Move the per-editor add-menu gating into the registry as
`create.isAvailable`, next to each editor's dispatch definition, and have
CreateDocumentItems iterate the registry: the registry owns both which
editors offer a create entry and when it is shown, while the toolbar keeps
only the slug-keyed rendering. The React components stay out of the
registry (imported by the pure dispatch helpers on the file-listing hot
path) for the same reason the route factories do. Note is rendered on its
own since it is not a registry editor. Reorder the registry so its order
matches the add menu (Docs, Excalidraw, Grist, OnlyOffice), which leaves
dispatch unchanged since those matchers are mutually exclusive.
The registry's JSDoc typedefs become real interfaces (EditorDescriptor,
EditorContext, CreateMenuContext, MakeRouteOptions, ShortcutTarget), so
the editor contract is checked rather than documented. routes.tsx is
typed the same way. The specs stay in JavaScript, like helpers.spec.js
for the TypeScript helpers.ts they cover: they gain no typedef removal,
only fixture casting.
@Crash--
Crash-- force-pushed the feat/editor-registry branch from dcfaf0e to d3d49a5 Compare June 29, 2026 14:42

@codescene-delta-analysis codescene-delta-analysis Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Health Improved (3 files improve in Code Health)

Our agent can fix these. Install it.

Gates Passed
3 Quality Gates Passed

View Improvements
File Code Health Impact Categories Improved
helpers.ts 9.11 → 9.18 Complex Method
AppRoute.jsx 9.22 → 9.23 Large Method
CreateDocumentItems.jsx 9.69 → 10.00 Complex Method

Quality Gate Profile: The Bare Minimum
Install CodeScene MCP: safeguard and uplift AI-generated code. Catch issues early with our IDE extension and CLI tool.

/** cozy-flag that gates mounting the editor's routes; `null` mounts them
* unconditionally (the editor gates access internally). Irrelevant for
* `'bridge'` documents (they mount no route). */
flag: string | null

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"flag" name is a little misleading: we end with flag: null for editor that "have a flag" literally because it is only to gate editor route (which I understand must be different that isEnabled)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

e2e Run the E2E suite on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants