You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Which component renders something is decided in several unrelated places, each with its own hard-coded mapping:
Page:selectComponent in browser/data-browser/src/views/ResourcePage.tsx:276, a switch from class URL to page component (Table, Document, Canvas, Folder, Dashboard, …), falling back to ResourcePageDefault.
Fallbacks after that switch (ResourcePage.tsx:185-255): Website / WebsiteExport, drive apps, plugin scripts, and getPluginForClass (iframe PluginView). Apps and plugins can't be cases because their classes are minted per drive, so they are special-cased around the switch.
The generic edit form: a separate route, /app/edit (routes/EditRoute.tsx → ResourceForm), reached through editURL() (helpers/navigation.tsx:80) from the Edit action and its shortcut (actions/resourceActions.tsx:127) and five other places.
Card, Inline and row views (views/Card/, views/ResourceInline/, ResourceRow.tsx) each have their own class → component mapping.
Resources embedded in a document:chunks/RTE/ResourceExtension/ResourceComponent.tsx:19.
Table view tabs:chunks/TablePage/tableViewKinds.ts (VIEW_KINDS: table, kanban, calendar, timer, dashboard, issues) plus drive apps offered through appsForClass(useDriveApps(drive), rowClass) in TableViewTabs.tsx:111. This is a second registry, and plugin views plug in here today, not in ResourcePage.
Folder list / grid:viewMap in views/FolderPage/index.tsx, keyed by DisplayStyle URL.
Ontology read / edit: one page with a local editMode state and an Edit/Read button (views/OntologyPage/OntologyPage.tsx).
Choosing between views is also done differently in each place:
URL ?view=
Local override
Stored value on the resource
Default
Table tabs (useTableView.ts:220)
yes
no
default-view
first tab
Folder list / grid
no
yes (folderDisplayPrefs in localStorage)
display-style
List
Ontology read / edit
no
no
no
edit if the ontology is empty
Everything else
no
no
no
only one view is reachable
Other problems:
Only the first class is considered (isA = isAList[0], ResourcePage.tsx:93), so a resource with several classes gets the view of whichever class happens to be listed first, and the views of its other classes can't be reached at all.
Adding a view means editing ResourcePage.tsx or tableViewKinds.ts and importing the view there. Views can't be added from outside the data browser, which blocks moving them into their own package (see "Why" below).
A plugin view has to choose which mechanism it plugs into, so it can't be offered both as a table tab and as a page view.
A registry pattern already exists for new-resource dialogs (registerNewResourceDialog, components/forms/NewForm/CustomCreateActions/CustomForms/index.ts).
Proposal: one registry
One registry keyed by class URL and view type. The view types are page, card, grid-item, inline, row, embed, and rows: a view of a collection of resources (a table's rows, a folder's children), looked up by the row class.
registerView({id: 'table',type: 'page',class: dataBrowser.classes.table,component: lazy(()=>import('@chunks/TablePage').then(m=>({default: m.TablePage}))),});// Table tab kinds are `rows` views. Their ids are today's `view-kind` strings.registerView({id: 'calendar',type: 'rows',match: ({ columns })=>columns.some(isDateProperty),// e.g. calendar needs a date columncomponent: lazy(()=>import('@chunks/TablePage/Calendar/CalendarView')),});// Per-drive classes (drive apps, plugins) register through a matcher instead of a fixed URL,// replacing both the ResourcePage special cases and `appsForClass`.registerView({id: app.subject,type: 'rows',match: ({ rowClass })=>app.classes.includes(rowClass),component: AppView});// The generic edit form: every resource, for anyone who can write it, lowest priority.registerView({id: 'resource-form',type: 'page',match: ({ canWrite })=>canWrite,priority: -Infinity,component: ResourceFormView});resolveViews(resource,'page');// every applicable page view, in priority orderresolveViews({ rowClass, columns },'rows');// every applicable table tab kind
Resolution considers all of a resource's classes and returns every applicable view in priority order (registration order or an explicit priority), not only one. This is the rule from Changing a view's kind replaces the only table view without warning #1806, applied everywhere: "the user should always be able to switch between all the applicable views".
A view can require write access (match sees canWrite), so edit views are simply absent for readers.
Each view type keeps its generic fallback (ResourcePageDefault, ResourceCard, DefaultGridItem, the plain Table tab, …).
Heavy views register a lazy() component, so the chunks/ bundle split stays as it is.
Built-in views are registered in one place (e.g. views/registerViews.ts).
Choosing the active view: one rule everywhere
Decided by Michiel. Wherever more than one view applies (a resource page, a table's tabs, a folder), one hook, e.g. useActiveView(resource, type), picks the active one in this order:
?view= in the URL. A link shows what its sender saw, a reload keeps it, back/forward moves between views. Tables already work this way; ShowRoute already has the parameter.
Local override, per resource, in localStorage. This is the folder behaviour, now for everything. The old folderDisplayPrefs key is read once so existing choices survive.
Stored value on the resource, shared with everyone: a table's default-view, a folder's display-style.
Class default: the first applicable view from resolveViews, with an optional per-view default rule (e.g. an empty ontology opens in its edit view).
Rules:
Every step is checked against resolveViews. A deleted tab, an uninstalled app, lost write access or an old link falls through to the next step; it never shows an empty page.
Switching views sets the URL and the local override, never the resource. Only an explicit "Set as default" action writes the stored value. Tables already have this; folders get the same action (today their toggle writes only localStorage and the shared display-style can't be changed from the page).
The value in steps 1–3 identifies a view within that resource's options: for a table, the View resource's subject (tabs are resources, as today); for a folder, the DisplayStyle URL; otherwise the registry id.
Ontology: two views instead of a mode
OntologyPage becomes two registered page views for the Ontology class, ontology-read (class and property cards plus the graph) and ontology-edit (the *Write cards and New buttons, requires write access). The Edit/Read button becomes the generic view switcher, so edit mode is linkable (?view=ontology-edit) and remembered per browser. OntologyContextProvider wraps both. The "empty ontology opens in edit" behaviour stays, as the edit view's default rule in step 4.
The generic edit form becomes a view
Decided by Michiel. ResourceForm is registered as the page view resource-form: it applies to every resource the viewer can write, with the lowest priority, so it is never the default but is always reachable from the view switcher. An ontology then offers three views: read, the ontology editor and the generic form.
editURL() returns the resource's own URL with ?view=resource-form, so the Edit action, its keyboard shortcut and the five other callers change in one place.
/app/edit?subject=… keeps working: the route redirects to ?view=resource-form, so existing links and bookmarks still open the form.
Opening the form this way sets only the URL (step 1), not the local override, so a quick edit doesn't make the form the remembered view for that resource. Worth confirming in review; if it should behave like any other switch, it just follows the general rule.
Storage is unchanged
A table tab is still a View resource that stores its kind as the view-kind string, plus its own configuration (filters, grouping, …). That string is now a registry id. A folder's display-style keeps its DisplayStyle URLs, which are the registry ids of the folder's list and grid views. Existing resources keep working with no migration.
Registry ids are stable
Ids are stored in View resources and folders, appear in URLs, and are referenced by app-view grants (#1788 / #1849 / #1888, scoped to "tables where this view is installed"). An id can never be renamed or reused once released; renaming it would orphan saved tabs, grants and links. Built-in table ids are exactly today's view-kind values, and a drive app's id stays its subject, as it is today (appViewOf in tableViewKinds.ts).
Steps
Registry + page views. Add the registry and resolveViews, and move selectComponent and the Website/app/plugin special cases in ResourcePage.tsx onto it. No behaviour change.
Active view selection. Add useActiveView with the four-step order; use it for page views and folders (list/grid as rows views, with the local override migrated), split the ontology into its two views, and register resource-form (with editURL() and the /app/edit redirect).
Card, grid-item, inline, row and embed views onto the same registry.
This is the first step toward moving the built-in views (table, kanban, calendar, document, canvas, …) out of data-browser into their own package. A views package can export a single registerViews(registry) instead of ResourcePage.tsx and tableViewKinds.ts importing each view. It also lets apps and plugins register views the same way as built-ins, in one place, and makes switching between views behave the same everywhere.
Done when
selectComponent and the Website/app/plugin special cases in ResourcePage.tsx go through the registry.
useActiveView applies ?view= → local override → stored value → class default for page views, folders and table tabs, with each step checked against resolveViews; unit tests cover every step and the fall-through for a view that no longer applies.
Switching views never writes the resource; an explicit "Set as default" does, for tables and folders.
Existing folderDisplayPrefs choices are kept.
The ontology has separate read and edit views; readers never get the edit view, and an empty ontology still opens in edit.
ResourceForm is the resource-form page view for every writable resource; editURL() and the Edit shortcut open it via ?view=, and /app/edit links redirect to it.
Card, grid-item, Inline, row and RTE-embed views go through the same registry.
Problem
Which component renders something is decided in several unrelated places, each with its own hard-coded mapping:
selectComponentinbrowser/data-browser/src/views/ResourcePage.tsx:276, aswitchfrom class URL to page component (Table, Document, Canvas, Folder, Dashboard, …), falling back toResourcePageDefault.ResourcePage.tsx:185-255): Website / WebsiteExport, drive apps, plugin scripts, andgetPluginForClass(iframePluginView). Apps and plugins can't becases because their classes are minted per drive, so they are special-cased around the switch./app/edit(routes/EditRoute.tsx→ResourceForm), reached througheditURL()(helpers/navigation.tsx:80) from the Edit action and its shortcut (actions/resourceActions.tsx:127) and five other places.views/Card/,views/ResourceInline/,ResourceRow.tsx) each have their own class → component mapping.gridItemMapinviews/FolderPage/GridItem/ResourceGridItem.tsx:33(Bookmark, Class, Property, ChatRoom, Document, DocumentV2, File, Article → tile component).chunks/RTE/ResourceExtension/ResourceComponent.tsx:19.chunks/TablePage/tableViewKinds.ts(VIEW_KINDS:table,kanban,calendar,timer,dashboard,issues) plus drive apps offered throughappsForClass(useDriveApps(drive), rowClass)inTableViewTabs.tsx:111. This is a second registry, and plugin views plug in here today, not inResourcePage.viewMapinviews/FolderPage/index.tsx, keyed byDisplayStyleURL.editModestate and an Edit/Read button (views/OntologyPage/OntologyPage.tsx).Choosing between views is also done differently in each place:
?view=useTableView.ts:220)default-viewfolderDisplayPrefsin localStorage)display-styleOther problems:
isA = isAList[0],ResourcePage.tsx:93), so a resource with several classes gets the view of whichever class happens to be listed first, and the views of its other classes can't be reached at all.ResourcePage.tsxortableViewKinds.tsand importing the view there. Views can't be added from outside the data browser, which blocks moving them into their own package (see "Why" below).A registry pattern already exists for new-resource dialogs (
registerNewResourceDialog,components/forms/NewForm/CustomCreateActions/CustomForms/index.ts).Proposal: one registry
One registry keyed by class URL and view type. The view types are
page,card,grid-item,inline,row,embed, androws: a view of a collection of resources (a table's rows, a folder's children), looked up by the row class.priority), not only one. This is the rule from Changing a view's kind replaces the only table view without warning #1806, applied everywhere: "the user should always be able to switch between all the applicable views".matchseescanWrite), so edit views are simply absent for readers.ResourcePageDefault,ResourceCard,DefaultGridItem, the plain Table tab, …).lazy()component, so thechunks/bundle split stays as it is.views/registerViews.ts).Choosing the active view: one rule everywhere
Decided by Michiel. Wherever more than one view applies (a resource page, a table's tabs, a folder), one hook, e.g.
useActiveView(resource, type), picks the active one in this order:?view=in the URL. A link shows what its sender saw, a reload keeps it, back/forward moves between views. Tables already work this way;ShowRoutealready has the parameter.folderDisplayPrefskey is read once so existing choices survive.default-view, a folder'sdisplay-style.resolveViews, with an optional per-view default rule (e.g. an empty ontology opens in its edit view).Rules:
resolveViews. A deleted tab, an uninstalled app, lost write access or an old link falls through to the next step; it never shows an empty page.display-stylecan't be changed from the page).DisplayStyleURL; otherwise the registry id.Ontology: two views instead of a mode
OntologyPagebecomes two registered page views for the Ontology class,ontology-read(class and property cards plus the graph) andontology-edit(the*Writecards and New buttons, requires write access). The Edit/Read button becomes the generic view switcher, so edit mode is linkable (?view=ontology-edit) and remembered per browser.OntologyContextProviderwraps both. The "empty ontology opens in edit" behaviour stays, as the edit view's default rule in step 4.The generic edit form becomes a view
Decided by Michiel.
ResourceFormis registered as the page viewresource-form: it applies to every resource the viewer can write, with the lowest priority, so it is never the default but is always reachable from the view switcher. An ontology then offers three views: read, the ontology editor and the generic form.editURL()returns the resource's own URL with?view=resource-form, so the Edit action, its keyboard shortcut and the five other callers change in one place./app/edit?subject=…keeps working: the route redirects to?view=resource-form, so existing links and bookmarks still open the form.Storage is unchanged
A table tab is still a View resource that stores its kind as the
view-kindstring, plus its own configuration (filters, grouping, …). That string is now a registry id. A folder'sdisplay-stylekeeps itsDisplayStyleURLs, which are the registry ids of the folder's list and grid views. Existing resources keep working with no migration.Registry ids are stable
Ids are stored in View resources and folders, appear in URLs, and are referenced by app-view grants (#1788 / #1849 / #1888, scoped to "tables where this view is installed"). An id can never be renamed or reused once released; renaming it would orphan saved tabs, grants and links. Built-in table ids are exactly today's
view-kindvalues, and a drive app's id stays its subject, as it is today (appViewOfintableViewKinds.ts).Steps
resolveViews, and moveselectComponentand the Website/app/plugin special cases inResourcePage.tsxonto it. No behaviour change.useActiveViewwith the four-step order; use it for page views and folders (list/grid asrowsviews, with the local override migrated), split the ontology into its two views, and registerresource-form(witheditURL()and the/app/editredirect).VIEW_KINDSandappsForClasswithrowsregistrations, makeTableViewTabslistresolveViews(…, 'rows'), and moveuseTableView's selection ontouseActiveView(which adds the local override for tables). Changing a view's kind replaces the only table view without warning #1806 only changes the menu's add-vs-convert logic, not the kind list, so it can land first.Related
rowsviews.view-kind; see "Registry ids are stable".rowsviews.Why
This is the first step toward moving the built-in views (table, kanban, calendar, document, canvas, …) out of
data-browserinto their own package. A views package can export a singleregisterViews(registry)instead ofResourcePage.tsxandtableViewKinds.tsimporting each view. It also lets apps and plugins register views the same way as built-ins, in one place, and makes switching between views behave the same everywhere.Done when
selectComponentand the Website/app/plugin special cases inResourcePage.tsxgo through the registry.useActiveViewapplies?view=→ local override → stored value → class default for page views, folders and table tabs, with each step checked againstresolveViews; unit tests cover every step and the fall-through for a view that no longer applies.folderDisplayPrefschoices are kept.ResourceFormis theresource-formpage view for every writable resource;editURL()and the Edit shortcut open it via?view=, and/app/editlinks redirect to it.VIEW_KINDSandappsForClassare replaced byrowsregistrations; the tab menu listsresolveViews(…, 'rows')(after Changing a view's kind replaces the only table view without warning #1806).resolveViewsconsiders every class of a resource and returns all applicable views, with a test for a multi-class resource.view-kind.views/README.mddocuments how to register a view, the selection order, and the id-stability rule.