Skip to content

fix(models): give drive a PluginConfiguration so it can be disabled safely - #11017

Open
clayrisser wants to merge 1 commit into
hcengineering:developfrom
clayrisser:fix/models-all-drive-class-filter
Open

fix(models): give drive a PluginConfiguration so it can be disabled safely#11017
clayrisser wants to merge 1 commit into
hcengineering:developfrom
clayrisser:fix/models-all-drive-class-filter

Conversation

@clayrisser

Copy link
Copy Markdown

Problem

models/all/src/index.ts:416 registers drive with a two-element tuple:

[driveModel, driveId],

so its PluginConfiguration is built with no classFilter. pluginFilterTx (foundations/core/packages/core/src/utils.ts:844-859) reads a missing classFilter as "exclude everything this plugin contributed":

for (const id of c.transactions) {
  if (c.classFilter !== undefined) {
    const filter = new Set(c.classFilter)
    
    if (filter.has(cud.objectClass)) {
      totalExcluded.add(id as Ref<Tx>)
    }
  } else {
    totalExcluded.add(id as Ref<Tx>)   // <- every tx, class definitions included
  }
}

So when drive is excluded — DISABLED_FEATURES=drive — the client loses not just drive's UI contributions but drive's class definitions. Other subsystems still hold references to those classes, and resolving one throws out of Hierarchy.getClass / getDomain:

  • Inbox — the export service opens a drive DocNotifyContext on every export
  • the screen-recorder save flow — resolves drive:class:Drive and drive:class:Folder
  • document embeds of drive files

The result is that disabling one feature breaks three unrelated ones, with a hierarchy exception rather than a graceful absence.

Fix

Give drive a config tuple whose classFilter names only what drive contributes to the shared UI surfaces — one workbench Application and two ObjectSearchCategory docs:

-    [driveModel, driveId],
+    [
+      driveModel,
+      driveId,
+      {
+        label: drive.string.Drive,
+        enabled: true,
+        beta: false,
+        icon: drive.icon.DriveApplication,
+        classFilter: [workbench.class.Application, presentation.class.ObjectSearchCategory]
+      }
+    ],

Disabling drive now hides the app and its search categories, and leaves the class definitions in place, so the three consumers above keep resolving.

The default import is the merged model plugin (as chunter does in the same file) because the base @hcengineering/drive namespace has no config strings of its own.

Two things I want to flag rather than bury

1. hidden is deliberately not set, and that has a visible consequence. The builder computes:

hidden: config !== undefined ? config.hidden : true,
enabled: (config?.enabled ?? true) && !(config?.hidden ?? false),

Setting hidden: true to preserve today's value would make enabled compute to false and disable drive for every deployment, flag or no flag. So hidden is left unset — which moves drive's hidden from true to undefined, and Configure.svelte:47,62 filters on it.hidden !== true.

Net effect: drive starts appearing in Settings → Configure, where it does not today. I think that is the right outcome — Configure is the list of plugins you can turn on and off, drive is a real user-facing app, and every app with a config tuple is already listed there — but it is a user-visible change that falls out of the fix rather than being asked for, and you should decide it rather than inherit it. If you would rather keep drive out of that list, the builder's hidden/enabled coupling needs untangling first, which is a bigger change than this one.

2. Drive is not the only two-element tuple. coreModel, activityModel, taskModel, inboxModel, recorderModel and roughly forty others are registered the same way, so they have the same classFilter: undefined property. They are not all broken by it — it only bites when the plugin is actually excluded and something outside it still references its classes — but the general question ("should a missing classFilter mean exclude everything, including class definitions?") is worth asking separately, and a safer default in pluginFilterTx would fix the whole family at once. This PR fixes the one case where we hit real breakage; I did not want to imply it is the only one.

Verification

Verified against develop @ 1be6047c8: [driveModel, driveId] is still the bare tuple at :416, pluginFilterTx's else { totalExcluded.add(...) } is still at :857-859, and the builder's hidden/enabled computation is still as quoted. git apply is clean.

Manual check: set DISABLED_FEATURES=drive, then open Inbox, save a screen recording, and open a document containing a drive-file embed. Before the change each throws out of the hierarchy; after it, drive is simply absent from the app list and search.

No automated test — models/all has no jest project, and the behaviour needs a built client model plus the feature flag.

Provenance

Found on a self-hosted deployment that sets DISABLED_FEATURES=drive: Inbox began throwing hierarchy errors, and the trail led back to the missing classFilter rather than to Inbox.

…fely

`[driveModel, driveId]` had no config tuple element, so drive's
PluginConfiguration got `classFilter: undefined` — and pluginFilterTx
(core/utils.ts) reads undefined as "exclude every tx this plugin
contributed", class definitions included. DISABLED_FEATURES=drive
therefore left the client with drive txes referenced but drive:class:*
missing, and anything still resolving one threw out of
Hierarchy.getClass/getDomain: Inbox (the export service opens a drive
DocNotifyContext on every export), the screen-recorder save flow
(drive:class:Drive / drive:class:Folder), doc embeds of drive files.

Drive only contributes three docs to the shared UI surfaces — one
workbench Application and two presentation ObjectSearchCategories — so
that pair is the whole classFilter; everything else stays. `hidden` is
deliberately unset: the builder computes
`enabled = config.enabled && !config.hidden`, so hiding would drop the
Application for every deployment, flag or no flag. The default import
is the merged model plugin (as chunter does) because the base
`@hcengineering/drive` namespace has no config strings of its own.

Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Clay Risser <clayrisser@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@clayrisser
clayrisser force-pushed the fix/models-all-drive-class-filter branch from bf57866 to 03e2627 Compare August 13, 2026 06:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant