@conduction/nextcloud-vue 2.77.0 → 2.79.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundled-packages.json +0 -2
- package/dist/esm/bundled-packages.json +0 -2
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +113 -104
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnAiCompanion/CnAiCompanion.vue2.js +23 -0
- package/dist/esm/components/CnAiCompanion/CnAiCompanion.vue2.js.map +1 -1
- package/dist/esm/components/CnAiCompanion/CnAiCompanion.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +12 -2
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +2 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +117 -0
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +122 -97
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +37 -0
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js +2 -2
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +168 -5
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +140 -92
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +84 -2
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +4 -2
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDateRangePicker/CnDateRangePicker.vue.js +1 -1
- package/dist/esm/components/CnDateRangePicker/CnDateRangePicker.vue2.js +45 -2
- package/dist/esm/components/CnDateRangePicker/CnDateRangePicker.vue2.js.map +1 -1
- package/dist/esm/components/CnDateRangePicker/CnDateRangePicker.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +61 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +173 -5
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +181 -99
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js +29 -11
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/openRegisterSource.js +252 -0
- package/dist/esm/components/CnFilesBrowser/openRegisterSource.js.map +1 -0
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +8 -2
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnFormPage/CnFormPage.vue2.js +4 -1
- package/dist/esm/components/CnFormPage/CnFormPage.vue2.js.map +1 -1
- package/dist/esm/components/CnFormPage/CnFormPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +31 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +3 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +100 -3
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +4 -2
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js +3 -2
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js.map +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js.map +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +36 -0
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +35 -23
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStatusBadge/CnStatusBadge.vue2.js +6 -2
- package/dist/esm/components/CnStatusBadge/CnStatusBadge.vue2.js.map +1 -1
- package/dist/esm/components/CnStatusBadge/CnStatusBadge.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +30 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +13 -0
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
- package/dist/esm/composables/useAppManifest.js +8 -3
- package/dist/esm/composables/useAppManifest.js.map +1 -1
- package/dist/esm/composables/useFormDraft.js +11 -1
- package/dist/esm/composables/useFormDraft.js.map +1 -1
- package/dist/esm/composables/useObjectPresence.js +15 -4
- package/dist/esm/composables/useObjectPresence.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +68 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +113 -2
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +526 -16
- package/dist/esm/utils/badgeVariants.js +32 -0
- package/dist/esm/utils/badgeVariants.js.map +1 -0
- package/dist/esm/utils/boardDate.js +193 -0
- package/dist/esm/utils/boardDate.js.map +1 -0
- package/dist/esm/utils/dynamicProperties.js +5 -1
- package/dist/esm/utils/dynamicProperties.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +57 -6
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +2569 -811
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +526 -16
- package/l10n/en.json +68 -1
- package/l10n/nl.json +113 -2
- package/package.json +1 -1
- package/scripts/build-validators.js +26 -0
- package/src/components/CnActionsBar/CnActionsBar.vue +48 -39
- package/src/components/CnAiCompanion/CnAiCompanion.vue +23 -0
- package/src/components/CnAppRoot/CnAppRoot.vue +12 -2
- package/src/components/CnBoardView/CnBoardView.vue +2 -1
- package/src/components/CnCellRenderer/CnCellRenderer.md +1 -0
- package/src/components/CnCellRenderer/CnCellRenderer.vue +169 -10
- package/src/components/CnChartWidget/CnChartWidget.vue +39 -2
- package/src/components/CnDashboardPage/CnDashboardPage.vue +248 -8
- package/src/components/CnDataTable/CnDataTable.md +2 -0
- package/src/components/CnDataTable/CnDataTable.vue +88 -4
- package/src/components/CnDateRangePicker/CnDateRangePicker.vue +44 -1
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +61 -1
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +232 -11
- package/src/components/CnFilesBrowser/filesBrowser.js +43 -2
- package/src/components/CnFilesBrowser/index.js +1 -0
- package/src/components/CnFilesBrowser/openRegisterSource.js +248 -0
- package/src/components/CnFormDialog/CnFormDialog.vue +8 -2
- package/src/components/CnFormPage/CnFormPage.vue +4 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +33 -3
- package/src/components/CnObjectSidebar/CnFilesTab.vue +103 -4
- package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +3 -2
- package/src/components/CnSavedViewsControl/CnSavedViewsControl.vue +1 -1
- package/src/components/CnStatWidget/CnStatWidget.vue +52 -2
- package/src/components/CnStatusBadge/CnStatusBadge.vue +6 -2
- package/src/components/CnTabsWidget/CnTabsWidget.vue +30 -1
- package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +13 -0
- package/src/components/index.js +1 -1
- package/src/composables/__tests__/useObjectPresence.spec.js +24 -0
- package/src/composables/useAppManifest.js +8 -3
- package/src/composables/useFormDraft.js +11 -1
- package/src/composables/useObjectPresence.js +15 -4
- package/src/css/badge.css +31 -0
- package/src/css/kpi-card.css +51 -0
- package/src/css/look-board-detail.css +10 -0
- package/src/css/look-board-index.css +128 -14
- package/src/css/look-board.css +85 -0
- package/src/css/table.css +37 -0
- package/src/index.js +4 -0
- package/src/schemas/app-manifest-v2.schema.json +50 -5
- package/src/utils/badgeVariants.js +29 -0
- package/src/utils/boardDate.js +189 -0
- package/src/utils/dynamicProperties.js +5 -1
- package/src/utils/validateManifestV2.compiled.js +57 -2
- package/dist/esm/_virtual/equal.js +0 -4
- package/dist/esm/_virtual/equal.js.map +0 -1
- package/dist/esm/_virtual/ucs2length.js +0 -4
- package/dist/esm/_virtual/ucs2length.js.map +0 -1
- package/dist/esm/node_modules/ajv/dist/runtime/equal.js +0 -9
- package/dist/esm/node_modules/ajv/dist/runtime/equal.js.map +0 -1
- package/dist/esm/node_modules/ajv/dist/runtime/ucs2length.js +0 -25
- package/dist/esm/node_modules/ajv/dist/runtime/ucs2length.js.map +0 -1
- package/dist/esm/node_modules/fast-deep-equal/index.js +0 -47
- package/dist/esm/node_modules/fast-deep-equal/index.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnWidgetObjectTable.vue2.js","sources":["../../../../src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue"],"sourcesContent":["<!--\n CnWidgetObjectTable — built-in v2 widget wrapping CnDataTable.\n\n Referenced in v2 manifests via `widgetKey: \"object-table\"`. Renders the\n table on the shared CnWidgetWrapper chrome (title + standard overflow\n Actions menu: Refresh / Documentation / Request a feature) and forwards\n all data props to CnDataTable so the table itself is a transparent\n pass-through.\n\n ADR-049 enrichment: a declarative token-resolved `source` drives\n CnDataTable's existing self-fetch (externally supplied `rows` always win),\n the compact-list surface (hideHeader / borderless / rowRoute /\n viewAllRoute / emptyText / rowIcon / #footer) passes straight through,\n and a declarative `actions[]` renders row-scoped actions via CnRowActions\n plus widget-scoped `object-op` create affordances in the footer.\n\n Spec: REQ-MVR-006 (manifest-v2-renderer) — built-in widget: object-table\n Spec: openspec/changes/list-widget-enrichment/specs/manifest-v2-renderer/spec.md\n-->\n<template>\n\t<!-- `refreshing` mirrors the endpoint fetch so the chrome's Refresh\n\t action spins (and stays disabled) for the real duration of an\n\t in-flight refetch. -->\n\t<component\n\t\t:is=\"hideWrapper ? 'CnWidgetHostShell' : 'CnWidgetWrapper'\"\n\t\tv-bind=\"hideWrapper ? {} : { title, widgetId, documentationUrl, flush: true, refreshing: epLoading }\">\n\t\t<div class=\"cn-widget-object-table\">\n\t\t\t<CnDataTable ref=\"dataTable\" v-bind=\"{ ...innerProps, ...$attrs }\">\n\t\t\t\t<!-- Forward every host-supplied CnDataTable scoped slot verbatim\n\t\t\t\t (#footer { total, shown }, #empty, #column-<key>, …). The\n\t\t\t\t #row-actions slot is NOT forwarded while the widget renders\n\t\t\t\t its own declarative actions[] menu. -->\n\t\t\t\t<template v-for=\"(_, name) in forwardedScopedSlots\" #[name]=\"slotData\">\n\t\t\t\t\t<!-- @slot Any CnDataTable scoped slot, forwarded verbatim — e.g. #footer ({ total, shown }), #empty, #column-<key>. #row-actions is withheld while the declarative actions[] menu renders. -->\n\t\t\t\t\t<slot :name=\"name\" v-bind=\"slotData\" />\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"rowScopedActions.length > 0\" #row-actions=\"{ row }\">\n\t\t\t\t\t<CnRowActions :actions=\"mappedRowActions\" :row=\"row\" />\n\t\t\t\t</template>\n\t\t\t</CnDataTable>\n\n\t\t\t<!-- Widget-scoped object-op create affordance: `create` has no row\n\t\t\t to mutate, so it renders in the list footer, never inside the\n\t\t\t per-row actions menu (ADR-049 / list-widget-enrichment). -->\n\t\t\t<div v-if=\"createActions.length > 0\" class=\"cn-widget-object-table__footer\">\n\t\t\t\t<NcButton\n\t\t\t\t\tv-for=\"action in createActions\"\n\t\t\t\t\t:key=\"action.id || action.label\"\n\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t:data-testid=\"`cn-widget-create-${action.id || 'action'}`\"\n\t\t\t\t\t@click=\"onActionTriggered(action, null)\">\n\t\t\t\t\t<template v-if=\"action.icon\" #icon>\n\t\t\t\t\t\t<CnIcon :name=\"action.icon\" :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ action.label }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\n\t\t\t<!-- A rejected write (OpenRegister RBAC) surfaces here — local row\n\t\t\t state is never mutated on failure (the store only writes its\n\t\t\t caches on success). -->\n\t\t\t<p v-if=\"opError\" class=\"cn-widget-object-table__error\" data-testid=\"cn-widget-object-table-error\">\n\t\t\t\t{{ opError }}\n\t\t\t</p>\n\n\t\t\t<CnConfirmDialog\n\t\t\t\tv-if=\"confirmAction\"\n\t\t\t\tref=\"confirmDialog\"\n\t\t\t\t:dialogTitle=\"confirmAction.label\"\n\t\t\t\t:message=\"confirmMessage\"\n\t\t\t\t:variant=\"confirmAction.op === 'delete' ? 'error' : 'primary'\"\n\t\t\t\t:confirmLabel=\"confirmAction.label\"\n\t\t\t\t@confirm=\"onConfirmConfirmed\"\n\t\t\t\t@close=\"closeConfirm\" />\n\t\t</div>\n\t</component>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton } from '@nextcloud/vue'\nimport { inject, ref } from 'vue'\nimport CnConfirmDialog from '../../dialogs/CnConfirmDialog.vue'\nimport CnDataTable from '../CnDataTable/CnDataTable.vue'\nimport { useEndpointSource } from '../../composables/useEndpointSource.js'\nimport { useObjectStore } from '../../store/useObjectStore.js'\nimport { dispatchAction, resolveObjectOpType } from '../../utils/actionsDispatcher.js'\nimport { resolveObjectTokenContext } from '../../utils/detailObjectContext.js'\nimport { dropOptionalUnresolved, hasUnresolvedTokens, resolveFilterTokens } from '../../utils/resolveFilterTokens.js'\nimport { compareVisibleWhen, readVisibleWhenPath } from '../../utils/visibleWhen.js'\nimport { CnIcon } from '../CnIcon/index.js'\nimport { CnRowActions } from '../CnRowActions/index.js'\nimport { CnWidgetWrapper } from '../CnWidgetWrapper/index.js'\n// Chrome-less pass-through used when `hideWrapper` is set (see hostShell.js\n// for why it lives in its own module).\nimport { CnWidgetHostShell } from './hostShell.js'\n\n/**\n * CnWidgetObjectTable — built-in v2 widget wrapping CnDataTable.\n *\n * Renders a data table on the shared CnWidgetWrapper chrome, which supplies\n * the widget title and the standard overflow Actions menu (Refresh /\n * Documentation / Request a feature). All data props are forwarded to the\n * inner CnDataTable; the chrome props (`title`, `documentationUrl`,\n * `widgetId`) are consumed by the wrapper and not passed down.\n *\n * A declarative `source` makes the widget self-fetch from OpenRegister:\n * `source.filter` is token-resolved with the shared `resolveFilterTokens`\n * grammar (`@me`, `@today`, `@workspace.*`, `?`-optional clauses dropped\n * when unresolved via `dropOptionalUnresolved`), `source.order` /\n * `source.limit` drive the fetch, and an `@resolve:` register sentinel is\n * passed through unexpanded (resolution is the host loader's job).\n * Externally supplied `rows` always win — the pre-existing pass-through\n * interface is unchanged.\n *\n * Wave 2 (#91): a declarative `endpointSource` (`{ url, method?, params?,\n * responsePath? }`) is the alternative to the OpenRegister `source` — rows\n * come from an app REST endpoint's payload at `responsePath`, fetched by the\n * shared `useEndpointSource` engine (token-resolved params, request dedup +\n * short-TTL cache, `cn:page:refresh` / `cn:widget:refresh` subscription).\n * Exactly one of `source` | `endpointSource`; columns / formatters /\n * `rowRoute` / `actions` apply unchanged on top of endpoint rows.\n *\n * A declarative `actions[]` array renders `patch` / `delete` `object-op`\n * actions (and reused `handler` / `open-modal` / `open-page` / `navigate`\n * actions) per row through `CnRowActions`; an `object-op` `create` action\n * renders as a widget-scoped footer affordance. `delete` is ALWAYS\n * confirm-gated through `CnConfirmDialog`; `patch` / `create` confirm only\n * on `confirm: true`. Mutations dispatch via the shared object store —\n * the manifest declares intent only, OpenRegister RBAC is the authority,\n * and a rejected write surfaces as an error without local mutation.\n *\n * A declarative `rowClass` (#91) — `[{ when: { field, op?, value }, class }]` reusing\n * the shared `visibleWhen` predicate grammar — is compiled into CnDataTable's\n * `rowClass` function so overdue / at-risk rows can be highlighted from the manifest\n * (a host-supplied `rowClass` FUNCTION still passes straight through).\n */\nexport default {\n\tname: 'CnWidgetObjectTable',\n\n\tcomponents: { CnDataTable, CnWidgetWrapper, CnWidgetHostShell, CnRowActions, CnIcon, NcButton, CnConfirmDialog },\n\n\tinject: {\n\t\t/**\n\t\t * Detail-page object context (`{ objectId, object, register, schema }`)\n\t\t * provided by CnDetailPage — enables `@objectId` / `@object.<field>`\n\t\t * tokens in `source.filter`. Null on dashboards.\n\t\t */\n\t\tcnObjectContext: { default: null },\n\t\t/**\n\t\t * v2 slot-grid detail context holder (`{ value: { objectData, schema,\n\t\t * objectType, objectId, register, store } | null }`) provided by\n\t\t * CnPageRenderer — backfills the object token context so\n\t\t * `@objectId` / `@object.<field>` resolve on detail surfaces where\n\t\t * CnDetailPage is not an ancestor (#91 Wave 3).\n\t\t */\n\t\tcnDetailObjectContext: { default: null },\n\t\t/**\n\t\t * Page-level workspace context (reactive `ref({})`) provided by\n\t\t * CnDashboardPage — enables `@workspace.<key>` tokens in\n\t\t * `source.filter`. Null on pages that don't provide it.\n\t\t */\n\t\tcnWorkspaceContext: { default: null },\n\t\t/**\n\t\t * Pre-bound `dispatchAction` provided by CnPageRenderer (router,\n\t\t * registry, handlers, openModal already wired). Used for the reused\n\t\t * non-mutating action types; falls back to a direct `dispatchAction`\n\t\t * call when mounted outside a CnPageRenderer tree.\n\t\t */\n\t\tcnDispatchAction: { default: null },\n\t},\n\n\tinheritAttrs: false,\n\n\tprops: {\n\t\t/**\n\t\t * Widget title shown in the CnWidgetWrapper header.\n\t\t */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Table'),\n\t\t},\n\n\t\t/**\n\t\t * Documentation link surfaced in the widget's overflow Actions menu.\n\t\t * Empty (the default) hides the Documentation item; the Refresh and\n\t\t * Request-a-feature items always render.\n\t\t */\n\t\tdocumentationUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Stable id forwarded to the widget chrome for the Refresh /\n\t\t * Request-a-feature payloads.\n\t\t */\n\t\twidgetId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Render content-only, WITHOUT the widget's own CnWidgetWrapper\n\t\t * chrome. Set by hosts that already provide the card chrome — e.g.\n\t\t * CnDashboardPage's dashboard widget grid, whose registry branch\n\t\t * wraps every catalog widget in its own CnWidgetWrapper (a naive\n\t\t * mount would double-card). Default `false` keeps the pre-existing\n\t\t * self-chromed v2 rendering byte-for-byte.\n\t\t */\n\t\thideWrapper: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Register slug. Forwarded to CnDataTable. */\n\t\tregister: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Schema slug. Forwarded to CnDataTable. */\n\t\tschema: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Column definitions. Forwarded to CnDataTable — bare string keys OR\n\t\t * the object form (`{ key, label, sortable, width, cellClass,\n\t\t * formatter, widget, format, aggregate }`) both pass straight through.\n\t\t *\n\t\t * @type {Array<object|string>}\n\t\t */\n\t\tcolumns: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Rows array. Forwarded to CnDataTable. External rows always win over `source`. */\n\t\trows: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Loading state. Forwarded to CnDataTable. */\n\t\tloading: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Declarative self-fetch source (ADR-049). When set and no external\n\t\t * `rows` are supplied, the widget resolves `filter` @-tokens and\n\t\t * drives CnDataTable's existing self-fetch with the resolved filter,\n\t\t * `order`, and `limit`. `register` MAY carry an `@resolve:` sentinel —\n\t\t * it is passed through unexpanded. `extend` (#91 Wave 3) forwards\n\t\t * OpenRegister `_extend[]` values (e.g. `[\"calculations\"]`) on the\n\t\t * fetch so virtual/declarative calc fields ride along and render as\n\t\t * ordinary columns. Default `null` keeps the pre-existing\n\t\t * pass-through behaviour byte-for-byte.\n\t\t *\n\t\t * @type {{register?: string, schema?: string, filter?: object, order?: object, limit?: number, extend?: string[]}|null}\n\t\t */\n\t\tsource: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Endpoint data binding (Wave 2, #91) — the alternative to the\n\t\t * OpenRegister `source` for rows an app REST endpoint computes (e.g. a\n\t\t * per-source performance report). Rows come from the payload at\n\t\t * `responsePath` (a dot-path; the plucked value must be an array —\n\t\t * anything else renders as empty). `params` values use the shared\n\t\t * filter-token grammar (`@workspace.dateFrom?`, `@me`, `@today±Nd`, …)\n\t\t * and re-resolve + refetch when the page context changes. Requests are\n\t\t * deduped + short-TTL cached per (url+params) by the shared\n\t\t * `useEndpointSource` engine, which also subscribes to\n\t\t * `cn:page:refresh` / `cn:widget:refresh`. Exactly one of `source` |\n\t\t * `endpointSource` (validator-enforced); external `rows` still win\n\t\t * over both. `columns` / formatters / `rowRoute` / `actions` apply\n\t\t * unchanged on top of endpoint rows.\n\t\t *\n\t\t * @type {{url: string, method?: string, params?: object, responsePath?: string}|null}\n\t\t */\n\t\tendpointSource: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Declarative actions (unified manifest action shape — `handler` |\n\t\t * `open-modal` | `open-page` | `navigate` | `object-op`). `object-op`\n\t\t * `patch` / `delete` (and every non-mutating type) render per row via\n\t\t * CnRowActions; `object-op` `create` renders as a widget-scoped footer\n\t\t * affordance. `delete` always confirms; `patch` / `create` confirm on\n\t\t * `confirm: true`. Authorization-shaped fields on an action have no\n\t\t * effect — OpenRegister RBAC is the only authority.\n\t\t *\n\t\t * @type {Array<object>}\n\t\t */\n\t\tactions: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Hide the column-header row. Forwarded to CnDataTable — the compact\n\t\t * dashboard-list surface.\n\t\t */\n\t\thideHeader: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Drop CnDataTable's card chrome (border, radius, shadow) so the table\n\t\t * sits flush inside the widget card. Forwarded to CnDataTable.\n\t\t */\n\t\tborderless: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Route NAME for row-click navigation. Mapped onto CnDataTable's\n\t\t * `rowClickRoute` as `{ name: rowRoute, params: { id } }` with the\n\t\t * row's `id` (or `@self.id`). Empty (the default) = no navigation.\n\t\t */\n\t\trowRoute: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Client-side row cap, forwarded to CnDataTable (`0` = all rows).\n\t\t * This is what makes `viewAllRoute` reachable for `endpointSource`\n\t\t * tables: the endpoint's full payload stays the `totalRowCount`\n\t\t * while CnDataTable renders only the first `limit` rows, so the\n\t\t * \"View all\" footer condition (total > shown) can finally hold.\n\t\t * The OpenRegister `source` mode keeps using `source.limit` (an\n\t\t * explicit assignment that wins over this passthrough).\n\t\t */\n\t\tlimit: {\n\t\t\ttype: Number,\n\t\t\tdefault: 0,\n\t\t},\n\n\t\t/**\n\t\t * vue-router route object for the \"View all\" footer link. Forwarded\n\t\t * to CnDataTable (footer shows when the rows are a `limit`-ed subset).\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tviewAllRoute: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Pre-translated \"View all\" footer label. Forwarded to CnDataTable. */\n\t\tviewAllLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: undefined,\n\t\t},\n\n\t\t/** Text shown when there are no rows. Forwarded to CnDataTable. */\n\t\temptyText: {\n\t\t\ttype: String,\n\t\t\tdefault: undefined,\n\t\t},\n\n\t\t/**\n\t\t * App wording for an unavailable personal lens (a `source.filter._recent`\n\t\t * tile on an instance that does not log reads), keyed `<lens>.<reason>`\n\t\t * or `<reason>`. Forwarded to CnDataTable, which shows the library's\n\t\t * object-neutral text without it.\n\t\t *\n\t\t * @spec openspec/changes/lens-says-why-it-is-empty/specs/personal-lens-availability/spec.md#requirement-an-unavailable-lens-explains-its-empty-page\n\t\t */\n\t\tlensReasonTexts: {\n\t\t\ttype: Object,\n\t\t\tdefault: undefined,\n\t\t},\n\n\t\t/**\n\t\t * Leading per-row icon: a static MDI icon name string, or a function\n\t\t * `(row) => iconName`. Forwarded to CnDataTable.\n\t\t *\n\t\t * @type {string|((row: object) => string)|null}\n\t\t */\n\t\trowIcon: {\n\t\t\ttype: [String, Function],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Per-row CSS class binding (#91). Two forms:\n\t\t * - a FUNCTION `(row) => string` — passed straight through to\n\t\t * CnDataTable's `rowClass` (the pre-existing host-supplied contract).\n\t\t * - an ARRAY of declarative rules `[{ when: { field, op?, value }, class }]`\n\t\t * compiled here into that function. Each rule adds its `class` to a\n\t\t * row when the shared `visibleWhen` predicate holds against the row:\n\t\t * `field` is a dot-path into the row, `op` is `eq|neq|gt|gte|lt|lte`\n\t\t * (default `eq`), `value` is the literal right-hand side. Rules are\n\t\t * evaluated in order and every matching `class` is space-joined, so an\n\t\t * overdue / at-risk row can be highlighted declaratively from a\n\t\t * manifest with no bespoke function. Default `null` = no row class.\n\t\t *\n\t\t * @type {((row: object) => string)|Array<{when: {field: string, op?: string, value: unknown}, class: string}>|null}\n\t\t */\n\t\trowClass: {\n\t\t\ttype: [Function, Array],\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['object-op'],\n\n\tsetup(props) {\n\t\t// Endpoint binding (Wave 2, #91): the shared useEndpointSource engine\n\t\t// owns token resolution, request dedup + TTL caching, and the\n\t\t// cn:page:refresh / cn:widget:refresh subscriptions (the widget's own\n\t\t// `widgetId` is matched against widget-scoped refresh payloads).\n\t\t// No-op while `endpointSource` is null, so the OpenRegister `source`\n\t\t// self-fetch path is untouched. Injects re-read in setup (same\n\t\t// resolution as the Options `inject` block).\n\t\tconst objectCtxRaw = inject('cnObjectContext', null)\n\t\tconst detailCtxRaw = inject('cnDetailObjectContext', null)\n\t\tconst workspaceRaw = inject('cnWorkspaceContext', ref(null))\n\t\tconst appConfigRaw = inject('cnAppConfig', ref({}))\n\t\tconst unwrap = (v) => ((v && typeof v === 'object' && 'value' in v) ? v.value : v)\n\t\t// External rows always win — and suppress the request entirely (the\n\t\t// pre-existing \"when rows are provided, no API calls are made\"\n\t\t// contract), so the getter resolves to null while `rows` are supplied.\n\t\tconst { data, loading, error, refetch } = useEndpointSource(\n\t\t\t() => ((props.rows && props.rows.length > 0) ? null : props.endpointSource),\n\t\t\t{\n\t\t\t\tctx: () => ({\n\t\t\t\t\t...(resolveObjectTokenContext(objectCtxRaw, detailCtxRaw) || {}),\n\t\t\t\t\tworkspace: unwrap(workspaceRaw) || {},\n\t\t\t\t\tconfig: unwrap(appConfigRaw) || {},\n\t\t\t\t}),\n\t\t\t\twidgetId: () => props.widgetId,\n\t\t\t},\n\t\t)\n\t\treturn { epData: data, epLoading: loading, epError: error, epRefetch: refetch }\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t/** Error message from the last rejected object-op write ('' = none). */\n\t\t\topError: '',\n\t\t\t/** @type {object|null} The object-op action awaiting confirmation (null = no dialog). */\n\t\t\tconfirmAction: null,\n\t\t\t/** @type {object|null} The row the pending confirm-gated action targets. */\n\t\t\tconfirmRow: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The merged detail-page object context for token resolution — both\n\t\t * detail-surface injects, holder fields backfilling (#91 Wave 3) —\n\t\t * or null on surfaces (dashboards) that don't provide one.\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tobjectCtx() {\n\t\t\treturn resolveObjectTokenContext(this.cnObjectContext, this.cnDetailObjectContext)\n\t\t},\n\n\t\t/**\n\t\t * The unwrapped workspace context bag (or null). Vue 2.7 inject may\n\t\t * hand back a raw ref; unwrap `.value` for token resolution.\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tworkspaceCtx() {\n\t\t\tconst c = this.cnWorkspaceContext\n\t\t\tif (!c) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn (typeof c === 'object' && 'value' in c) ? c.value : c\n\t\t},\n\n\t\t/**\n\t\t * Token-resolution context merged from the detail-page object context\n\t\t * and the page-level workspace bag (`@workspace.<key>`).\n\t\t *\n\t\t * @return {object}\n\t\t */\n\t\ttokenCtx() {\n\t\t\tconst base = this.objectCtx ? { ...this.objectCtx } : {}\n\t\t\tbase.workspace = this.workspaceCtx || {}\n\t\t\treturn base\n\t\t},\n\n\t\t/**\n\t\t * `source.filter` with every @-token resolved against `tokenCtx`, then\n\t\t * with any UNRESOLVED OPTIONAL clause (`@workspace.<key>?`) dropped —\n\t\t * an optional clause simply shows all rows until its context is set.\n\t\t *\n\t\t * @return {object}\n\t\t */\n\t\tresolvedFilter() {\n\t\t\treturn dropOptionalUnresolved(resolveFilterTokens((this.source && this.source.filter) || {}, this.tokenCtx))\n\t\t},\n\n\t\t/**\n\t\t * Whether a REQUIRED context token in `source.filter` is still\n\t\t * unresolved — the widget then skips the fetch instead of querying the\n\t\t * whole register.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\twaitingForContext() {\n\t\t\treturn !!this.source && hasUnresolvedTokens(this.resolvedFilter)\n\t\t},\n\n\t\t/**\n\t\t * Whether the Wave-2 `endpointSource` drives the rows: a usable\n\t\t * endpoint config and no external rows (external rows always win).\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tendpointActive() {\n\t\t\treturn !!(this.endpointSource && this.endpointSource.url)\n\t\t\t\t&& (!this.rows || this.rows.length === 0)\n\t\t},\n\n\t\t/**\n\t\t * Rows resolved from the endpoint payload. The payload (after the\n\t\t * `responsePath` pluck the shared engine applies) must be an ARRAY;\n\t\t * anything else renders as an empty table rather than crashing the\n\t\t * column renderer.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\tendpointRows() {\n\t\t\treturn Array.isArray(this.epData) ? this.epData : []\n\t\t},\n\n\t\t/**\n\t\t * Whether the declarative `source` drives a self-fetch: a usable\n\t\t * source, no external rows (external rows always win), no active\n\t\t * endpoint binding (exactly-one-of — endpointSource wins when both\n\t\t * slip past the validator), and no unresolved required token.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tselfFetchActive() {\n\t\t\treturn !!(this.source && this.source.register && this.source.schema)\n\t\t\t\t&& (!this.rows || this.rows.length === 0)\n\t\t\t\t&& !this.endpointActive\n\t\t\t\t&& !this.waitingForContext\n\t\t},\n\n\t\t/**\n\t\t * Query params handed to CnDataTable's self-fetch: the resolved filter\n\t\t * as direct field params (OpenRegister object-search shape),\n\t\t * `_order[field]` entries from `source.order`, and — when\n\t\t * `source.limit` is set — `_limit` of limit + 1 so CnDataTable's\n\t\t * View-all footer can detect that more rows exist while the display\n\t\t * caps at `limit`.\n\t\t *\n\t\t * IN-lists: an ARRAY filter value (either directly on the field or via\n\t\t * the `{ in: [...] }` operator form) is emitted as the bare field with\n\t\t * the array value, which axios serializes as repeated bracket params\n\t\t * (`field[]=a&field[]=b`) — the same shape `buildQueryString` /\n\t\t * `useObjectStore` send and the only IN form OpenRegister parses (a\n\t\t * `field[in]`-style key is NOT understood server-side). Empty arrays\n\t\t * are skipped (no constraint).\n\t\t *\n\t\t * @return {object}\n\t\t */\n\t\tresolvedFetchParams() {\n\t\t\tconst src = this.source || {}\n\t\t\tconst params = {}\n\t\t\tfor (const [field, dir] of Object.entries(src.order || {})) {\n\t\t\t\tparams[`_order[${field}]`] = dir === 'desc' ? 'desc' : 'asc'\n\t\t\t}\n\t\t\tif (typeof src.limit === 'number' && src.limit > 0) {\n\t\t\t\tparams._limit = src.limit + 1\n\t\t\t}\n\t\t\t// `source.extend` (#91 Wave 3): forwarded as OR's repeated\n\t\t\t// `_extend[]` param so virtual/declarative fields (e.g.\n\t\t\t// `calculations` — procest daysOverdue) ride the fetch and are\n\t\t\t// displayable as ordinary columns. Serialized by the shared\n\t\t\t// query builder (array value → repeated `key[]`).\n\t\t\tif (Array.isArray(src.extend) && src.extend.length > 0) {\n\t\t\t\tparams._extend = src.extend.filter((e) => typeof e === 'string' && e !== '')\n\t\t\t}\n\t\t\tconst filter = this.resolvedFilter\n\t\t\tif (filter && typeof filter === 'object') {\n\t\t\t\tfor (const [k, v] of Object.entries(filter)) {\n\t\t\t\t\tif (Array.isArray(v)) {\n\t\t\t\t\t\tif (v.length > 0) {\n\t\t\t\t\t\t\tparams[k] = v\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (v && typeof v === 'object') {\n\t\t\t\t\t\tfor (const [op, ov] of Object.entries(v)) {\n\t\t\t\t\t\t\tif (op === 'in' && Array.isArray(ov)) {\n\t\t\t\t\t\t\t\t// OR's IN form is the bare repeated field param.\n\t\t\t\t\t\t\t\tif (ov.length > 0) {\n\t\t\t\t\t\t\t\t\tparams[k] = ov\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tparams[`${k}[${op}]`] = ov\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (v !== '' && v !== null && v !== undefined) {\n\t\t\t\t\t\tparams[k] = v\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn params\n\t\t},\n\n\t\t/**\n\t\t * `$props` minus the chrome props (`title`, `documentationUrl`,\n\t\t * `widgetId`, `hideWrapper`) and the widget-only props (`source`,\n\t\t * `endpointSource`, `actions`, `rowRoute`), so they are consumed here\n\t\t * and never forwarded to the inner CnDataTable. `undefined` values are\n\t\t * dropped so CnDataTable's own prop defaults apply. When the\n\t\t * declarative `source` is active it supplies `register` / `schemaId` /\n\t\t * `fetchParams` / `limit`; an `@resolve:` register sentinel passes\n\t\t * through unexpanded. When the Wave-2 `endpointSource` is active it\n\t\t * supplies the resolved `rows` + `loading` instead — columns,\n\t\t * formatters, and row navigation apply unchanged on top.\n\t\t *\n\t\t * @return {object}\n\t\t */\n\t\tinnerProps() {\n\t\t\tconst { title, documentationUrl, widgetId, hideWrapper, source, endpointSource, actions, rowRoute, rowClass, schema, ...rest } = this.$props\n\t\t\tconst inner = {}\n\t\t\tfor (const [k, v] of Object.entries(rest)) {\n\t\t\t\tif (v !== undefined) {\n\t\t\t\t\tinner[k] = v\n\t\t\t\t}\n\t\t\t}\n\t\t\t// This widget's `schema` is a slug, while CnDataTable's `schema` is a\n\t\t\t// schema object, so it is not forwarded. Nor is it mapped to `schemaId`:\n\t\t\t// that would turn on CnDataTable's unscoped self-fetch whenever the\n\t\t\t// widget's own rows are empty. Self-fetch comes from `source` alone.\n\t\t\t// `rowClass` is consumed here (function or declarative rules[]) and\n\t\t\t// forwarded to CnDataTable as a compiled `(row) => class` function.\n\t\t\tif (this.compiledRowClass) {\n\t\t\t\tinner.rowClass = this.compiledRowClass\n\t\t\t}\n\t\t\tif (this.selfFetchActive) {\n\t\t\t\tinner.register = this.source.register\n\t\t\t\tinner.schemaId = this.source.schema\n\t\t\t\tinner.fetchParams = this.resolvedFetchParams\n\t\t\t\tif (typeof this.source.limit === 'number' && this.source.limit > 0) {\n\t\t\t\t\tinner.limit = this.source.limit\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (this.endpointActive) {\n\t\t\t\tinner.rows = this.endpointRows\n\t\t\t\tinner.loading = this.epLoading || this.loading\n\t\t\t}\n\t\t\tif (this.rowRoute) {\n\t\t\t\tinner.rowClickRoute = this.rowRouteFn\n\t\t\t}\n\t\t\treturn inner\n\t\t},\n\n\t\t/**\n\t\t * The effective `rowClass` function forwarded to CnDataTable. A\n\t\t * host-supplied FUNCTION passes straight through; a declarative\n\t\t * rules[] array is compiled into a `(row) => string` that space-joins\n\t\t * every rule whose shared `visibleWhen` predicate holds against the\n\t\t * row (`field` dot-path + `op` + `value`). Null when `rowClass` is\n\t\t * unset or an empty array — the widget then forwards nothing so\n\t\t * CnDataTable's own default applies.\n\t\t *\n\t\t * @return {?(row: object) => string} The row-class function, or null.\n\t\t */\n\t\tcompiledRowClass() {\n\t\t\tconst rc = this.rowClass\n\t\t\tif (typeof rc === 'function') {\n\t\t\t\treturn rc\n\t\t\t}\n\t\t\tif (!Array.isArray(rc) || rc.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst rules = rc\n\t\t\treturn (row) => {\n\t\t\t\tconst classes = []\n\t\t\t\tfor (const rule of rules) {\n\t\t\t\t\tif (!rule || !rule.class) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tconst when = rule.when || {}\n\t\t\t\t\tconst actual = readVisibleWhenPath(row, when.field)\n\t\t\t\t\tif (compareVisibleWhen(actual, when.op || 'eq', when.value)) {\n\t\t\t\t\t\tclasses.push(rule.class)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn classes.join(' ')\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Row-click navigation function derived from the `rowRoute` route\n\t\t * name (CnDataTable's `rowClickRoute` shape).\n\t\t *\n\t\t * @return {(row: object) => (object|null)} A vue-router location, or null for a row with no id.\n\t\t */\n\t\trowRouteFn() {\n\t\t\treturn (row) => {\n\t\t\t\tconst id = row && (row.id || (row['@self'] && row['@self'].id))\n\t\t\t\treturn id ? { name: this.rowRoute, params: { id: String(id) } } : null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Actions rendered per row: every non-`object-op` type plus\n\t\t * `object-op` `patch` / `delete` (they mutate THAT row's object).\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\trowScopedActions() {\n\t\t\treturn (this.actions || []).filter((a) => a && (a.type !== 'object-op' || a.op === 'patch' || a.op === 'delete'))\n\t\t},\n\n\t\t/**\n\t\t * `object-op` `create` actions — widget-scoped (no row to mutate), so\n\t\t * they render in the list footer, never in the row menu.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\tcreateActions() {\n\t\t\treturn (this.actions || []).filter((a) => a && a.type === 'object-op' && a.op === 'create')\n\t\t},\n\n\t\t/**\n\t\t * Row-scoped actions mapped onto the CnRowActions shape: label + icon\n\t\t * pass through, `object-op` `delete` renders destructive, and the\n\t\t * handler routes back through the widget's confirm-gated dispatcher.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\tmappedRowActions() {\n\t\t\treturn this.rowScopedActions.map((action) => ({\n\t\t\t\tlabel: action.label,\n\t\t\t\ticon: action.icon,\n\t\t\t\tdestructive: action.type === 'object-op' && action.op === 'delete',\n\t\t\t\thandler: (row) => this.onActionTriggered(action, row),\n\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * Host-supplied CnDataTable scoped slots to forward (keys only). The\n\t\t * `row-actions` slot is withheld while the widget renders its own\n\t\t * declarative actions menu.\n\t\t *\n\t\t * @return {object}\n\t\t */\n\t\tforwardedScopedSlots() {\n\t\t\tconst out = {}\n\t\t\tfor (const name of Object.keys(this.$slots || {})) {\n\t\t\t\tif (name === 'row-actions' && this.rowScopedActions.length > 0) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tout[name] = true\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\t/**\n\t\t * Confirmation question for the pending confirm-gated action —\n\t\t * destructive copy for `delete`, a generic confirm otherwise.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tconfirmMessage() {\n\t\t\tif (this.confirmAction && this.confirmAction.op === 'delete') {\n\t\t\t\treturn t('nextcloud-vue', 'Are you sure you want to delete this item? This action cannot be undone.')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Are you sure you want to continue?')\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Entry point for every declarative action trigger (row menu or\n\t\t * footer create button). `object-op` `delete` ALWAYS confirms;\n\t\t * `patch` / `create` confirm only when the action sets\n\t\t * `confirm: true`; everything else dispatches immediately.\n\t\t *\n\t\t * @param {object} action The manifest action.\n\t\t * @param {object|null} row The row for row-scoped actions (null for create).\n\t\t * @return {Promise<void>|void}\n\t\t */\n\t\tonActionTriggered(action, row) {\n\t\t\tif (action && action.type === 'object-op' && (action.op === 'delete' || action.confirm === true)) {\n\t\t\t\tthis.confirmAction = action\n\t\t\t\tthis.confirmRow = row\n\t\t\t\treturn\n\t\t\t}\n\t\t\treturn this.runAction(action, row)\n\t\t},\n\n\t\t/**\n\t\t * Confirm-dialog primary button: run the pending action and report\n\t\t * the outcome back into the dialog's result phase.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onConfirmConfirmed() {\n\t\t\tconst outcome = await this.runAction(this.confirmAction, this.confirmRow)\n\t\t\tconst dialog = this.$refs.confirmDialog\n\t\t\tif (dialog) {\n\t\t\t\tdialog.setResult(outcome.ok ? { success: true } : { error: outcome.error })\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Close/cancel the confirm dialog and clear the pending action.\n\t\t */\n\t\tcloseConfirm() {\n\t\t\tthis.confirmAction = null\n\t\t\tthis.confirmRow = null\n\t\t},\n\n\t\t/**\n\t\t * Dispatch an action. Non-mutating types go through the page's\n\t\t * pre-bound `cnDispatchAction` (falling back to a bare\n\t\t * `dispatchAction`); for `handler` actions the row is appended as the\n\t\t * final handler argument so row context reaches the registry function.\n\t\t * `object-op` actions dispatch via the shared object store — a\n\t\t * rejected (RBAC) write surfaces `opError` and mutates NO local state;\n\t\t * a success clears the error and refreshes the self-fetched rows.\n\t\t *\n\t\t * @param {object} action The manifest action.\n\t\t * @param {object|null} row The target row (null for create).\n\t\t * @return {Promise<{ok: boolean, error?: string}>}\n\t\t */\n\t\tasync runAction(action, row) {\n\t\t\tif (!action) {\n\t\t\t\treturn { ok: false, error: '' }\n\t\t\t}\n\t\t\tif (action.type !== 'object-op') {\n\t\t\t\tconst wrapped = (!action.type || action.type === 'handler')\n\t\t\t\t\t? { ...action, args: [...(action.args || []), row] }\n\t\t\t\t\t: action\n\t\t\t\tif (typeof this.cnDispatchAction === 'function') {\n\t\t\t\t\tthis.cnDispatchAction(wrapped)\n\t\t\t\t} else {\n\t\t\t\t\tdispatchAction(wrapped, { router: this.$router || null })\n\t\t\t\t}\n\t\t\t\treturn { ok: true }\n\t\t\t}\n\n\t\t\tconst store = this.getObjectStore()\n\t\t\tconst result = await dispatchAction(action, {\n\t\t\t\tobjectStore: store,\n\t\t\t\tsource: this.source,\n\t\t\t\trow,\n\t\t\t})\n\t\t\tconst ok = !(result === null || result === false || result === undefined)\n\t\t\tif (!ok) {\n\t\t\t\tconst error = this.readStoreError(store)\n\t\t\t\tthis.opError = error\n\t\t\t\treturn { ok: false, error }\n\t\t\t}\n\t\t\tthis.opError = ''\n\t\t\tthis.refresh()\n\t\t\t/**\n\t\t\t * @event object-op Emitted after a successful declarative mutation\n\t\t\t * so a host that supplies external `rows` can refetch them.\n\t\t\t * @type {{action: object, row: (object|null), result: unknown}}\n\t\t\t */\n\t\t\tthis.$emit('object-op', { action, row, result })\n\t\t\treturn { ok: true }\n\t\t},\n\n\t\t/**\n\t\t * Re-run the active fetch: the endpoint binding (force-refetch past\n\t\t * the shared cache) or the inner CnDataTable's self-fetch. No-op when\n\t\t * the widget is fed external rows.\n\t\t */\n\t\trefresh() {\n\t\t\tif (this.endpointActive) {\n\t\t\t\tthis.epRefetch()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst table = this.$refs.dataTable\n\t\t\tif (this.selfFetchActive && table && typeof table.fetchData === 'function') {\n\t\t\t\ttable.fetchData()\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The shared object store instance, or null when no Pinia is active\n\t\t * (`dispatchAction` then warns and no-ops).\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tgetObjectStore() {\n\t\t\ttry {\n\t\t\t\treturn useObjectStore()\n\t\t\t} catch {\n\t\t\t\treturn null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Human-readable message for the last store error on this widget's\n\t\t * source type (RBAC rejection, network failure, …).\n\t\t *\n\t\t * @param {object|null} store The object store used for the dispatch.\n\t\t * @return {string}\n\t\t */\n\t\treadStoreError(store) {\n\t\t\ttry {\n\t\t\t\tif (store && this.source) {\n\t\t\t\t\tconst type = resolveObjectOpType(store, this.source)\n\t\t\t\t\tconst err = store.errors && store.errors[type]\n\t\t\t\t\tif (err && err.message) {\n\t\t\t\t\t\treturn err.message\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// fall through to the generic message\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'The operation was rejected')\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-widget-object-table {\n\twidth: 100%;\n}\n\n.cn-widget-object-table__footer {\n\tdisplay: flex;\n\tgap: 4px;\n\tpadding: 4px 8px;\n\tborder-top: 1px solid var(--color-border);\n}\n\n.cn-widget-object-table__error {\n\tcolor: var(--color-text-error, var(--color-error-text));\n\tfont-size: 0.85em;\n\tmargin: 4px 8px 0;\n}\n</style>\n"],"names":["CnDataTable","CnWidgetWrapper","CnRowActions","CnIcon","CnConfirmDialog","t"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnWidgetObjectTable.vue2.js","sources":["../../../../src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue"],"sourcesContent":["<!--\n CnWidgetObjectTable — built-in v2 widget wrapping CnDataTable.\n\n Referenced in v2 manifests via `widgetKey: \"object-table\"`. Renders the\n table on the shared CnWidgetWrapper chrome (title + standard overflow\n Actions menu: Refresh / Documentation / Request a feature) and forwards\n all data props to CnDataTable so the table itself is a transparent\n pass-through.\n\n ADR-049 enrichment: a declarative token-resolved `source` drives\n CnDataTable's existing self-fetch (externally supplied `rows` always win),\n the compact-list surface (hideHeader / borderless / rowRoute /\n viewAllRoute / emptyText / rowIcon / #footer) passes straight through,\n and a declarative `actions[]` renders row-scoped actions via CnRowActions\n plus widget-scoped `object-op` create affordances in the footer.\n\n Spec: REQ-MVR-006 (manifest-v2-renderer) — built-in widget: object-table\n Spec: openspec/changes/list-widget-enrichment/specs/manifest-v2-renderer/spec.md\n-->\n<template>\n\t<!-- `refreshing` mirrors the endpoint fetch so the chrome's Refresh\n\t action spins (and stays disabled) for the real duration of an\n\t in-flight refetch. -->\n\t<component\n\t\t:is=\"hideWrapper ? 'CnWidgetHostShell' : 'CnWidgetWrapper'\"\n\t\tv-bind=\"hideWrapper ? {} : { title, widgetId, documentationUrl, flush: true, refreshing: epLoading }\">\n\t\t<div class=\"cn-widget-object-table\">\n\t\t\t<CnDataTable ref=\"dataTable\" v-bind=\"{ ...innerProps, ...$attrs }\">\n\t\t\t\t<!-- Forward every host-supplied CnDataTable scoped slot verbatim\n\t\t\t\t (#footer { total, shown }, #empty, #column-<key>, …). The\n\t\t\t\t #row-actions slot is NOT forwarded while the widget renders\n\t\t\t\t its own declarative actions[] menu. -->\n\t\t\t\t<template v-for=\"(_, name) in forwardedScopedSlots\" #[name]=\"slotData\">\n\t\t\t\t\t<!-- @slot Any CnDataTable scoped slot, forwarded verbatim — e.g. #footer ({ total, shown }), #empty, #column-<key>. #row-actions is withheld while the declarative actions[] menu renders. -->\n\t\t\t\t\t<slot :name=\"name\" v-bind=\"slotData\" />\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"rowScopedActions.length > 0\" #row-actions=\"{ row }\">\n\t\t\t\t\t<CnRowActions :actions=\"mappedRowActions\" :row=\"row\" />\n\t\t\t\t</template>\n\t\t\t</CnDataTable>\n\n\t\t\t<!-- Widget-scoped object-op create affordance: `create` has no row\n\t\t\t to mutate, so it renders in the list footer, never inside the\n\t\t\t per-row actions menu (ADR-049 / list-widget-enrichment). -->\n\t\t\t<div v-if=\"createActions.length > 0\" class=\"cn-widget-object-table__footer\">\n\t\t\t\t<NcButton\n\t\t\t\t\tv-for=\"action in createActions\"\n\t\t\t\t\t:key=\"action.id || action.label\"\n\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t:data-testid=\"`cn-widget-create-${action.id || 'action'}`\"\n\t\t\t\t\t@click=\"onActionTriggered(action, null)\">\n\t\t\t\t\t<template v-if=\"action.icon\" #icon>\n\t\t\t\t\t\t<CnIcon :name=\"action.icon\" :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ action.label }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\n\t\t\t<!-- A rejected write (OpenRegister RBAC) surfaces here — local row\n\t\t\t state is never mutated on failure (the store only writes its\n\t\t\t caches on success). -->\n\t\t\t<p v-if=\"opError\" class=\"cn-widget-object-table__error\" data-testid=\"cn-widget-object-table-error\">\n\t\t\t\t{{ opError }}\n\t\t\t</p>\n\n\t\t\t<CnConfirmDialog\n\t\t\t\tv-if=\"confirmAction\"\n\t\t\t\tref=\"confirmDialog\"\n\t\t\t\t:dialogTitle=\"confirmAction.label\"\n\t\t\t\t:message=\"confirmMessage\"\n\t\t\t\t:variant=\"confirmAction.op === 'delete' ? 'error' : 'primary'\"\n\t\t\t\t:confirmLabel=\"confirmAction.label\"\n\t\t\t\t@confirm=\"onConfirmConfirmed\"\n\t\t\t\t@close=\"closeConfirm\" />\n\t\t</div>\n\t</component>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton } from '@nextcloud/vue'\nimport { inject, ref } from 'vue'\nimport CnConfirmDialog from '../../dialogs/CnConfirmDialog.vue'\nimport CnDataTable from '../CnDataTable/CnDataTable.vue'\nimport { useEndpointSource } from '../../composables/useEndpointSource.js'\nimport { useObjectStore } from '../../store/useObjectStore.js'\nimport { dispatchAction, resolveObjectOpType } from '../../utils/actionsDispatcher.js'\nimport { resolveObjectTokenContext } from '../../utils/detailObjectContext.js'\nimport { dropOptionalUnresolved, hasUnresolvedTokens, resolveFilterTokens } from '../../utils/resolveFilterTokens.js'\nimport { compareVisibleWhen, readVisibleWhenPath } from '../../utils/visibleWhen.js'\nimport { CnIcon } from '../CnIcon/index.js'\nimport { CnRowActions } from '../CnRowActions/index.js'\nimport { CnWidgetWrapper } from '../CnWidgetWrapper/index.js'\n// Chrome-less pass-through used when `hideWrapper` is set (see hostShell.js\n// for why it lives in its own module).\nimport { CnWidgetHostShell } from './hostShell.js'\n\n/**\n * CnWidgetObjectTable — built-in v2 widget wrapping CnDataTable.\n *\n * Renders a data table on the shared CnWidgetWrapper chrome, which supplies\n * the widget title and the standard overflow Actions menu (Refresh /\n * Documentation / Request a feature). All data props are forwarded to the\n * inner CnDataTable; the chrome props (`title`, `documentationUrl`,\n * `widgetId`) are consumed by the wrapper and not passed down.\n *\n * A declarative `source` makes the widget self-fetch from OpenRegister:\n * `source.filter` is token-resolved with the shared `resolveFilterTokens`\n * grammar (`@me`, `@today`, `@workspace.*`, `?`-optional clauses dropped\n * when unresolved via `dropOptionalUnresolved`), `source.order` /\n * `source.limit` drive the fetch, and an `@resolve:` register sentinel is\n * passed through unexpanded (resolution is the host loader's job).\n * Externally supplied `rows` always win — the pre-existing pass-through\n * interface is unchanged.\n *\n * Wave 2 (#91): a declarative `endpointSource` (`{ url, method?, params?,\n * responsePath? }`) is the alternative to the OpenRegister `source` — rows\n * come from an app REST endpoint's payload at `responsePath`, fetched by the\n * shared `useEndpointSource` engine (token-resolved params, request dedup +\n * short-TTL cache, `cn:page:refresh` / `cn:widget:refresh` subscription).\n * Exactly one of `source` | `endpointSource`; columns / formatters /\n * `rowRoute` / `actions` apply unchanged on top of endpoint rows.\n *\n * A declarative `actions[]` array renders `patch` / `delete` `object-op`\n * actions (and reused `handler` / `open-modal` / `open-page` / `navigate`\n * actions) per row through `CnRowActions`; an `object-op` `create` action\n * renders as a widget-scoped footer affordance. `delete` is ALWAYS\n * confirm-gated through `CnConfirmDialog`; `patch` / `create` confirm only\n * on `confirm: true`. Mutations dispatch via the shared object store —\n * the manifest declares intent only, OpenRegister RBAC is the authority,\n * and a rejected write surfaces as an error without local mutation.\n *\n * A declarative `rowClass` (#91) — `[{ when: { field, op?, value }, class }]` reusing\n * the shared `visibleWhen` predicate grammar — is compiled into CnDataTable's\n * `rowClass` function so overdue / at-risk rows can be highlighted from the manifest\n * (a host-supplied `rowClass` FUNCTION still passes straight through).\n */\nexport default {\n\tname: 'CnWidgetObjectTable',\n\n\tcomponents: { CnDataTable, CnWidgetWrapper, CnWidgetHostShell, CnRowActions, CnIcon, NcButton, CnConfirmDialog },\n\n\tinject: {\n\t\t/**\n\t\t * Detail-page object context (`{ objectId, object, register, schema }`)\n\t\t * provided by CnDetailPage — enables `@objectId` / `@object.<field>`\n\t\t * tokens in `source.filter`. Null on dashboards.\n\t\t */\n\t\tcnObjectContext: { default: null },\n\t\t/**\n\t\t * v2 slot-grid detail context holder (`{ value: { objectData, schema,\n\t\t * objectType, objectId, register, store } | null }`) provided by\n\t\t * CnPageRenderer — backfills the object token context so\n\t\t * `@objectId` / `@object.<field>` resolve on detail surfaces where\n\t\t * CnDetailPage is not an ancestor (#91 Wave 3).\n\t\t */\n\t\tcnDetailObjectContext: { default: null },\n\t\t/**\n\t\t * Page-level workspace context (reactive `ref({})`) provided by\n\t\t * CnDashboardPage — enables `@workspace.<key>` tokens in\n\t\t * `source.filter`. Null on pages that don't provide it.\n\t\t */\n\t\tcnWorkspaceContext: { default: null },\n\t\t/**\n\t\t * Pre-bound `dispatchAction` provided by CnPageRenderer (router,\n\t\t * registry, handlers, openModal already wired). Used for the reused\n\t\t * non-mutating action types; falls back to a direct `dispatchAction`\n\t\t * call when mounted outside a CnPageRenderer tree.\n\t\t */\n\t\tcnDispatchAction: { default: null },\n\t},\n\n\tinheritAttrs: false,\n\n\tprops: {\n\t\t/**\n\t\t * Widget title shown in the CnWidgetWrapper header.\n\t\t */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Table'),\n\t\t},\n\n\t\t/**\n\t\t * Documentation link surfaced in the widget's overflow Actions menu.\n\t\t * Empty (the default) hides the Documentation item; the Refresh and\n\t\t * Request-a-feature items always render.\n\t\t */\n\t\tdocumentationUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Stable id forwarded to the widget chrome for the Refresh /\n\t\t * Request-a-feature payloads.\n\t\t */\n\t\twidgetId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Render content-only, WITHOUT the widget's own CnWidgetWrapper\n\t\t * chrome. Set by hosts that already provide the card chrome — e.g.\n\t\t * CnDashboardPage's dashboard widget grid, whose registry branch\n\t\t * wraps every catalog widget in its own CnWidgetWrapper (a naive\n\t\t * mount would double-card). Default `false` keeps the pre-existing\n\t\t * self-chromed v2 rendering byte-for-byte.\n\t\t */\n\t\thideWrapper: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Register slug. Forwarded to CnDataTable. */\n\t\tregister: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Schema slug. Forwarded to CnDataTable. */\n\t\tschema: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Column definitions. Forwarded to CnDataTable — bare string keys OR\n\t\t * the object form (`{ key, label, sortable, width, cellClass,\n\t\t * formatter, widget, format, aggregate }`) both pass straight through.\n\t\t *\n\t\t * @type {Array<object|string>}\n\t\t */\n\t\tcolumns: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Rows array. Forwarded to CnDataTable. External rows always win over `source`. */\n\t\trows: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Loading state. Forwarded to CnDataTable. */\n\t\tloading: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Declarative self-fetch source (ADR-049). When set and no external\n\t\t * `rows` are supplied, the widget resolves `filter` @-tokens and\n\t\t * drives CnDataTable's existing self-fetch with the resolved filter,\n\t\t * `order`, and `limit`. `register` MAY carry an `@resolve:` sentinel —\n\t\t * it is passed through unexpanded. `extend` (#91 Wave 3) forwards\n\t\t * OpenRegister `_extend[]` values (e.g. `[\"calculations\"]`) on the\n\t\t * fetch so virtual/declarative calc fields ride along and render as\n\t\t * ordinary columns. Default `null` keeps the pre-existing\n\t\t * pass-through behaviour byte-for-byte.\n\t\t *\n\t\t * @type {{register?: string, schema?: string, filter?: object, order?: object, limit?: number, extend?: string[]}|null}\n\t\t */\n\t\tsource: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Endpoint data binding (Wave 2, #91) — the alternative to the\n\t\t * OpenRegister `source` for rows an app REST endpoint computes (e.g. a\n\t\t * per-source performance report). Rows come from the payload at\n\t\t * `responsePath` (a dot-path; the plucked value must be an array —\n\t\t * anything else renders as empty). `params` values use the shared\n\t\t * filter-token grammar (`@workspace.dateFrom?`, `@me`, `@today±Nd`, …)\n\t\t * and re-resolve + refetch when the page context changes. Requests are\n\t\t * deduped + short-TTL cached per (url+params) by the shared\n\t\t * `useEndpointSource` engine, which also subscribes to\n\t\t * `cn:page:refresh` / `cn:widget:refresh`. Exactly one of `source` |\n\t\t * `endpointSource` (validator-enforced); external `rows` still win\n\t\t * over both. `columns` / formatters / `rowRoute` / `actions` apply\n\t\t * unchanged on top of endpoint rows.\n\t\t *\n\t\t * @type {{url: string, method?: string, params?: object, responsePath?: string}|null}\n\t\t */\n\t\tendpointSource: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Declarative actions (unified manifest action shape — `handler` |\n\t\t * `open-modal` | `open-page` | `navigate` | `object-op`). `object-op`\n\t\t * `patch` / `delete` (and every non-mutating type) render per row via\n\t\t * CnRowActions; `object-op` `create` renders as a widget-scoped footer\n\t\t * affordance. `delete` always confirms; `patch` / `create` confirm on\n\t\t * `confirm: true`. Authorization-shaped fields on an action have no\n\t\t * effect — OpenRegister RBAC is the only authority.\n\t\t *\n\t\t * @type {Array<object>}\n\t\t */\n\t\tactions: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Hide the column-header row. Forwarded to CnDataTable — the compact\n\t\t * dashboard-list surface.\n\t\t */\n\t\thideHeader: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Drop CnDataTable's card chrome (border, radius, shadow) so the table\n\t\t * sits flush inside the widget card. Forwarded to CnDataTable.\n\t\t */\n\t\tborderless: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Fit the table to the tile: the title column (a column's `grow: true`,\n\t\t * else `title` or `name`, else the first) shrinks and ends in an\n\t\t * ellipsis so the trailing columns stay visible. On by default; pass\n\t\t * `false` for the table's own auto layout. Forwarded to CnDataTable.\n\t\t *\n\t\t * @spec openspec/changes/narrow-object-table-widgets/specs/narrow-object-table-widgets/spec.md#requirement-a-narrow-object-table-keeps-its-trailing-columns-in-view\n\t\t */\n\t\tfitWidth: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Route NAME for row-click navigation. Mapped onto CnDataTable's\n\t\t * `rowClickRoute` as `{ name: rowRoute, params: { id } }` with the\n\t\t * row's `id` (or `@self.id`). Empty (the default) = no navigation.\n\t\t */\n\t\trowRoute: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Client-side row cap, forwarded to CnDataTable (`0` = all rows).\n\t\t * This is what makes `viewAllRoute` reachable for `endpointSource`\n\t\t * tables: the endpoint's full payload stays the `totalRowCount`\n\t\t * while CnDataTable renders only the first `limit` rows, so the\n\t\t * \"View all\" footer condition (total > shown) can finally hold.\n\t\t * The OpenRegister `source` mode keeps using `source.limit` (an\n\t\t * explicit assignment that wins over this passthrough).\n\t\t */\n\t\tlimit: {\n\t\t\ttype: Number,\n\t\t\tdefault: 0,\n\t\t},\n\n\t\t/**\n\t\t * vue-router route object for the \"View all\" footer link. Forwarded\n\t\t * to CnDataTable (footer shows when the rows are a `limit`-ed subset).\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tviewAllRoute: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Pre-translated \"View all\" footer label. Forwarded to CnDataTable. */\n\t\tviewAllLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: undefined,\n\t\t},\n\n\t\t/** Text shown when there are no rows. Forwarded to CnDataTable. */\n\t\temptyText: {\n\t\t\ttype: String,\n\t\t\tdefault: undefined,\n\t\t},\n\n\t\t/**\n\t\t * App wording for an unavailable personal lens (a `source.filter._recent`\n\t\t * tile on an instance that does not log reads), keyed `<lens>.<reason>`\n\t\t * or `<reason>`. Forwarded to CnDataTable, which shows the library's\n\t\t * object-neutral text without it.\n\t\t *\n\t\t * @spec openspec/changes/lens-says-why-it-is-empty/specs/personal-lens-availability/spec.md#requirement-an-unavailable-lens-explains-its-empty-page\n\t\t */\n\t\tlensReasonTexts: {\n\t\t\ttype: Object,\n\t\t\tdefault: undefined,\n\t\t},\n\n\t\t/**\n\t\t * Leading per-row icon: a static MDI icon name string, or a function\n\t\t * `(row) => iconName`. Forwarded to CnDataTable.\n\t\t *\n\t\t * @type {string|((row: object) => string)|null}\n\t\t */\n\t\trowIcon: {\n\t\t\ttype: [String, Function],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Per-row CSS class binding (#91). Two forms:\n\t\t * - a FUNCTION `(row) => string` — passed straight through to\n\t\t * CnDataTable's `rowClass` (the pre-existing host-supplied contract).\n\t\t * - an ARRAY of declarative rules `[{ when: { field, op?, value }, class }]`\n\t\t * compiled here into that function. Each rule adds its `class` to a\n\t\t * row when the shared `visibleWhen` predicate holds against the row:\n\t\t * `field` is a dot-path into the row, `op` is `eq|neq|gt|gte|lt|lte`\n\t\t * (default `eq`), `value` is the literal right-hand side. Rules are\n\t\t * evaluated in order and every matching `class` is space-joined, so an\n\t\t * overdue / at-risk row can be highlighted declaratively from a\n\t\t * manifest with no bespoke function. Default `null` = no row class.\n\t\t *\n\t\t * @type {((row: object) => string)|Array<{when: {field: string, op?: string, value: unknown}, class: string}>|null}\n\t\t */\n\t\trowClass: {\n\t\t\ttype: [Function, Array],\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['object-op'],\n\n\tsetup(props) {\n\t\t// Endpoint binding (Wave 2, #91): the shared useEndpointSource engine\n\t\t// owns token resolution, request dedup + TTL caching, and the\n\t\t// cn:page:refresh / cn:widget:refresh subscriptions (the widget's own\n\t\t// `widgetId` is matched against widget-scoped refresh payloads).\n\t\t// No-op while `endpointSource` is null, so the OpenRegister `source`\n\t\t// self-fetch path is untouched. Injects re-read in setup (same\n\t\t// resolution as the Options `inject` block).\n\t\tconst objectCtxRaw = inject('cnObjectContext', null)\n\t\tconst detailCtxRaw = inject('cnDetailObjectContext', null)\n\t\tconst workspaceRaw = inject('cnWorkspaceContext', ref(null))\n\t\tconst appConfigRaw = inject('cnAppConfig', ref({}))\n\t\tconst unwrap = (v) => ((v && typeof v === 'object' && 'value' in v) ? v.value : v)\n\t\t// External rows always win — and suppress the request entirely (the\n\t\t// pre-existing \"when rows are provided, no API calls are made\"\n\t\t// contract), so the getter resolves to null while `rows` are supplied.\n\t\tconst { data, loading, error, refetch } = useEndpointSource(\n\t\t\t() => ((props.rows && props.rows.length > 0) ? null : props.endpointSource),\n\t\t\t{\n\t\t\t\tctx: () => ({\n\t\t\t\t\t...(resolveObjectTokenContext(objectCtxRaw, detailCtxRaw) || {}),\n\t\t\t\t\tworkspace: unwrap(workspaceRaw) || {},\n\t\t\t\t\tconfig: unwrap(appConfigRaw) || {},\n\t\t\t\t}),\n\t\t\t\twidgetId: () => props.widgetId,\n\t\t\t},\n\t\t)\n\t\treturn { epData: data, epLoading: loading, epError: error, epRefetch: refetch }\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t/** Error message from the last rejected object-op write ('' = none). */\n\t\t\topError: '',\n\t\t\t/** @type {object|null} The object-op action awaiting confirmation (null = no dialog). */\n\t\t\tconfirmAction: null,\n\t\t\t/** @type {object|null} The row the pending confirm-gated action targets. */\n\t\t\tconfirmRow: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The merged detail-page object context for token resolution — both\n\t\t * detail-surface injects, holder fields backfilling (#91 Wave 3) —\n\t\t * or null on surfaces (dashboards) that don't provide one.\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tobjectCtx() {\n\t\t\treturn resolveObjectTokenContext(this.cnObjectContext, this.cnDetailObjectContext)\n\t\t},\n\n\t\t/**\n\t\t * The unwrapped workspace context bag (or null). Vue 2.7 inject may\n\t\t * hand back a raw ref; unwrap `.value` for token resolution.\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tworkspaceCtx() {\n\t\t\tconst c = this.cnWorkspaceContext\n\t\t\tif (!c) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn (typeof c === 'object' && 'value' in c) ? c.value : c\n\t\t},\n\n\t\t/**\n\t\t * Token-resolution context merged from the detail-page object context\n\t\t * and the page-level workspace bag (`@workspace.<key>`).\n\t\t *\n\t\t * @return {object}\n\t\t */\n\t\ttokenCtx() {\n\t\t\tconst base = this.objectCtx ? { ...this.objectCtx } : {}\n\t\t\tbase.workspace = this.workspaceCtx || {}\n\t\t\treturn base\n\t\t},\n\n\t\t/**\n\t\t * `source.filter` with every @-token resolved against `tokenCtx`, then\n\t\t * with any UNRESOLVED OPTIONAL clause (`@workspace.<key>?`) dropped —\n\t\t * an optional clause simply shows all rows until its context is set.\n\t\t *\n\t\t * @return {object}\n\t\t */\n\t\tresolvedFilter() {\n\t\t\treturn dropOptionalUnresolved(resolveFilterTokens((this.source && this.source.filter) || {}, this.tokenCtx))\n\t\t},\n\n\t\t/**\n\t\t * Whether a REQUIRED context token in `source.filter` is still\n\t\t * unresolved — the widget then skips the fetch instead of querying the\n\t\t * whole register.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\twaitingForContext() {\n\t\t\treturn !!this.source && hasUnresolvedTokens(this.resolvedFilter)\n\t\t},\n\n\t\t/**\n\t\t * Whether the Wave-2 `endpointSource` drives the rows: a usable\n\t\t * endpoint config and no external rows (external rows always win).\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tendpointActive() {\n\t\t\treturn !!(this.endpointSource && this.endpointSource.url)\n\t\t\t\t&& (!this.rows || this.rows.length === 0)\n\t\t},\n\n\t\t/**\n\t\t * Rows resolved from the endpoint payload. The payload (after the\n\t\t * `responsePath` pluck the shared engine applies) must be an ARRAY;\n\t\t * anything else renders as an empty table rather than crashing the\n\t\t * column renderer.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\tendpointRows() {\n\t\t\treturn Array.isArray(this.epData) ? this.epData : []\n\t\t},\n\n\t\t/**\n\t\t * Whether the declarative `source` drives a self-fetch: a usable\n\t\t * source, no external rows (external rows always win), no active\n\t\t * endpoint binding (exactly-one-of — endpointSource wins when both\n\t\t * slip past the validator), and no unresolved required token.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tselfFetchActive() {\n\t\t\treturn !!(this.source && this.source.register && this.source.schema)\n\t\t\t\t&& (!this.rows || this.rows.length === 0)\n\t\t\t\t&& !this.endpointActive\n\t\t\t\t&& !this.waitingForContext\n\t\t},\n\n\t\t/**\n\t\t * Query params handed to CnDataTable's self-fetch: the resolved filter\n\t\t * as direct field params (OpenRegister object-search shape),\n\t\t * `_order[field]` entries from `source.order`, and — when\n\t\t * `source.limit` is set — `_limit` of limit + 1 so CnDataTable's\n\t\t * View-all footer can detect that more rows exist while the display\n\t\t * caps at `limit`.\n\t\t *\n\t\t * IN-lists: an ARRAY filter value (either directly on the field or via\n\t\t * the `{ in: [...] }` operator form) is emitted as the bare field with\n\t\t * the array value, which axios serializes as repeated bracket params\n\t\t * (`field[]=a&field[]=b`) — the same shape `buildQueryString` /\n\t\t * `useObjectStore` send and the only IN form OpenRegister parses (a\n\t\t * `field[in]`-style key is NOT understood server-side). Empty arrays\n\t\t * are skipped (no constraint).\n\t\t *\n\t\t * @return {object}\n\t\t */\n\t\tresolvedFetchParams() {\n\t\t\tconst src = this.source || {}\n\t\t\tconst params = {}\n\t\t\tfor (const [field, dir] of Object.entries(src.order || {})) {\n\t\t\t\tparams[`_order[${field}]`] = dir === 'desc' ? 'desc' : 'asc'\n\t\t\t}\n\t\t\tif (typeof src.limit === 'number' && src.limit > 0) {\n\t\t\t\tparams._limit = src.limit + 1\n\t\t\t}\n\t\t\t// `source.extend` (#91 Wave 3): forwarded as OR's repeated\n\t\t\t// `_extend[]` param so virtual/declarative fields (e.g.\n\t\t\t// `calculations` — procest daysOverdue) ride the fetch and are\n\t\t\t// displayable as ordinary columns. Serialized by the shared\n\t\t\t// query builder (array value → repeated `key[]`).\n\t\t\tif (Array.isArray(src.extend) && src.extend.length > 0) {\n\t\t\t\tparams._extend = src.extend.filter((e) => typeof e === 'string' && e !== '')\n\t\t\t}\n\t\t\tconst filter = this.resolvedFilter\n\t\t\tif (filter && typeof filter === 'object') {\n\t\t\t\tfor (const [k, v] of Object.entries(filter)) {\n\t\t\t\t\tif (Array.isArray(v)) {\n\t\t\t\t\t\tif (v.length > 0) {\n\t\t\t\t\t\t\tparams[k] = v\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (v && typeof v === 'object') {\n\t\t\t\t\t\tfor (const [op, ov] of Object.entries(v)) {\n\t\t\t\t\t\t\tif (op === 'in' && Array.isArray(ov)) {\n\t\t\t\t\t\t\t\t// OR's IN form is the bare repeated field param.\n\t\t\t\t\t\t\t\tif (ov.length > 0) {\n\t\t\t\t\t\t\t\t\tparams[k] = ov\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tparams[`${k}[${op}]`] = ov\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (v !== '' && v !== null && v !== undefined) {\n\t\t\t\t\t\tparams[k] = v\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn params\n\t\t},\n\n\t\t/**\n\t\t * `$props` minus the chrome props (`title`, `documentationUrl`,\n\t\t * `widgetId`, `hideWrapper`) and the widget-only props (`source`,\n\t\t * `endpointSource`, `actions`, `rowRoute`), so they are consumed here\n\t\t * and never forwarded to the inner CnDataTable. `undefined` values are\n\t\t * dropped so CnDataTable's own prop defaults apply. When the\n\t\t * declarative `source` is active it supplies `register` / `schemaId` /\n\t\t * `fetchParams` / `limit`; an `@resolve:` register sentinel passes\n\t\t * through unexpanded. When the Wave-2 `endpointSource` is active it\n\t\t * supplies the resolved `rows` + `loading` instead — columns,\n\t\t * formatters, and row navigation apply unchanged on top.\n\t\t *\n\t\t * @return {object}\n\t\t */\n\t\tinnerProps() {\n\t\t\tconst { title, documentationUrl, widgetId, hideWrapper, source, endpointSource, actions, rowRoute, rowClass, schema, ...rest } = this.$props\n\t\t\tconst inner = {}\n\t\t\tfor (const [k, v] of Object.entries(rest)) {\n\t\t\t\tif (v !== undefined) {\n\t\t\t\t\tinner[k] = v\n\t\t\t\t}\n\t\t\t}\n\t\t\t// This widget's `schema` is a slug, while CnDataTable's `schema` is a\n\t\t\t// schema object, so it is not forwarded. Nor is it mapped to `schemaId`:\n\t\t\t// that would turn on CnDataTable's unscoped self-fetch whenever the\n\t\t\t// widget's own rows are empty. Self-fetch comes from `source` alone.\n\t\t\t// `rowClass` is consumed here (function or declarative rules[]) and\n\t\t\t// forwarded to CnDataTable as a compiled `(row) => class` function.\n\t\t\tif (this.compiledRowClass) {\n\t\t\t\tinner.rowClass = this.compiledRowClass\n\t\t\t}\n\t\t\tif (this.selfFetchActive) {\n\t\t\t\tinner.register = this.source.register\n\t\t\t\tinner.schemaId = this.source.schema\n\t\t\t\tinner.fetchParams = this.resolvedFetchParams\n\t\t\t\tif (typeof this.source.limit === 'number' && this.source.limit > 0) {\n\t\t\t\t\tinner.limit = this.source.limit\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (this.endpointActive) {\n\t\t\t\tinner.rows = this.endpointRows\n\t\t\t\tinner.loading = this.epLoading || this.loading\n\t\t\t}\n\t\t\tif (this.rowRoute) {\n\t\t\t\tinner.rowClickRoute = this.rowRouteFn\n\t\t\t}\n\t\t\treturn inner\n\t\t},\n\n\t\t/**\n\t\t * The effective `rowClass` function forwarded to CnDataTable. A\n\t\t * host-supplied FUNCTION passes straight through; a declarative\n\t\t * rules[] array is compiled into a `(row) => string` that space-joins\n\t\t * every rule whose shared `visibleWhen` predicate holds against the\n\t\t * row (`field` dot-path + `op` + `value`). Null when `rowClass` is\n\t\t * unset or an empty array — the widget then forwards nothing so\n\t\t * CnDataTable's own default applies.\n\t\t *\n\t\t * @return {?(row: object) => string} The row-class function, or null.\n\t\t */\n\t\tcompiledRowClass() {\n\t\t\tconst rc = this.rowClass\n\t\t\tif (typeof rc === 'function') {\n\t\t\t\treturn rc\n\t\t\t}\n\t\t\tif (!Array.isArray(rc) || rc.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst rules = rc\n\t\t\treturn (row) => {\n\t\t\t\tconst classes = []\n\t\t\t\tfor (const rule of rules) {\n\t\t\t\t\tif (!rule || !rule.class) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tconst when = rule.when || {}\n\t\t\t\t\tconst actual = readVisibleWhenPath(row, when.field)\n\t\t\t\t\tif (compareVisibleWhen(actual, when.op || 'eq', when.value)) {\n\t\t\t\t\t\tclasses.push(rule.class)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn classes.join(' ')\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Row-click navigation function derived from the `rowRoute` route\n\t\t * name (CnDataTable's `rowClickRoute` shape).\n\t\t *\n\t\t * @return {(row: object) => (object|null)} A vue-router location, or null for a row with no id.\n\t\t */\n\t\trowRouteFn() {\n\t\t\treturn (row) => {\n\t\t\t\tconst id = row && (row.id || (row['@self'] && row['@self'].id))\n\t\t\t\treturn id ? { name: this.rowRoute, params: { id: String(id) } } : null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Actions rendered per row: every non-`object-op` type plus\n\t\t * `object-op` `patch` / `delete` (they mutate THAT row's object).\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\trowScopedActions() {\n\t\t\treturn (this.actions || []).filter((a) => a && (a.type !== 'object-op' || a.op === 'patch' || a.op === 'delete'))\n\t\t},\n\n\t\t/**\n\t\t * `object-op` `create` actions — widget-scoped (no row to mutate), so\n\t\t * they render in the list footer, never in the row menu.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\tcreateActions() {\n\t\t\treturn (this.actions || []).filter((a) => a && a.type === 'object-op' && a.op === 'create')\n\t\t},\n\n\t\t/**\n\t\t * Row-scoped actions mapped onto the CnRowActions shape: label + icon\n\t\t * pass through, `object-op` `delete` renders destructive, and the\n\t\t * handler routes back through the widget's confirm-gated dispatcher.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\tmappedRowActions() {\n\t\t\treturn this.rowScopedActions.map((action) => ({\n\t\t\t\tlabel: action.label,\n\t\t\t\ticon: action.icon,\n\t\t\t\tdestructive: action.type === 'object-op' && action.op === 'delete',\n\t\t\t\thandler: (row) => this.onActionTriggered(action, row),\n\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * Host-supplied CnDataTable scoped slots to forward (keys only). The\n\t\t * `row-actions` slot is withheld while the widget renders its own\n\t\t * declarative actions menu.\n\t\t *\n\t\t * @return {object}\n\t\t */\n\t\tforwardedScopedSlots() {\n\t\t\tconst out = {}\n\t\t\tfor (const name of Object.keys(this.$slots || {})) {\n\t\t\t\tif (name === 'row-actions' && this.rowScopedActions.length > 0) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tout[name] = true\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\t/**\n\t\t * Confirmation question for the pending confirm-gated action —\n\t\t * destructive copy for `delete`, a generic confirm otherwise.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tconfirmMessage() {\n\t\t\tif (this.confirmAction && this.confirmAction.op === 'delete') {\n\t\t\t\treturn t('nextcloud-vue', 'Are you sure you want to delete this item? This action cannot be undone.')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Are you sure you want to continue?')\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Entry point for every declarative action trigger (row menu or\n\t\t * footer create button). `object-op` `delete` ALWAYS confirms;\n\t\t * `patch` / `create` confirm only when the action sets\n\t\t * `confirm: true`; everything else dispatches immediately.\n\t\t *\n\t\t * @param {object} action The manifest action.\n\t\t * @param {object|null} row The row for row-scoped actions (null for create).\n\t\t * @return {Promise<void>|void}\n\t\t */\n\t\tonActionTriggered(action, row) {\n\t\t\tif (action && action.type === 'object-op' && (action.op === 'delete' || action.confirm === true)) {\n\t\t\t\tthis.confirmAction = action\n\t\t\t\tthis.confirmRow = row\n\t\t\t\treturn\n\t\t\t}\n\t\t\treturn this.runAction(action, row)\n\t\t},\n\n\t\t/**\n\t\t * Confirm-dialog primary button: run the pending action and report\n\t\t * the outcome back into the dialog's result phase.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onConfirmConfirmed() {\n\t\t\tconst outcome = await this.runAction(this.confirmAction, this.confirmRow)\n\t\t\tconst dialog = this.$refs.confirmDialog\n\t\t\tif (dialog) {\n\t\t\t\tdialog.setResult(outcome.ok ? { success: true } : { error: outcome.error })\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Close/cancel the confirm dialog and clear the pending action.\n\t\t */\n\t\tcloseConfirm() {\n\t\t\tthis.confirmAction = null\n\t\t\tthis.confirmRow = null\n\t\t},\n\n\t\t/**\n\t\t * Dispatch an action. Non-mutating types go through the page's\n\t\t * pre-bound `cnDispatchAction` (falling back to a bare\n\t\t * `dispatchAction`); for `handler` actions the row is appended as the\n\t\t * final handler argument so row context reaches the registry function.\n\t\t * `object-op` actions dispatch via the shared object store — a\n\t\t * rejected (RBAC) write surfaces `opError` and mutates NO local state;\n\t\t * a success clears the error and refreshes the self-fetched rows.\n\t\t *\n\t\t * @param {object} action The manifest action.\n\t\t * @param {object|null} row The target row (null for create).\n\t\t * @return {Promise<{ok: boolean, error?: string}>}\n\t\t */\n\t\tasync runAction(action, row) {\n\t\t\tif (!action) {\n\t\t\t\treturn { ok: false, error: '' }\n\t\t\t}\n\t\t\tif (action.type !== 'object-op') {\n\t\t\t\tconst wrapped = (!action.type || action.type === 'handler')\n\t\t\t\t\t? { ...action, args: [...(action.args || []), row] }\n\t\t\t\t\t: action\n\t\t\t\tif (typeof this.cnDispatchAction === 'function') {\n\t\t\t\t\tthis.cnDispatchAction(wrapped)\n\t\t\t\t} else {\n\t\t\t\t\tdispatchAction(wrapped, { router: this.$router || null })\n\t\t\t\t}\n\t\t\t\treturn { ok: true }\n\t\t\t}\n\n\t\t\tconst store = this.getObjectStore()\n\t\t\tconst result = await dispatchAction(action, {\n\t\t\t\tobjectStore: store,\n\t\t\t\tsource: this.source,\n\t\t\t\trow,\n\t\t\t})\n\t\t\tconst ok = !(result === null || result === false || result === undefined)\n\t\t\tif (!ok) {\n\t\t\t\tconst error = this.readStoreError(store)\n\t\t\t\tthis.opError = error\n\t\t\t\treturn { ok: false, error }\n\t\t\t}\n\t\t\tthis.opError = ''\n\t\t\tthis.refresh()\n\t\t\t/**\n\t\t\t * @event object-op Emitted after a successful declarative mutation\n\t\t\t * so a host that supplies external `rows` can refetch them.\n\t\t\t * @type {{action: object, row: (object|null), result: unknown}}\n\t\t\t */\n\t\t\tthis.$emit('object-op', { action, row, result })\n\t\t\treturn { ok: true }\n\t\t},\n\n\t\t/**\n\t\t * Re-run the active fetch: the endpoint binding (force-refetch past\n\t\t * the shared cache) or the inner CnDataTable's self-fetch. No-op when\n\t\t * the widget is fed external rows.\n\t\t */\n\t\trefresh() {\n\t\t\tif (this.endpointActive) {\n\t\t\t\tthis.epRefetch()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst table = this.$refs.dataTable\n\t\t\tif (this.selfFetchActive && table && typeof table.fetchData === 'function') {\n\t\t\t\ttable.fetchData()\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The shared object store instance, or null when no Pinia is active\n\t\t * (`dispatchAction` then warns and no-ops).\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tgetObjectStore() {\n\t\t\ttry {\n\t\t\t\treturn useObjectStore()\n\t\t\t} catch {\n\t\t\t\treturn null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Human-readable message for the last store error on this widget's\n\t\t * source type (RBAC rejection, network failure, …).\n\t\t *\n\t\t * @param {object|null} store The object store used for the dispatch.\n\t\t * @return {string}\n\t\t */\n\t\treadStoreError(store) {\n\t\t\ttry {\n\t\t\t\tif (store && this.source) {\n\t\t\t\t\tconst type = resolveObjectOpType(store, this.source)\n\t\t\t\t\tconst err = store.errors && store.errors[type]\n\t\t\t\t\tif (err && err.message) {\n\t\t\t\t\t\treturn err.message\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// fall through to the generic message\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'The operation was rejected')\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-widget-object-table {\n\twidth: 100%;\n}\n\n.cn-widget-object-table__footer {\n\tdisplay: flex;\n\tgap: 4px;\n\tpadding: 4px 8px;\n\tborder-top: 1px solid var(--color-border);\n}\n\n.cn-widget-object-table__error {\n\tcolor: var(--color-text-error, var(--color-error-text));\n\tfont-size: 0.85em;\n\tmargin: 4px 8px 0;\n}\n</style>\n"],"names":["CnDataTable","CnWidgetWrapper","CnRowActions","CnIcon","CnConfirmDialog","t"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnWidgetObjectTable.vue3.js","sources":["../../../../src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue?vue&type=template&id=f6accf5e&lang.js"],"sourcesContent":["<!--\n CnWidgetObjectTable — built-in v2 widget wrapping CnDataTable.\n\n Referenced in v2 manifests via `widgetKey: \"object-table\"`. Renders the\n table on the shared CnWidgetWrapper chrome (title + standard overflow\n Actions menu: Refresh / Documentation / Request a feature) and forwards\n all data props to CnDataTable so the table itself is a transparent\n pass-through.\n\n ADR-049 enrichment: a declarative token-resolved `source` drives\n CnDataTable's existing self-fetch (externally supplied `rows` always win),\n the compact-list surface (hideHeader / borderless / rowRoute /\n viewAllRoute / emptyText / rowIcon / #footer) passes straight through,\n and a declarative `actions[]` renders row-scoped actions via CnRowActions\n plus widget-scoped `object-op` create affordances in the footer.\n\n Spec: REQ-MVR-006 (manifest-v2-renderer) — built-in widget: object-table\n Spec: openspec/changes/list-widget-enrichment/specs/manifest-v2-renderer/spec.md\n-->\n<template>\n\t<!-- `refreshing` mirrors the endpoint fetch so the chrome's Refresh\n\t action spins (and stays disabled) for the real duration of an\n\t in-flight refetch. -->\n\t<component\n\t\t:is=\"hideWrapper ? 'CnWidgetHostShell' : 'CnWidgetWrapper'\"\n\t\tv-bind=\"hideWrapper ? {} : { title, widgetId, documentationUrl, flush: true, refreshing: epLoading }\">\n\t\t<div class=\"cn-widget-object-table\">\n\t\t\t<CnDataTable ref=\"dataTable\" v-bind=\"{ ...innerProps, ...$attrs }\">\n\t\t\t\t<!-- Forward every host-supplied CnDataTable scoped slot verbatim\n\t\t\t\t (#footer { total, shown }, #empty, #column-<key>, …). The\n\t\t\t\t #row-actions slot is NOT forwarded while the widget renders\n\t\t\t\t its own declarative actions[] menu. -->\n\t\t\t\t<template v-for=\"(_, name) in forwardedScopedSlots\" #[name]=\"slotData\">\n\t\t\t\t\t<!-- @slot Any CnDataTable scoped slot, forwarded verbatim — e.g. #footer ({ total, shown }), #empty, #column-<key>. #row-actions is withheld while the declarative actions[] menu renders. -->\n\t\t\t\t\t<slot :name=\"name\" v-bind=\"slotData\" />\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"rowScopedActions.length > 0\" #row-actions=\"{ row }\">\n\t\t\t\t\t<CnRowActions :actions=\"mappedRowActions\" :row=\"row\" />\n\t\t\t\t</template>\n\t\t\t</CnDataTable>\n\n\t\t\t<!-- Widget-scoped object-op create affordance: `create` has no row\n\t\t\t to mutate, so it renders in the list footer, never inside the\n\t\t\t per-row actions menu (ADR-049 / list-widget-enrichment). -->\n\t\t\t<div v-if=\"createActions.length > 0\" class=\"cn-widget-object-table__footer\">\n\t\t\t\t<NcButton\n\t\t\t\t\tv-for=\"action in createActions\"\n\t\t\t\t\t:key=\"action.id || action.label\"\n\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t:data-testid=\"`cn-widget-create-${action.id || 'action'}`\"\n\t\t\t\t\t@click=\"onActionTriggered(action, null)\">\n\t\t\t\t\t<template v-if=\"action.icon\" #icon>\n\t\t\t\t\t\t<CnIcon :name=\"action.icon\" :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ action.label }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\n\t\t\t<!-- A rejected write (OpenRegister RBAC) surfaces here — local row\n\t\t\t state is never mutated on failure (the store only writes its\n\t\t\t caches on success). -->\n\t\t\t<p v-if=\"opError\" class=\"cn-widget-object-table__error\" data-testid=\"cn-widget-object-table-error\">\n\t\t\t\t{{ opError }}\n\t\t\t</p>\n\n\t\t\t<CnConfirmDialog\n\t\t\t\tv-if=\"confirmAction\"\n\t\t\t\tref=\"confirmDialog\"\n\t\t\t\t:dialogTitle=\"confirmAction.label\"\n\t\t\t\t:message=\"confirmMessage\"\n\t\t\t\t:variant=\"confirmAction.op === 'delete' ? 'error' : 'primary'\"\n\t\t\t\t:confirmLabel=\"confirmAction.label\"\n\t\t\t\t@confirm=\"onConfirmConfirmed\"\n\t\t\t\t@close=\"closeConfirm\" />\n\t\t</div>\n\t</component>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton } from '@nextcloud/vue'\nimport { inject, ref } from 'vue'\nimport CnConfirmDialog from '../../dialogs/CnConfirmDialog.vue'\nimport CnDataTable from '../CnDataTable/CnDataTable.vue'\nimport { useEndpointSource } from '../../composables/useEndpointSource.js'\nimport { useObjectStore } from '../../store/useObjectStore.js'\nimport { dispatchAction, resolveObjectOpType } from '../../utils/actionsDispatcher.js'\nimport { resolveObjectTokenContext } from '../../utils/detailObjectContext.js'\nimport { dropOptionalUnresolved, hasUnresolvedTokens, resolveFilterTokens } from '../../utils/resolveFilterTokens.js'\nimport { compareVisibleWhen, readVisibleWhenPath } from '../../utils/visibleWhen.js'\nimport { CnIcon } from '../CnIcon/index.js'\nimport { CnRowActions } from '../CnRowActions/index.js'\nimport { CnWidgetWrapper } from '../CnWidgetWrapper/index.js'\n// Chrome-less pass-through used when `hideWrapper` is set (see hostShell.js\n// for why it lives in its own module).\nimport { CnWidgetHostShell } from './hostShell.js'\n\n/**\n * CnWidgetObjectTable — built-in v2 widget wrapping CnDataTable.\n *\n * Renders a data table on the shared CnWidgetWrapper chrome, which supplies\n * the widget title and the standard overflow Actions menu (Refresh /\n * Documentation / Request a feature). All data props are forwarded to the\n * inner CnDataTable; the chrome props (`title`, `documentationUrl`,\n * `widgetId`) are consumed by the wrapper and not passed down.\n *\n * A declarative `source` makes the widget self-fetch from OpenRegister:\n * `source.filter` is token-resolved with the shared `resolveFilterTokens`\n * grammar (`@me`, `@today`, `@workspace.*`, `?`-optional clauses dropped\n * when unresolved via `dropOptionalUnresolved`), `source.order` /\n * `source.limit` drive the fetch, and an `@resolve:` register sentinel is\n * passed through unexpanded (resolution is the host loader's job).\n * Externally supplied `rows` always win — the pre-existing pass-through\n * interface is unchanged.\n *\n * Wave 2 (#91): a declarative `endpointSource` (`{ url, method?, params?,\n * responsePath? }`) is the alternative to the OpenRegister `source` — rows\n * come from an app REST endpoint's payload at `responsePath`, fetched by the\n * shared `useEndpointSource` engine (token-resolved params, request dedup +\n * short-TTL cache, `cn:page:refresh` / `cn:widget:refresh` subscription).\n * Exactly one of `source` | `endpointSource`; columns / formatters /\n * `rowRoute` / `actions` apply unchanged on top of endpoint rows.\n *\n * A declarative `actions[]` array renders `patch` / `delete` `object-op`\n * actions (and reused `handler` / `open-modal` / `open-page` / `navigate`\n * actions) per row through `CnRowActions`; an `object-op` `create` action\n * renders as a widget-scoped footer affordance. `delete` is ALWAYS\n * confirm-gated through `CnConfirmDialog`; `patch` / `create` confirm only\n * on `confirm: true`. Mutations dispatch via the shared object store —\n * the manifest declares intent only, OpenRegister RBAC is the authority,\n * and a rejected write surfaces as an error without local mutation.\n *\n * A declarative `rowClass` (#91) — `[{ when: { field, op?, value }, class }]` reusing\n * the shared `visibleWhen` predicate grammar — is compiled into CnDataTable's\n * `rowClass` function so overdue / at-risk rows can be highlighted from the manifest\n * (a host-supplied `rowClass` FUNCTION still passes straight through).\n */\nexport default {\n\tname: 'CnWidgetObjectTable',\n\n\tcomponents: { CnDataTable, CnWidgetWrapper, CnWidgetHostShell, CnRowActions, CnIcon, NcButton, CnConfirmDialog },\n\n\tinject: {\n\t\t/**\n\t\t * Detail-page object context (`{ objectId, object, register, schema }`)\n\t\t * provided by CnDetailPage — enables `@objectId` / `@object.<field>`\n\t\t * tokens in `source.filter`. Null on dashboards.\n\t\t */\n\t\tcnObjectContext: { default: null },\n\t\t/**\n\t\t * v2 slot-grid detail context holder (`{ value: { objectData, schema,\n\t\t * objectType, objectId, register, store } | null }`) provided by\n\t\t * CnPageRenderer — backfills the object token context so\n\t\t * `@objectId` / `@object.<field>` resolve on detail surfaces where\n\t\t * CnDetailPage is not an ancestor (#91 Wave 3).\n\t\t */\n\t\tcnDetailObjectContext: { default: null },\n\t\t/**\n\t\t * Page-level workspace context (reactive `ref({})`) provided by\n\t\t * CnDashboardPage — enables `@workspace.<key>` tokens in\n\t\t * `source.filter`. Null on pages that don't provide it.\n\t\t */\n\t\tcnWorkspaceContext: { default: null },\n\t\t/**\n\t\t * Pre-bound `dispatchAction` provided by CnPageRenderer (router,\n\t\t * registry, handlers, openModal already wired). Used for the reused\n\t\t * non-mutating action types; falls back to a direct `dispatchAction`\n\t\t * call when mounted outside a CnPageRenderer tree.\n\t\t */\n\t\tcnDispatchAction: { default: null },\n\t},\n\n\tinheritAttrs: false,\n\n\tprops: {\n\t\t/**\n\t\t * Widget title shown in the CnWidgetWrapper header.\n\t\t */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Table'),\n\t\t},\n\n\t\t/**\n\t\t * Documentation link surfaced in the widget's overflow Actions menu.\n\t\t * Empty (the default) hides the Documentation item; the Refresh and\n\t\t * Request-a-feature items always render.\n\t\t */\n\t\tdocumentationUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Stable id forwarded to the widget chrome for the Refresh /\n\t\t * Request-a-feature payloads.\n\t\t */\n\t\twidgetId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Render content-only, WITHOUT the widget's own CnWidgetWrapper\n\t\t * chrome. Set by hosts that already provide the card chrome — e.g.\n\t\t * CnDashboardPage's dashboard widget grid, whose registry branch\n\t\t * wraps every catalog widget in its own CnWidgetWrapper (a naive\n\t\t * mount would double-card). Default `false` keeps the pre-existing\n\t\t * self-chromed v2 rendering byte-for-byte.\n\t\t */\n\t\thideWrapper: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Register slug. Forwarded to CnDataTable. */\n\t\tregister: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Schema slug. Forwarded to CnDataTable. */\n\t\tschema: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Column definitions. Forwarded to CnDataTable — bare string keys OR\n\t\t * the object form (`{ key, label, sortable, width, cellClass,\n\t\t * formatter, widget, format, aggregate }`) both pass straight through.\n\t\t *\n\t\t * @type {Array<object|string>}\n\t\t */\n\t\tcolumns: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Rows array. Forwarded to CnDataTable. External rows always win over `source`. */\n\t\trows: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Loading state. Forwarded to CnDataTable. */\n\t\tloading: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Declarative self-fetch source (ADR-049). When set and no external\n\t\t * `rows` are supplied, the widget resolves `filter` @-tokens and\n\t\t * drives CnDataTable's existing self-fetch with the resolved filter,\n\t\t * `order`, and `limit`. `register` MAY carry an `@resolve:` sentinel —\n\t\t * it is passed through unexpanded. `extend` (#91 Wave 3) forwards\n\t\t * OpenRegister `_extend[]` values (e.g. `[\"calculations\"]`) on the\n\t\t * fetch so virtual/declarative calc fields ride along and render as\n\t\t * ordinary columns. Default `null` keeps the pre-existing\n\t\t * pass-through behaviour byte-for-byte.\n\t\t *\n\t\t * @type {{register?: string, schema?: string, filter?: object, order?: object, limit?: number, extend?: string[]}|null}\n\t\t */\n\t\tsource: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Endpoint data binding (Wave 2, #91) — the alternative to the\n\t\t * OpenRegister `source` for rows an app REST endpoint computes (e.g. a\n\t\t * per-source performance report). Rows come from the payload at\n\t\t * `responsePath` (a dot-path; the plucked value must be an array —\n\t\t * anything else renders as empty). `params` values use the shared\n\t\t * filter-token grammar (`@workspace.dateFrom?`, `@me`, `@today±Nd`, …)\n\t\t * and re-resolve + refetch when the page context changes. Requests are\n\t\t * deduped + short-TTL cached per (url+params) by the shared\n\t\t * `useEndpointSource` engine, which also subscribes to\n\t\t * `cn:page:refresh` / `cn:widget:refresh`. Exactly one of `source` |\n\t\t * `endpointSource` (validator-enforced); external `rows` still win\n\t\t * over both. `columns` / formatters / `rowRoute` / `actions` apply\n\t\t * unchanged on top of endpoint rows.\n\t\t *\n\t\t * @type {{url: string, method?: string, params?: object, responsePath?: string}|null}\n\t\t */\n\t\tendpointSource: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Declarative actions (unified manifest action shape — `handler` |\n\t\t * `open-modal` | `open-page` | `navigate` | `object-op`). `object-op`\n\t\t * `patch` / `delete` (and every non-mutating type) render per row via\n\t\t * CnRowActions; `object-op` `create` renders as a widget-scoped footer\n\t\t * affordance. `delete` always confirms; `patch` / `create` confirm on\n\t\t * `confirm: true`. Authorization-shaped fields on an action have no\n\t\t * effect — OpenRegister RBAC is the only authority.\n\t\t *\n\t\t * @type {Array<object>}\n\t\t */\n\t\tactions: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Hide the column-header row. Forwarded to CnDataTable — the compact\n\t\t * dashboard-list surface.\n\t\t */\n\t\thideHeader: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Drop CnDataTable's card chrome (border, radius, shadow) so the table\n\t\t * sits flush inside the widget card. Forwarded to CnDataTable.\n\t\t */\n\t\tborderless: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Route NAME for row-click navigation. Mapped onto CnDataTable's\n\t\t * `rowClickRoute` as `{ name: rowRoute, params: { id } }` with the\n\t\t * row's `id` (or `@self.id`). Empty (the default) = no navigation.\n\t\t */\n\t\trowRoute: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Client-side row cap, forwarded to CnDataTable (`0` = all rows).\n\t\t * This is what makes `viewAllRoute` reachable for `endpointSource`\n\t\t * tables: the endpoint's full payload stays the `totalRowCount`\n\t\t * while CnDataTable renders only the first `limit` rows, so the\n\t\t * \"View all\" footer condition (total > shown) can finally hold.\n\t\t * The OpenRegister `source` mode keeps using `source.limit` (an\n\t\t * explicit assignment that wins over this passthrough).\n\t\t */\n\t\tlimit: {\n\t\t\ttype: Number,\n\t\t\tdefault: 0,\n\t\t},\n\n\t\t/**\n\t\t * vue-router route object for the \"View all\" footer link. Forwarded\n\t\t * to CnDataTable (footer shows when the rows are a `limit`-ed subset).\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tviewAllRoute: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Pre-translated \"View all\" footer label. Forwarded to CnDataTable. */\n\t\tviewAllLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: undefined,\n\t\t},\n\n\t\t/** Text shown when there are no rows. Forwarded to CnDataTable. */\n\t\temptyText: {\n\t\t\ttype: String,\n\t\t\tdefault: undefined,\n\t\t},\n\n\t\t/**\n\t\t * App wording for an unavailable personal lens (a `source.filter._recent`\n\t\t * tile on an instance that does not log reads), keyed `<lens>.<reason>`\n\t\t * or `<reason>`. Forwarded to CnDataTable, which shows the library's\n\t\t * object-neutral text without it.\n\t\t *\n\t\t * @spec openspec/changes/lens-says-why-it-is-empty/specs/personal-lens-availability/spec.md#requirement-an-unavailable-lens-explains-its-empty-page\n\t\t */\n\t\tlensReasonTexts: {\n\t\t\ttype: Object,\n\t\t\tdefault: undefined,\n\t\t},\n\n\t\t/**\n\t\t * Leading per-row icon: a static MDI icon name string, or a function\n\t\t * `(row) => iconName`. Forwarded to CnDataTable.\n\t\t *\n\t\t * @type {string|((row: object) => string)|null}\n\t\t */\n\t\trowIcon: {\n\t\t\ttype: [String, Function],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Per-row CSS class binding (#91). Two forms:\n\t\t * - a FUNCTION `(row) => string` — passed straight through to\n\t\t * CnDataTable's `rowClass` (the pre-existing host-supplied contract).\n\t\t * - an ARRAY of declarative rules `[{ when: { field, op?, value }, class }]`\n\t\t * compiled here into that function. Each rule adds its `class` to a\n\t\t * row when the shared `visibleWhen` predicate holds against the row:\n\t\t * `field` is a dot-path into the row, `op` is `eq|neq|gt|gte|lt|lte`\n\t\t * (default `eq`), `value` is the literal right-hand side. Rules are\n\t\t * evaluated in order and every matching `class` is space-joined, so an\n\t\t * overdue / at-risk row can be highlighted declaratively from a\n\t\t * manifest with no bespoke function. Default `null` = no row class.\n\t\t *\n\t\t * @type {((row: object) => string)|Array<{when: {field: string, op?: string, value: unknown}, class: string}>|null}\n\t\t */\n\t\trowClass: {\n\t\t\ttype: [Function, Array],\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['object-op'],\n\n\tsetup(props) {\n\t\t// Endpoint binding (Wave 2, #91): the shared useEndpointSource engine\n\t\t// owns token resolution, request dedup + TTL caching, and the\n\t\t// cn:page:refresh / cn:widget:refresh subscriptions (the widget's own\n\t\t// `widgetId` is matched against widget-scoped refresh payloads).\n\t\t// No-op while `endpointSource` is null, so the OpenRegister `source`\n\t\t// self-fetch path is untouched. Injects re-read in setup (same\n\t\t// resolution as the Options `inject` block).\n\t\tconst objectCtxRaw = inject('cnObjectContext', null)\n\t\tconst detailCtxRaw = inject('cnDetailObjectContext', null)\n\t\tconst workspaceRaw = inject('cnWorkspaceContext', ref(null))\n\t\tconst appConfigRaw = inject('cnAppConfig', ref({}))\n\t\tconst unwrap = (v) => ((v && typeof v === 'object' && 'value' in v) ? v.value : v)\n\t\t// External rows always win — and suppress the request entirely (the\n\t\t// pre-existing \"when rows are provided, no API calls are made\"\n\t\t// contract), so the getter resolves to null while `rows` are supplied.\n\t\tconst { data, loading, error, refetch } = useEndpointSource(\n\t\t\t() => ((props.rows && props.rows.length > 0) ? null : props.endpointSource),\n\t\t\t{\n\t\t\t\tctx: () => ({\n\t\t\t\t\t...(resolveObjectTokenContext(objectCtxRaw, detailCtxRaw) || {}),\n\t\t\t\t\tworkspace: unwrap(workspaceRaw) || {},\n\t\t\t\t\tconfig: unwrap(appConfigRaw) || {},\n\t\t\t\t}),\n\t\t\t\twidgetId: () => props.widgetId,\n\t\t\t},\n\t\t)\n\t\treturn { epData: data, epLoading: loading, epError: error, epRefetch: refetch }\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t/** Error message from the last rejected object-op write ('' = none). */\n\t\t\topError: '',\n\t\t\t/** @type {object|null} The object-op action awaiting confirmation (null = no dialog). */\n\t\t\tconfirmAction: null,\n\t\t\t/** @type {object|null} The row the pending confirm-gated action targets. */\n\t\t\tconfirmRow: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The merged detail-page object context for token resolution — both\n\t\t * detail-surface injects, holder fields backfilling (#91 Wave 3) —\n\t\t * or null on surfaces (dashboards) that don't provide one.\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tobjectCtx() {\n\t\t\treturn resolveObjectTokenContext(this.cnObjectContext, this.cnDetailObjectContext)\n\t\t},\n\n\t\t/**\n\t\t * The unwrapped workspace context bag (or null). Vue 2.7 inject may\n\t\t * hand back a raw ref; unwrap `.value` for token resolution.\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tworkspaceCtx() {\n\t\t\tconst c = this.cnWorkspaceContext\n\t\t\tif (!c) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn (typeof c === 'object' && 'value' in c) ? c.value : c\n\t\t},\n\n\t\t/**\n\t\t * Token-resolution context merged from the detail-page object context\n\t\t * and the page-level workspace bag (`@workspace.<key>`).\n\t\t *\n\t\t * @return {object}\n\t\t */\n\t\ttokenCtx() {\n\t\t\tconst base = this.objectCtx ? { ...this.objectCtx } : {}\n\t\t\tbase.workspace = this.workspaceCtx || {}\n\t\t\treturn base\n\t\t},\n\n\t\t/**\n\t\t * `source.filter` with every @-token resolved against `tokenCtx`, then\n\t\t * with any UNRESOLVED OPTIONAL clause (`@workspace.<key>?`) dropped —\n\t\t * an optional clause simply shows all rows until its context is set.\n\t\t *\n\t\t * @return {object}\n\t\t */\n\t\tresolvedFilter() {\n\t\t\treturn dropOptionalUnresolved(resolveFilterTokens((this.source && this.source.filter) || {}, this.tokenCtx))\n\t\t},\n\n\t\t/**\n\t\t * Whether a REQUIRED context token in `source.filter` is still\n\t\t * unresolved — the widget then skips the fetch instead of querying the\n\t\t * whole register.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\twaitingForContext() {\n\t\t\treturn !!this.source && hasUnresolvedTokens(this.resolvedFilter)\n\t\t},\n\n\t\t/**\n\t\t * Whether the Wave-2 `endpointSource` drives the rows: a usable\n\t\t * endpoint config and no external rows (external rows always win).\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tendpointActive() {\n\t\t\treturn !!(this.endpointSource && this.endpointSource.url)\n\t\t\t\t&& (!this.rows || this.rows.length === 0)\n\t\t},\n\n\t\t/**\n\t\t * Rows resolved from the endpoint payload. The payload (after the\n\t\t * `responsePath` pluck the shared engine applies) must be an ARRAY;\n\t\t * anything else renders as an empty table rather than crashing the\n\t\t * column renderer.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\tendpointRows() {\n\t\t\treturn Array.isArray(this.epData) ? this.epData : []\n\t\t},\n\n\t\t/**\n\t\t * Whether the declarative `source` drives a self-fetch: a usable\n\t\t * source, no external rows (external rows always win), no active\n\t\t * endpoint binding (exactly-one-of — endpointSource wins when both\n\t\t * slip past the validator), and no unresolved required token.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tselfFetchActive() {\n\t\t\treturn !!(this.source && this.source.register && this.source.schema)\n\t\t\t\t&& (!this.rows || this.rows.length === 0)\n\t\t\t\t&& !this.endpointActive\n\t\t\t\t&& !this.waitingForContext\n\t\t},\n\n\t\t/**\n\t\t * Query params handed to CnDataTable's self-fetch: the resolved filter\n\t\t * as direct field params (OpenRegister object-search shape),\n\t\t * `_order[field]` entries from `source.order`, and — when\n\t\t * `source.limit` is set — `_limit` of limit + 1 so CnDataTable's\n\t\t * View-all footer can detect that more rows exist while the display\n\t\t * caps at `limit`.\n\t\t *\n\t\t * IN-lists: an ARRAY filter value (either directly on the field or via\n\t\t * the `{ in: [...] }` operator form) is emitted as the bare field with\n\t\t * the array value, which axios serializes as repeated bracket params\n\t\t * (`field[]=a&field[]=b`) — the same shape `buildQueryString` /\n\t\t * `useObjectStore` send and the only IN form OpenRegister parses (a\n\t\t * `field[in]`-style key is NOT understood server-side). Empty arrays\n\t\t * are skipped (no constraint).\n\t\t *\n\t\t * @return {object}\n\t\t */\n\t\tresolvedFetchParams() {\n\t\t\tconst src = this.source || {}\n\t\t\tconst params = {}\n\t\t\tfor (const [field, dir] of Object.entries(src.order || {})) {\n\t\t\t\tparams[`_order[${field}]`] = dir === 'desc' ? 'desc' : 'asc'\n\t\t\t}\n\t\t\tif (typeof src.limit === 'number' && src.limit > 0) {\n\t\t\t\tparams._limit = src.limit + 1\n\t\t\t}\n\t\t\t// `source.extend` (#91 Wave 3): forwarded as OR's repeated\n\t\t\t// `_extend[]` param so virtual/declarative fields (e.g.\n\t\t\t// `calculations` — procest daysOverdue) ride the fetch and are\n\t\t\t// displayable as ordinary columns. Serialized by the shared\n\t\t\t// query builder (array value → repeated `key[]`).\n\t\t\tif (Array.isArray(src.extend) && src.extend.length > 0) {\n\t\t\t\tparams._extend = src.extend.filter((e) => typeof e === 'string' && e !== '')\n\t\t\t}\n\t\t\tconst filter = this.resolvedFilter\n\t\t\tif (filter && typeof filter === 'object') {\n\t\t\t\tfor (const [k, v] of Object.entries(filter)) {\n\t\t\t\t\tif (Array.isArray(v)) {\n\t\t\t\t\t\tif (v.length > 0) {\n\t\t\t\t\t\t\tparams[k] = v\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (v && typeof v === 'object') {\n\t\t\t\t\t\tfor (const [op, ov] of Object.entries(v)) {\n\t\t\t\t\t\t\tif (op === 'in' && Array.isArray(ov)) {\n\t\t\t\t\t\t\t\t// OR's IN form is the bare repeated field param.\n\t\t\t\t\t\t\t\tif (ov.length > 0) {\n\t\t\t\t\t\t\t\t\tparams[k] = ov\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tparams[`${k}[${op}]`] = ov\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (v !== '' && v !== null && v !== undefined) {\n\t\t\t\t\t\tparams[k] = v\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn params\n\t\t},\n\n\t\t/**\n\t\t * `$props` minus the chrome props (`title`, `documentationUrl`,\n\t\t * `widgetId`, `hideWrapper`) and the widget-only props (`source`,\n\t\t * `endpointSource`, `actions`, `rowRoute`), so they are consumed here\n\t\t * and never forwarded to the inner CnDataTable. `undefined` values are\n\t\t * dropped so CnDataTable's own prop defaults apply. When the\n\t\t * declarative `source` is active it supplies `register` / `schemaId` /\n\t\t * `fetchParams` / `limit`; an `@resolve:` register sentinel passes\n\t\t * through unexpanded. When the Wave-2 `endpointSource` is active it\n\t\t * supplies the resolved `rows` + `loading` instead — columns,\n\t\t * formatters, and row navigation apply unchanged on top.\n\t\t *\n\t\t * @return {object}\n\t\t */\n\t\tinnerProps() {\n\t\t\tconst { title, documentationUrl, widgetId, hideWrapper, source, endpointSource, actions, rowRoute, rowClass, schema, ...rest } = this.$props\n\t\t\tconst inner = {}\n\t\t\tfor (const [k, v] of Object.entries(rest)) {\n\t\t\t\tif (v !== undefined) {\n\t\t\t\t\tinner[k] = v\n\t\t\t\t}\n\t\t\t}\n\t\t\t// This widget's `schema` is a slug, while CnDataTable's `schema` is a\n\t\t\t// schema object, so it is not forwarded. Nor is it mapped to `schemaId`:\n\t\t\t// that would turn on CnDataTable's unscoped self-fetch whenever the\n\t\t\t// widget's own rows are empty. Self-fetch comes from `source` alone.\n\t\t\t// `rowClass` is consumed here (function or declarative rules[]) and\n\t\t\t// forwarded to CnDataTable as a compiled `(row) => class` function.\n\t\t\tif (this.compiledRowClass) {\n\t\t\t\tinner.rowClass = this.compiledRowClass\n\t\t\t}\n\t\t\tif (this.selfFetchActive) {\n\t\t\t\tinner.register = this.source.register\n\t\t\t\tinner.schemaId = this.source.schema\n\t\t\t\tinner.fetchParams = this.resolvedFetchParams\n\t\t\t\tif (typeof this.source.limit === 'number' && this.source.limit > 0) {\n\t\t\t\t\tinner.limit = this.source.limit\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (this.endpointActive) {\n\t\t\t\tinner.rows = this.endpointRows\n\t\t\t\tinner.loading = this.epLoading || this.loading\n\t\t\t}\n\t\t\tif (this.rowRoute) {\n\t\t\t\tinner.rowClickRoute = this.rowRouteFn\n\t\t\t}\n\t\t\treturn inner\n\t\t},\n\n\t\t/**\n\t\t * The effective `rowClass` function forwarded to CnDataTable. A\n\t\t * host-supplied FUNCTION passes straight through; a declarative\n\t\t * rules[] array is compiled into a `(row) => string` that space-joins\n\t\t * every rule whose shared `visibleWhen` predicate holds against the\n\t\t * row (`field` dot-path + `op` + `value`). Null when `rowClass` is\n\t\t * unset or an empty array — the widget then forwards nothing so\n\t\t * CnDataTable's own default applies.\n\t\t *\n\t\t * @return {?(row: object) => string} The row-class function, or null.\n\t\t */\n\t\tcompiledRowClass() {\n\t\t\tconst rc = this.rowClass\n\t\t\tif (typeof rc === 'function') {\n\t\t\t\treturn rc\n\t\t\t}\n\t\t\tif (!Array.isArray(rc) || rc.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst rules = rc\n\t\t\treturn (row) => {\n\t\t\t\tconst classes = []\n\t\t\t\tfor (const rule of rules) {\n\t\t\t\t\tif (!rule || !rule.class) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tconst when = rule.when || {}\n\t\t\t\t\tconst actual = readVisibleWhenPath(row, when.field)\n\t\t\t\t\tif (compareVisibleWhen(actual, when.op || 'eq', when.value)) {\n\t\t\t\t\t\tclasses.push(rule.class)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn classes.join(' ')\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Row-click navigation function derived from the `rowRoute` route\n\t\t * name (CnDataTable's `rowClickRoute` shape).\n\t\t *\n\t\t * @return {(row: object) => (object|null)} A vue-router location, or null for a row with no id.\n\t\t */\n\t\trowRouteFn() {\n\t\t\treturn (row) => {\n\t\t\t\tconst id = row && (row.id || (row['@self'] && row['@self'].id))\n\t\t\t\treturn id ? { name: this.rowRoute, params: { id: String(id) } } : null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Actions rendered per row: every non-`object-op` type plus\n\t\t * `object-op` `patch` / `delete` (they mutate THAT row's object).\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\trowScopedActions() {\n\t\t\treturn (this.actions || []).filter((a) => a && (a.type !== 'object-op' || a.op === 'patch' || a.op === 'delete'))\n\t\t},\n\n\t\t/**\n\t\t * `object-op` `create` actions — widget-scoped (no row to mutate), so\n\t\t * they render in the list footer, never in the row menu.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\tcreateActions() {\n\t\t\treturn (this.actions || []).filter((a) => a && a.type === 'object-op' && a.op === 'create')\n\t\t},\n\n\t\t/**\n\t\t * Row-scoped actions mapped onto the CnRowActions shape: label + icon\n\t\t * pass through, `object-op` `delete` renders destructive, and the\n\t\t * handler routes back through the widget's confirm-gated dispatcher.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\tmappedRowActions() {\n\t\t\treturn this.rowScopedActions.map((action) => ({\n\t\t\t\tlabel: action.label,\n\t\t\t\ticon: action.icon,\n\t\t\t\tdestructive: action.type === 'object-op' && action.op === 'delete',\n\t\t\t\thandler: (row) => this.onActionTriggered(action, row),\n\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * Host-supplied CnDataTable scoped slots to forward (keys only). The\n\t\t * `row-actions` slot is withheld while the widget renders its own\n\t\t * declarative actions menu.\n\t\t *\n\t\t * @return {object}\n\t\t */\n\t\tforwardedScopedSlots() {\n\t\t\tconst out = {}\n\t\t\tfor (const name of Object.keys(this.$slots || {})) {\n\t\t\t\tif (name === 'row-actions' && this.rowScopedActions.length > 0) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tout[name] = true\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\t/**\n\t\t * Confirmation question for the pending confirm-gated action —\n\t\t * destructive copy for `delete`, a generic confirm otherwise.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tconfirmMessage() {\n\t\t\tif (this.confirmAction && this.confirmAction.op === 'delete') {\n\t\t\t\treturn t('nextcloud-vue', 'Are you sure you want to delete this item? This action cannot be undone.')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Are you sure you want to continue?')\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Entry point for every declarative action trigger (row menu or\n\t\t * footer create button). `object-op` `delete` ALWAYS confirms;\n\t\t * `patch` / `create` confirm only when the action sets\n\t\t * `confirm: true`; everything else dispatches immediately.\n\t\t *\n\t\t * @param {object} action The manifest action.\n\t\t * @param {object|null} row The row for row-scoped actions (null for create).\n\t\t * @return {Promise<void>|void}\n\t\t */\n\t\tonActionTriggered(action, row) {\n\t\t\tif (action && action.type === 'object-op' && (action.op === 'delete' || action.confirm === true)) {\n\t\t\t\tthis.confirmAction = action\n\t\t\t\tthis.confirmRow = row\n\t\t\t\treturn\n\t\t\t}\n\t\t\treturn this.runAction(action, row)\n\t\t},\n\n\t\t/**\n\t\t * Confirm-dialog primary button: run the pending action and report\n\t\t * the outcome back into the dialog's result phase.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onConfirmConfirmed() {\n\t\t\tconst outcome = await this.runAction(this.confirmAction, this.confirmRow)\n\t\t\tconst dialog = this.$refs.confirmDialog\n\t\t\tif (dialog) {\n\t\t\t\tdialog.setResult(outcome.ok ? { success: true } : { error: outcome.error })\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Close/cancel the confirm dialog and clear the pending action.\n\t\t */\n\t\tcloseConfirm() {\n\t\t\tthis.confirmAction = null\n\t\t\tthis.confirmRow = null\n\t\t},\n\n\t\t/**\n\t\t * Dispatch an action. Non-mutating types go through the page's\n\t\t * pre-bound `cnDispatchAction` (falling back to a bare\n\t\t * `dispatchAction`); for `handler` actions the row is appended as the\n\t\t * final handler argument so row context reaches the registry function.\n\t\t * `object-op` actions dispatch via the shared object store — a\n\t\t * rejected (RBAC) write surfaces `opError` and mutates NO local state;\n\t\t * a success clears the error and refreshes the self-fetched rows.\n\t\t *\n\t\t * @param {object} action The manifest action.\n\t\t * @param {object|null} row The target row (null for create).\n\t\t * @return {Promise<{ok: boolean, error?: string}>}\n\t\t */\n\t\tasync runAction(action, row) {\n\t\t\tif (!action) {\n\t\t\t\treturn { ok: false, error: '' }\n\t\t\t}\n\t\t\tif (action.type !== 'object-op') {\n\t\t\t\tconst wrapped = (!action.type || action.type === 'handler')\n\t\t\t\t\t? { ...action, args: [...(action.args || []), row] }\n\t\t\t\t\t: action\n\t\t\t\tif (typeof this.cnDispatchAction === 'function') {\n\t\t\t\t\tthis.cnDispatchAction(wrapped)\n\t\t\t\t} else {\n\t\t\t\t\tdispatchAction(wrapped, { router: this.$router || null })\n\t\t\t\t}\n\t\t\t\treturn { ok: true }\n\t\t\t}\n\n\t\t\tconst store = this.getObjectStore()\n\t\t\tconst result = await dispatchAction(action, {\n\t\t\t\tobjectStore: store,\n\t\t\t\tsource: this.source,\n\t\t\t\trow,\n\t\t\t})\n\t\t\tconst ok = !(result === null || result === false || result === undefined)\n\t\t\tif (!ok) {\n\t\t\t\tconst error = this.readStoreError(store)\n\t\t\t\tthis.opError = error\n\t\t\t\treturn { ok: false, error }\n\t\t\t}\n\t\t\tthis.opError = ''\n\t\t\tthis.refresh()\n\t\t\t/**\n\t\t\t * @event object-op Emitted after a successful declarative mutation\n\t\t\t * so a host that supplies external `rows` can refetch them.\n\t\t\t * @type {{action: object, row: (object|null), result: unknown}}\n\t\t\t */\n\t\t\tthis.$emit('object-op', { action, row, result })\n\t\t\treturn { ok: true }\n\t\t},\n\n\t\t/**\n\t\t * Re-run the active fetch: the endpoint binding (force-refetch past\n\t\t * the shared cache) or the inner CnDataTable's self-fetch. No-op when\n\t\t * the widget is fed external rows.\n\t\t */\n\t\trefresh() {\n\t\t\tif (this.endpointActive) {\n\t\t\t\tthis.epRefetch()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst table = this.$refs.dataTable\n\t\t\tif (this.selfFetchActive && table && typeof table.fetchData === 'function') {\n\t\t\t\ttable.fetchData()\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The shared object store instance, or null when no Pinia is active\n\t\t * (`dispatchAction` then warns and no-ops).\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tgetObjectStore() {\n\t\t\ttry {\n\t\t\t\treturn useObjectStore()\n\t\t\t} catch {\n\t\t\t\treturn null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Human-readable message for the last store error on this widget's\n\t\t * source type (RBAC rejection, network failure, …).\n\t\t *\n\t\t * @param {object|null} store The object store used for the dispatch.\n\t\t * @return {string}\n\t\t */\n\t\treadStoreError(store) {\n\t\t\ttry {\n\t\t\t\tif (store && this.source) {\n\t\t\t\t\tconst type = resolveObjectOpType(store, this.source)\n\t\t\t\t\tconst err = store.errors && store.errors[type]\n\t\t\t\t\tif (err && err.message) {\n\t\t\t\t\t\treturn err.message\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// fall through to the generic message\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'The operation was rejected')\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-widget-object-table {\n\twidth: 100%;\n}\n\n.cn-widget-object-table__footer {\n\tdisplay: flex;\n\tgap: 4px;\n\tpadding: 4px 8px;\n\tborder-top: 1px solid var(--color-border);\n}\n\n.cn-widget-object-table__error {\n\tcolor: var(--color-text-error, var(--color-error-text));\n\tfont-size: 0.85em;\n\tmargin: 4px 8px 0;\n}\n</style>\n"],"names":["_normalizeProps","_guardReactiveProps","_createElementVNode","_createSlots","_withCtx","_renderSlot","_createVNode","_openBlock","_createElementBlock","_Fragment","_renderList","_toDisplayString"],"mappings":";;AA6CO,MAAA,UAAA,GAAA,EAAA,KAAK,EAAA,wBAAA,GAAA;;;AAkBN,EAAA,KAAA,EAAA,gCAAA;;;;;;;;;;;;;;;iEApBY,CAAA,WAAA,GAAA,mBAAA,GAAA,iBAAA,CAAA,EAAAA,cAAA,CAAAC,kBAAA,CACR,MAAW,CAAA,WAAA,GAAA,EAAA,GAAA,EAAA,KAAA,EAAU,MAAM,CAAA,KAAA,EAAA,QAAA,EAAC,MAAQ,CAAA,QAAA,EAAA,gBAAA,EAAE,MAAgB,CAAA,gBAAA,EAAA,KAAA,EAAA,IAAA,EAAA,UAAA,EAAc,MAAA,CAAA,SAAA,EAAA,CAAA,CAAA,EAAA;qBAC1E;AAAA,MAAAC,kBAAA,QAAA;mBAaa,CAAA,sBAAA,aAAA,EAZD,GAAG,EAAC,WAAW,EAAA,EAAA,EAAA,GAAI,QAAA,CAAA,UAAA,EAAA,GAAA,IAAA,CAAA,MAAA,EAAA,CAAA,EAAAC,WAAA,CAAA,EAAA,CAAA,EAAA,CAAA,gBAAA,EAAA;qBAKD,QAAK,CAAA,oBAAA,EAAA,CAAjB,CAAC,EAAE,IAAI,KAAA;;oBAAU,IAAA;AAElC,cAAA,EAAA,EAAAC,OAAA,CAAA,CAFkC,QAAA,KAAA;gBAElCC,UAAU,CAAA,IAAA,CAAA,MAAA,EAAA,IAAA,EAAAL,cAAA,CAAAC,kBAAA,CAAA,QAAA,CAAA,CAAA,CAAA;;;;AAED,UAAA,CAAA,QAAA,CAAA,gBAAA,CAAA,MAAA,GAAA,CAAA;;sBAAA,aAAA;AACT,gBAAA,EAAA,EAAAG,OAAA,CAAA,CAAG,EADM,GAAA,EAAA,KAAA;kBACTE,WAAG,CAAA,uBAAA,EAAA;AAAA,oBAAA,OAAA,EAAA,QAAA,CAAA,gBAAA;AAAA,oBAAA,GAAA,EAAA,GAAA;;;;;;;AAOF,QAAA,CAAA,QAAA,CAAA,aAAA,CAAA,MAAA,GAAA,CAAA;AAAH,aAAAC,SAAA,EAAA,EAAAC,kBAAA,CAYM,OAZN,UAYM,EAAA;kDADF,CAAAC,QAAA,EAAA,IAAA,EAAAC,UAAA,CATW,QAAA,CAAA,aAAA,EAAA,CAAN,MAAM,KAAA;gDASX,CAAA,mBAAA,EAAA;AARD,kBAAA,GAAG,EAAE,MAAM,CAAC,EAAE,IAAG,MAAA,CAAA,KAAA;AAClB,kBAAA,OAAO,EAAC,UAAS;kBAChB,aAAW,EAAA,CAAA,iBAAA,EAAoB,MAAA,CAAA,EAAA,IAAA,QAAA,CAAA,CAAA;kBAC/B,OAAK,EAAA,MAAA,KAAE,QAAe,CAAA,iBAAA,CAAA,MAAA,EAAA,IAAA,CAAA,CAAA;;mCAGZ,MACX;oCADW,GACX,GAAAC,eAAA,CAAG,MAAM,CAAC,KAAK,CAAA,EAAA,CAAA,YAAA;;;;AAHC,kBAAA,CAAA,MAAM,CAAC,IAAI;;8BAAG,MAAI;oCACjC,MAAgC;0BAAhCL,WAAgC,CAAA,iBAAA,EAAA;4BAAvB,IAAI,EAAE,MAAM,CAAC,IAAI;AAAG,4BAAA,IAAG,EAAA,EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"CnWidgetObjectTable.vue3.js","sources":["../../../../src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue?vue&type=template&id=f6accf5e&lang.js"],"sourcesContent":["<!--\n CnWidgetObjectTable — built-in v2 widget wrapping CnDataTable.\n\n Referenced in v2 manifests via `widgetKey: \"object-table\"`. Renders the\n table on the shared CnWidgetWrapper chrome (title + standard overflow\n Actions menu: Refresh / Documentation / Request a feature) and forwards\n all data props to CnDataTable so the table itself is a transparent\n pass-through.\n\n ADR-049 enrichment: a declarative token-resolved `source` drives\n CnDataTable's existing self-fetch (externally supplied `rows` always win),\n the compact-list surface (hideHeader / borderless / rowRoute /\n viewAllRoute / emptyText / rowIcon / #footer) passes straight through,\n and a declarative `actions[]` renders row-scoped actions via CnRowActions\n plus widget-scoped `object-op` create affordances in the footer.\n\n Spec: REQ-MVR-006 (manifest-v2-renderer) — built-in widget: object-table\n Spec: openspec/changes/list-widget-enrichment/specs/manifest-v2-renderer/spec.md\n-->\n<template>\n\t<!-- `refreshing` mirrors the endpoint fetch so the chrome's Refresh\n\t action spins (and stays disabled) for the real duration of an\n\t in-flight refetch. -->\n\t<component\n\t\t:is=\"hideWrapper ? 'CnWidgetHostShell' : 'CnWidgetWrapper'\"\n\t\tv-bind=\"hideWrapper ? {} : { title, widgetId, documentationUrl, flush: true, refreshing: epLoading }\">\n\t\t<div class=\"cn-widget-object-table\">\n\t\t\t<CnDataTable ref=\"dataTable\" v-bind=\"{ ...innerProps, ...$attrs }\">\n\t\t\t\t<!-- Forward every host-supplied CnDataTable scoped slot verbatim\n\t\t\t\t (#footer { total, shown }, #empty, #column-<key>, …). The\n\t\t\t\t #row-actions slot is NOT forwarded while the widget renders\n\t\t\t\t its own declarative actions[] menu. -->\n\t\t\t\t<template v-for=\"(_, name) in forwardedScopedSlots\" #[name]=\"slotData\">\n\t\t\t\t\t<!-- @slot Any CnDataTable scoped slot, forwarded verbatim — e.g. #footer ({ total, shown }), #empty, #column-<key>. #row-actions is withheld while the declarative actions[] menu renders. -->\n\t\t\t\t\t<slot :name=\"name\" v-bind=\"slotData\" />\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"rowScopedActions.length > 0\" #row-actions=\"{ row }\">\n\t\t\t\t\t<CnRowActions :actions=\"mappedRowActions\" :row=\"row\" />\n\t\t\t\t</template>\n\t\t\t</CnDataTable>\n\n\t\t\t<!-- Widget-scoped object-op create affordance: `create` has no row\n\t\t\t to mutate, so it renders in the list footer, never inside the\n\t\t\t per-row actions menu (ADR-049 / list-widget-enrichment). -->\n\t\t\t<div v-if=\"createActions.length > 0\" class=\"cn-widget-object-table__footer\">\n\t\t\t\t<NcButton\n\t\t\t\t\tv-for=\"action in createActions\"\n\t\t\t\t\t:key=\"action.id || action.label\"\n\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t:data-testid=\"`cn-widget-create-${action.id || 'action'}`\"\n\t\t\t\t\t@click=\"onActionTriggered(action, null)\">\n\t\t\t\t\t<template v-if=\"action.icon\" #icon>\n\t\t\t\t\t\t<CnIcon :name=\"action.icon\" :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ action.label }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\n\t\t\t<!-- A rejected write (OpenRegister RBAC) surfaces here — local row\n\t\t\t state is never mutated on failure (the store only writes its\n\t\t\t caches on success). -->\n\t\t\t<p v-if=\"opError\" class=\"cn-widget-object-table__error\" data-testid=\"cn-widget-object-table-error\">\n\t\t\t\t{{ opError }}\n\t\t\t</p>\n\n\t\t\t<CnConfirmDialog\n\t\t\t\tv-if=\"confirmAction\"\n\t\t\t\tref=\"confirmDialog\"\n\t\t\t\t:dialogTitle=\"confirmAction.label\"\n\t\t\t\t:message=\"confirmMessage\"\n\t\t\t\t:variant=\"confirmAction.op === 'delete' ? 'error' : 'primary'\"\n\t\t\t\t:confirmLabel=\"confirmAction.label\"\n\t\t\t\t@confirm=\"onConfirmConfirmed\"\n\t\t\t\t@close=\"closeConfirm\" />\n\t\t</div>\n\t</component>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton } from '@nextcloud/vue'\nimport { inject, ref } from 'vue'\nimport CnConfirmDialog from '../../dialogs/CnConfirmDialog.vue'\nimport CnDataTable from '../CnDataTable/CnDataTable.vue'\nimport { useEndpointSource } from '../../composables/useEndpointSource.js'\nimport { useObjectStore } from '../../store/useObjectStore.js'\nimport { dispatchAction, resolveObjectOpType } from '../../utils/actionsDispatcher.js'\nimport { resolveObjectTokenContext } from '../../utils/detailObjectContext.js'\nimport { dropOptionalUnresolved, hasUnresolvedTokens, resolveFilterTokens } from '../../utils/resolveFilterTokens.js'\nimport { compareVisibleWhen, readVisibleWhenPath } from '../../utils/visibleWhen.js'\nimport { CnIcon } from '../CnIcon/index.js'\nimport { CnRowActions } from '../CnRowActions/index.js'\nimport { CnWidgetWrapper } from '../CnWidgetWrapper/index.js'\n// Chrome-less pass-through used when `hideWrapper` is set (see hostShell.js\n// for why it lives in its own module).\nimport { CnWidgetHostShell } from './hostShell.js'\n\n/**\n * CnWidgetObjectTable — built-in v2 widget wrapping CnDataTable.\n *\n * Renders a data table on the shared CnWidgetWrapper chrome, which supplies\n * the widget title and the standard overflow Actions menu (Refresh /\n * Documentation / Request a feature). All data props are forwarded to the\n * inner CnDataTable; the chrome props (`title`, `documentationUrl`,\n * `widgetId`) are consumed by the wrapper and not passed down.\n *\n * A declarative `source` makes the widget self-fetch from OpenRegister:\n * `source.filter` is token-resolved with the shared `resolveFilterTokens`\n * grammar (`@me`, `@today`, `@workspace.*`, `?`-optional clauses dropped\n * when unresolved via `dropOptionalUnresolved`), `source.order` /\n * `source.limit` drive the fetch, and an `@resolve:` register sentinel is\n * passed through unexpanded (resolution is the host loader's job).\n * Externally supplied `rows` always win — the pre-existing pass-through\n * interface is unchanged.\n *\n * Wave 2 (#91): a declarative `endpointSource` (`{ url, method?, params?,\n * responsePath? }`) is the alternative to the OpenRegister `source` — rows\n * come from an app REST endpoint's payload at `responsePath`, fetched by the\n * shared `useEndpointSource` engine (token-resolved params, request dedup +\n * short-TTL cache, `cn:page:refresh` / `cn:widget:refresh` subscription).\n * Exactly one of `source` | `endpointSource`; columns / formatters /\n * `rowRoute` / `actions` apply unchanged on top of endpoint rows.\n *\n * A declarative `actions[]` array renders `patch` / `delete` `object-op`\n * actions (and reused `handler` / `open-modal` / `open-page` / `navigate`\n * actions) per row through `CnRowActions`; an `object-op` `create` action\n * renders as a widget-scoped footer affordance. `delete` is ALWAYS\n * confirm-gated through `CnConfirmDialog`; `patch` / `create` confirm only\n * on `confirm: true`. Mutations dispatch via the shared object store —\n * the manifest declares intent only, OpenRegister RBAC is the authority,\n * and a rejected write surfaces as an error without local mutation.\n *\n * A declarative `rowClass` (#91) — `[{ when: { field, op?, value }, class }]` reusing\n * the shared `visibleWhen` predicate grammar — is compiled into CnDataTable's\n * `rowClass` function so overdue / at-risk rows can be highlighted from the manifest\n * (a host-supplied `rowClass` FUNCTION still passes straight through).\n */\nexport default {\n\tname: 'CnWidgetObjectTable',\n\n\tcomponents: { CnDataTable, CnWidgetWrapper, CnWidgetHostShell, CnRowActions, CnIcon, NcButton, CnConfirmDialog },\n\n\tinject: {\n\t\t/**\n\t\t * Detail-page object context (`{ objectId, object, register, schema }`)\n\t\t * provided by CnDetailPage — enables `@objectId` / `@object.<field>`\n\t\t * tokens in `source.filter`. Null on dashboards.\n\t\t */\n\t\tcnObjectContext: { default: null },\n\t\t/**\n\t\t * v2 slot-grid detail context holder (`{ value: { objectData, schema,\n\t\t * objectType, objectId, register, store } | null }`) provided by\n\t\t * CnPageRenderer — backfills the object token context so\n\t\t * `@objectId` / `@object.<field>` resolve on detail surfaces where\n\t\t * CnDetailPage is not an ancestor (#91 Wave 3).\n\t\t */\n\t\tcnDetailObjectContext: { default: null },\n\t\t/**\n\t\t * Page-level workspace context (reactive `ref({})`) provided by\n\t\t * CnDashboardPage — enables `@workspace.<key>` tokens in\n\t\t * `source.filter`. Null on pages that don't provide it.\n\t\t */\n\t\tcnWorkspaceContext: { default: null },\n\t\t/**\n\t\t * Pre-bound `dispatchAction` provided by CnPageRenderer (router,\n\t\t * registry, handlers, openModal already wired). Used for the reused\n\t\t * non-mutating action types; falls back to a direct `dispatchAction`\n\t\t * call when mounted outside a CnPageRenderer tree.\n\t\t */\n\t\tcnDispatchAction: { default: null },\n\t},\n\n\tinheritAttrs: false,\n\n\tprops: {\n\t\t/**\n\t\t * Widget title shown in the CnWidgetWrapper header.\n\t\t */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Table'),\n\t\t},\n\n\t\t/**\n\t\t * Documentation link surfaced in the widget's overflow Actions menu.\n\t\t * Empty (the default) hides the Documentation item; the Refresh and\n\t\t * Request-a-feature items always render.\n\t\t */\n\t\tdocumentationUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Stable id forwarded to the widget chrome for the Refresh /\n\t\t * Request-a-feature payloads.\n\t\t */\n\t\twidgetId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Render content-only, WITHOUT the widget's own CnWidgetWrapper\n\t\t * chrome. Set by hosts that already provide the card chrome — e.g.\n\t\t * CnDashboardPage's dashboard widget grid, whose registry branch\n\t\t * wraps every catalog widget in its own CnWidgetWrapper (a naive\n\t\t * mount would double-card). Default `false` keeps the pre-existing\n\t\t * self-chromed v2 rendering byte-for-byte.\n\t\t */\n\t\thideWrapper: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Register slug. Forwarded to CnDataTable. */\n\t\tregister: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Schema slug. Forwarded to CnDataTable. */\n\t\tschema: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Column definitions. Forwarded to CnDataTable — bare string keys OR\n\t\t * the object form (`{ key, label, sortable, width, cellClass,\n\t\t * formatter, widget, format, aggregate }`) both pass straight through.\n\t\t *\n\t\t * @type {Array<object|string>}\n\t\t */\n\t\tcolumns: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Rows array. Forwarded to CnDataTable. External rows always win over `source`. */\n\t\trows: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Loading state. Forwarded to CnDataTable. */\n\t\tloading: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Declarative self-fetch source (ADR-049). When set and no external\n\t\t * `rows` are supplied, the widget resolves `filter` @-tokens and\n\t\t * drives CnDataTable's existing self-fetch with the resolved filter,\n\t\t * `order`, and `limit`. `register` MAY carry an `@resolve:` sentinel —\n\t\t * it is passed through unexpanded. `extend` (#91 Wave 3) forwards\n\t\t * OpenRegister `_extend[]` values (e.g. `[\"calculations\"]`) on the\n\t\t * fetch so virtual/declarative calc fields ride along and render as\n\t\t * ordinary columns. Default `null` keeps the pre-existing\n\t\t * pass-through behaviour byte-for-byte.\n\t\t *\n\t\t * @type {{register?: string, schema?: string, filter?: object, order?: object, limit?: number, extend?: string[]}|null}\n\t\t */\n\t\tsource: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Endpoint data binding (Wave 2, #91) — the alternative to the\n\t\t * OpenRegister `source` for rows an app REST endpoint computes (e.g. a\n\t\t * per-source performance report). Rows come from the payload at\n\t\t * `responsePath` (a dot-path; the plucked value must be an array —\n\t\t * anything else renders as empty). `params` values use the shared\n\t\t * filter-token grammar (`@workspace.dateFrom?`, `@me`, `@today±Nd`, …)\n\t\t * and re-resolve + refetch when the page context changes. Requests are\n\t\t * deduped + short-TTL cached per (url+params) by the shared\n\t\t * `useEndpointSource` engine, which also subscribes to\n\t\t * `cn:page:refresh` / `cn:widget:refresh`. Exactly one of `source` |\n\t\t * `endpointSource` (validator-enforced); external `rows` still win\n\t\t * over both. `columns` / formatters / `rowRoute` / `actions` apply\n\t\t * unchanged on top of endpoint rows.\n\t\t *\n\t\t * @type {{url: string, method?: string, params?: object, responsePath?: string}|null}\n\t\t */\n\t\tendpointSource: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Declarative actions (unified manifest action shape — `handler` |\n\t\t * `open-modal` | `open-page` | `navigate` | `object-op`). `object-op`\n\t\t * `patch` / `delete` (and every non-mutating type) render per row via\n\t\t * CnRowActions; `object-op` `create` renders as a widget-scoped footer\n\t\t * affordance. `delete` always confirms; `patch` / `create` confirm on\n\t\t * `confirm: true`. Authorization-shaped fields on an action have no\n\t\t * effect — OpenRegister RBAC is the only authority.\n\t\t *\n\t\t * @type {Array<object>}\n\t\t */\n\t\tactions: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Hide the column-header row. Forwarded to CnDataTable — the compact\n\t\t * dashboard-list surface.\n\t\t */\n\t\thideHeader: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Drop CnDataTable's card chrome (border, radius, shadow) so the table\n\t\t * sits flush inside the widget card. Forwarded to CnDataTable.\n\t\t */\n\t\tborderless: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Fit the table to the tile: the title column (a column's `grow: true`,\n\t\t * else `title` or `name`, else the first) shrinks and ends in an\n\t\t * ellipsis so the trailing columns stay visible. On by default; pass\n\t\t * `false` for the table's own auto layout. Forwarded to CnDataTable.\n\t\t *\n\t\t * @spec openspec/changes/narrow-object-table-widgets/specs/narrow-object-table-widgets/spec.md#requirement-a-narrow-object-table-keeps-its-trailing-columns-in-view\n\t\t */\n\t\tfitWidth: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Route NAME for row-click navigation. Mapped onto CnDataTable's\n\t\t * `rowClickRoute` as `{ name: rowRoute, params: { id } }` with the\n\t\t * row's `id` (or `@self.id`). Empty (the default) = no navigation.\n\t\t */\n\t\trowRoute: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Client-side row cap, forwarded to CnDataTable (`0` = all rows).\n\t\t * This is what makes `viewAllRoute` reachable for `endpointSource`\n\t\t * tables: the endpoint's full payload stays the `totalRowCount`\n\t\t * while CnDataTable renders only the first `limit` rows, so the\n\t\t * \"View all\" footer condition (total > shown) can finally hold.\n\t\t * The OpenRegister `source` mode keeps using `source.limit` (an\n\t\t * explicit assignment that wins over this passthrough).\n\t\t */\n\t\tlimit: {\n\t\t\ttype: Number,\n\t\t\tdefault: 0,\n\t\t},\n\n\t\t/**\n\t\t * vue-router route object for the \"View all\" footer link. Forwarded\n\t\t * to CnDataTable (footer shows when the rows are a `limit`-ed subset).\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tviewAllRoute: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Pre-translated \"View all\" footer label. Forwarded to CnDataTable. */\n\t\tviewAllLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: undefined,\n\t\t},\n\n\t\t/** Text shown when there are no rows. Forwarded to CnDataTable. */\n\t\temptyText: {\n\t\t\ttype: String,\n\t\t\tdefault: undefined,\n\t\t},\n\n\t\t/**\n\t\t * App wording for an unavailable personal lens (a `source.filter._recent`\n\t\t * tile on an instance that does not log reads), keyed `<lens>.<reason>`\n\t\t * or `<reason>`. Forwarded to CnDataTable, which shows the library's\n\t\t * object-neutral text without it.\n\t\t *\n\t\t * @spec openspec/changes/lens-says-why-it-is-empty/specs/personal-lens-availability/spec.md#requirement-an-unavailable-lens-explains-its-empty-page\n\t\t */\n\t\tlensReasonTexts: {\n\t\t\ttype: Object,\n\t\t\tdefault: undefined,\n\t\t},\n\n\t\t/**\n\t\t * Leading per-row icon: a static MDI icon name string, or a function\n\t\t * `(row) => iconName`. Forwarded to CnDataTable.\n\t\t *\n\t\t * @type {string|((row: object) => string)|null}\n\t\t */\n\t\trowIcon: {\n\t\t\ttype: [String, Function],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Per-row CSS class binding (#91). Two forms:\n\t\t * - a FUNCTION `(row) => string` — passed straight through to\n\t\t * CnDataTable's `rowClass` (the pre-existing host-supplied contract).\n\t\t * - an ARRAY of declarative rules `[{ when: { field, op?, value }, class }]`\n\t\t * compiled here into that function. Each rule adds its `class` to a\n\t\t * row when the shared `visibleWhen` predicate holds against the row:\n\t\t * `field` is a dot-path into the row, `op` is `eq|neq|gt|gte|lt|lte`\n\t\t * (default `eq`), `value` is the literal right-hand side. Rules are\n\t\t * evaluated in order and every matching `class` is space-joined, so an\n\t\t * overdue / at-risk row can be highlighted declaratively from a\n\t\t * manifest with no bespoke function. Default `null` = no row class.\n\t\t *\n\t\t * @type {((row: object) => string)|Array<{when: {field: string, op?: string, value: unknown}, class: string}>|null}\n\t\t */\n\t\trowClass: {\n\t\t\ttype: [Function, Array],\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['object-op'],\n\n\tsetup(props) {\n\t\t// Endpoint binding (Wave 2, #91): the shared useEndpointSource engine\n\t\t// owns token resolution, request dedup + TTL caching, and the\n\t\t// cn:page:refresh / cn:widget:refresh subscriptions (the widget's own\n\t\t// `widgetId` is matched against widget-scoped refresh payloads).\n\t\t// No-op while `endpointSource` is null, so the OpenRegister `source`\n\t\t// self-fetch path is untouched. Injects re-read in setup (same\n\t\t// resolution as the Options `inject` block).\n\t\tconst objectCtxRaw = inject('cnObjectContext', null)\n\t\tconst detailCtxRaw = inject('cnDetailObjectContext', null)\n\t\tconst workspaceRaw = inject('cnWorkspaceContext', ref(null))\n\t\tconst appConfigRaw = inject('cnAppConfig', ref({}))\n\t\tconst unwrap = (v) => ((v && typeof v === 'object' && 'value' in v) ? v.value : v)\n\t\t// External rows always win — and suppress the request entirely (the\n\t\t// pre-existing \"when rows are provided, no API calls are made\"\n\t\t// contract), so the getter resolves to null while `rows` are supplied.\n\t\tconst { data, loading, error, refetch } = useEndpointSource(\n\t\t\t() => ((props.rows && props.rows.length > 0) ? null : props.endpointSource),\n\t\t\t{\n\t\t\t\tctx: () => ({\n\t\t\t\t\t...(resolveObjectTokenContext(objectCtxRaw, detailCtxRaw) || {}),\n\t\t\t\t\tworkspace: unwrap(workspaceRaw) || {},\n\t\t\t\t\tconfig: unwrap(appConfigRaw) || {},\n\t\t\t\t}),\n\t\t\t\twidgetId: () => props.widgetId,\n\t\t\t},\n\t\t)\n\t\treturn { epData: data, epLoading: loading, epError: error, epRefetch: refetch }\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t/** Error message from the last rejected object-op write ('' = none). */\n\t\t\topError: '',\n\t\t\t/** @type {object|null} The object-op action awaiting confirmation (null = no dialog). */\n\t\t\tconfirmAction: null,\n\t\t\t/** @type {object|null} The row the pending confirm-gated action targets. */\n\t\t\tconfirmRow: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The merged detail-page object context for token resolution — both\n\t\t * detail-surface injects, holder fields backfilling (#91 Wave 3) —\n\t\t * or null on surfaces (dashboards) that don't provide one.\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tobjectCtx() {\n\t\t\treturn resolveObjectTokenContext(this.cnObjectContext, this.cnDetailObjectContext)\n\t\t},\n\n\t\t/**\n\t\t * The unwrapped workspace context bag (or null). Vue 2.7 inject may\n\t\t * hand back a raw ref; unwrap `.value` for token resolution.\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tworkspaceCtx() {\n\t\t\tconst c = this.cnWorkspaceContext\n\t\t\tif (!c) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn (typeof c === 'object' && 'value' in c) ? c.value : c\n\t\t},\n\n\t\t/**\n\t\t * Token-resolution context merged from the detail-page object context\n\t\t * and the page-level workspace bag (`@workspace.<key>`).\n\t\t *\n\t\t * @return {object}\n\t\t */\n\t\ttokenCtx() {\n\t\t\tconst base = this.objectCtx ? { ...this.objectCtx } : {}\n\t\t\tbase.workspace = this.workspaceCtx || {}\n\t\t\treturn base\n\t\t},\n\n\t\t/**\n\t\t * `source.filter` with every @-token resolved against `tokenCtx`, then\n\t\t * with any UNRESOLVED OPTIONAL clause (`@workspace.<key>?`) dropped —\n\t\t * an optional clause simply shows all rows until its context is set.\n\t\t *\n\t\t * @return {object}\n\t\t */\n\t\tresolvedFilter() {\n\t\t\treturn dropOptionalUnresolved(resolveFilterTokens((this.source && this.source.filter) || {}, this.tokenCtx))\n\t\t},\n\n\t\t/**\n\t\t * Whether a REQUIRED context token in `source.filter` is still\n\t\t * unresolved — the widget then skips the fetch instead of querying the\n\t\t * whole register.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\twaitingForContext() {\n\t\t\treturn !!this.source && hasUnresolvedTokens(this.resolvedFilter)\n\t\t},\n\n\t\t/**\n\t\t * Whether the Wave-2 `endpointSource` drives the rows: a usable\n\t\t * endpoint config and no external rows (external rows always win).\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tendpointActive() {\n\t\t\treturn !!(this.endpointSource && this.endpointSource.url)\n\t\t\t\t&& (!this.rows || this.rows.length === 0)\n\t\t},\n\n\t\t/**\n\t\t * Rows resolved from the endpoint payload. The payload (after the\n\t\t * `responsePath` pluck the shared engine applies) must be an ARRAY;\n\t\t * anything else renders as an empty table rather than crashing the\n\t\t * column renderer.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\tendpointRows() {\n\t\t\treturn Array.isArray(this.epData) ? this.epData : []\n\t\t},\n\n\t\t/**\n\t\t * Whether the declarative `source` drives a self-fetch: a usable\n\t\t * source, no external rows (external rows always win), no active\n\t\t * endpoint binding (exactly-one-of — endpointSource wins when both\n\t\t * slip past the validator), and no unresolved required token.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tselfFetchActive() {\n\t\t\treturn !!(this.source && this.source.register && this.source.schema)\n\t\t\t\t&& (!this.rows || this.rows.length === 0)\n\t\t\t\t&& !this.endpointActive\n\t\t\t\t&& !this.waitingForContext\n\t\t},\n\n\t\t/**\n\t\t * Query params handed to CnDataTable's self-fetch: the resolved filter\n\t\t * as direct field params (OpenRegister object-search shape),\n\t\t * `_order[field]` entries from `source.order`, and — when\n\t\t * `source.limit` is set — `_limit` of limit + 1 so CnDataTable's\n\t\t * View-all footer can detect that more rows exist while the display\n\t\t * caps at `limit`.\n\t\t *\n\t\t * IN-lists: an ARRAY filter value (either directly on the field or via\n\t\t * the `{ in: [...] }` operator form) is emitted as the bare field with\n\t\t * the array value, which axios serializes as repeated bracket params\n\t\t * (`field[]=a&field[]=b`) — the same shape `buildQueryString` /\n\t\t * `useObjectStore` send and the only IN form OpenRegister parses (a\n\t\t * `field[in]`-style key is NOT understood server-side). Empty arrays\n\t\t * are skipped (no constraint).\n\t\t *\n\t\t * @return {object}\n\t\t */\n\t\tresolvedFetchParams() {\n\t\t\tconst src = this.source || {}\n\t\t\tconst params = {}\n\t\t\tfor (const [field, dir] of Object.entries(src.order || {})) {\n\t\t\t\tparams[`_order[${field}]`] = dir === 'desc' ? 'desc' : 'asc'\n\t\t\t}\n\t\t\tif (typeof src.limit === 'number' && src.limit > 0) {\n\t\t\t\tparams._limit = src.limit + 1\n\t\t\t}\n\t\t\t// `source.extend` (#91 Wave 3): forwarded as OR's repeated\n\t\t\t// `_extend[]` param so virtual/declarative fields (e.g.\n\t\t\t// `calculations` — procest daysOverdue) ride the fetch and are\n\t\t\t// displayable as ordinary columns. Serialized by the shared\n\t\t\t// query builder (array value → repeated `key[]`).\n\t\t\tif (Array.isArray(src.extend) && src.extend.length > 0) {\n\t\t\t\tparams._extend = src.extend.filter((e) => typeof e === 'string' && e !== '')\n\t\t\t}\n\t\t\tconst filter = this.resolvedFilter\n\t\t\tif (filter && typeof filter === 'object') {\n\t\t\t\tfor (const [k, v] of Object.entries(filter)) {\n\t\t\t\t\tif (Array.isArray(v)) {\n\t\t\t\t\t\tif (v.length > 0) {\n\t\t\t\t\t\t\tparams[k] = v\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (v && typeof v === 'object') {\n\t\t\t\t\t\tfor (const [op, ov] of Object.entries(v)) {\n\t\t\t\t\t\t\tif (op === 'in' && Array.isArray(ov)) {\n\t\t\t\t\t\t\t\t// OR's IN form is the bare repeated field param.\n\t\t\t\t\t\t\t\tif (ov.length > 0) {\n\t\t\t\t\t\t\t\t\tparams[k] = ov\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tparams[`${k}[${op}]`] = ov\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (v !== '' && v !== null && v !== undefined) {\n\t\t\t\t\t\tparams[k] = v\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn params\n\t\t},\n\n\t\t/**\n\t\t * `$props` minus the chrome props (`title`, `documentationUrl`,\n\t\t * `widgetId`, `hideWrapper`) and the widget-only props (`source`,\n\t\t * `endpointSource`, `actions`, `rowRoute`), so they are consumed here\n\t\t * and never forwarded to the inner CnDataTable. `undefined` values are\n\t\t * dropped so CnDataTable's own prop defaults apply. When the\n\t\t * declarative `source` is active it supplies `register` / `schemaId` /\n\t\t * `fetchParams` / `limit`; an `@resolve:` register sentinel passes\n\t\t * through unexpanded. When the Wave-2 `endpointSource` is active it\n\t\t * supplies the resolved `rows` + `loading` instead — columns,\n\t\t * formatters, and row navigation apply unchanged on top.\n\t\t *\n\t\t * @return {object}\n\t\t */\n\t\tinnerProps() {\n\t\t\tconst { title, documentationUrl, widgetId, hideWrapper, source, endpointSource, actions, rowRoute, rowClass, schema, ...rest } = this.$props\n\t\t\tconst inner = {}\n\t\t\tfor (const [k, v] of Object.entries(rest)) {\n\t\t\t\tif (v !== undefined) {\n\t\t\t\t\tinner[k] = v\n\t\t\t\t}\n\t\t\t}\n\t\t\t// This widget's `schema` is a slug, while CnDataTable's `schema` is a\n\t\t\t// schema object, so it is not forwarded. Nor is it mapped to `schemaId`:\n\t\t\t// that would turn on CnDataTable's unscoped self-fetch whenever the\n\t\t\t// widget's own rows are empty. Self-fetch comes from `source` alone.\n\t\t\t// `rowClass` is consumed here (function or declarative rules[]) and\n\t\t\t// forwarded to CnDataTable as a compiled `(row) => class` function.\n\t\t\tif (this.compiledRowClass) {\n\t\t\t\tinner.rowClass = this.compiledRowClass\n\t\t\t}\n\t\t\tif (this.selfFetchActive) {\n\t\t\t\tinner.register = this.source.register\n\t\t\t\tinner.schemaId = this.source.schema\n\t\t\t\tinner.fetchParams = this.resolvedFetchParams\n\t\t\t\tif (typeof this.source.limit === 'number' && this.source.limit > 0) {\n\t\t\t\t\tinner.limit = this.source.limit\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (this.endpointActive) {\n\t\t\t\tinner.rows = this.endpointRows\n\t\t\t\tinner.loading = this.epLoading || this.loading\n\t\t\t}\n\t\t\tif (this.rowRoute) {\n\t\t\t\tinner.rowClickRoute = this.rowRouteFn\n\t\t\t}\n\t\t\treturn inner\n\t\t},\n\n\t\t/**\n\t\t * The effective `rowClass` function forwarded to CnDataTable. A\n\t\t * host-supplied FUNCTION passes straight through; a declarative\n\t\t * rules[] array is compiled into a `(row) => string` that space-joins\n\t\t * every rule whose shared `visibleWhen` predicate holds against the\n\t\t * row (`field` dot-path + `op` + `value`). Null when `rowClass` is\n\t\t * unset or an empty array — the widget then forwards nothing so\n\t\t * CnDataTable's own default applies.\n\t\t *\n\t\t * @return {?(row: object) => string} The row-class function, or null.\n\t\t */\n\t\tcompiledRowClass() {\n\t\t\tconst rc = this.rowClass\n\t\t\tif (typeof rc === 'function') {\n\t\t\t\treturn rc\n\t\t\t}\n\t\t\tif (!Array.isArray(rc) || rc.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst rules = rc\n\t\t\treturn (row) => {\n\t\t\t\tconst classes = []\n\t\t\t\tfor (const rule of rules) {\n\t\t\t\t\tif (!rule || !rule.class) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tconst when = rule.when || {}\n\t\t\t\t\tconst actual = readVisibleWhenPath(row, when.field)\n\t\t\t\t\tif (compareVisibleWhen(actual, when.op || 'eq', when.value)) {\n\t\t\t\t\t\tclasses.push(rule.class)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn classes.join(' ')\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Row-click navigation function derived from the `rowRoute` route\n\t\t * name (CnDataTable's `rowClickRoute` shape).\n\t\t *\n\t\t * @return {(row: object) => (object|null)} A vue-router location, or null for a row with no id.\n\t\t */\n\t\trowRouteFn() {\n\t\t\treturn (row) => {\n\t\t\t\tconst id = row && (row.id || (row['@self'] && row['@self'].id))\n\t\t\t\treturn id ? { name: this.rowRoute, params: { id: String(id) } } : null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Actions rendered per row: every non-`object-op` type plus\n\t\t * `object-op` `patch` / `delete` (they mutate THAT row's object).\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\trowScopedActions() {\n\t\t\treturn (this.actions || []).filter((a) => a && (a.type !== 'object-op' || a.op === 'patch' || a.op === 'delete'))\n\t\t},\n\n\t\t/**\n\t\t * `object-op` `create` actions — widget-scoped (no row to mutate), so\n\t\t * they render in the list footer, never in the row menu.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\tcreateActions() {\n\t\t\treturn (this.actions || []).filter((a) => a && a.type === 'object-op' && a.op === 'create')\n\t\t},\n\n\t\t/**\n\t\t * Row-scoped actions mapped onto the CnRowActions shape: label + icon\n\t\t * pass through, `object-op` `delete` renders destructive, and the\n\t\t * handler routes back through the widget's confirm-gated dispatcher.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\tmappedRowActions() {\n\t\t\treturn this.rowScopedActions.map((action) => ({\n\t\t\t\tlabel: action.label,\n\t\t\t\ticon: action.icon,\n\t\t\t\tdestructive: action.type === 'object-op' && action.op === 'delete',\n\t\t\t\thandler: (row) => this.onActionTriggered(action, row),\n\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * Host-supplied CnDataTable scoped slots to forward (keys only). The\n\t\t * `row-actions` slot is withheld while the widget renders its own\n\t\t * declarative actions menu.\n\t\t *\n\t\t * @return {object}\n\t\t */\n\t\tforwardedScopedSlots() {\n\t\t\tconst out = {}\n\t\t\tfor (const name of Object.keys(this.$slots || {})) {\n\t\t\t\tif (name === 'row-actions' && this.rowScopedActions.length > 0) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tout[name] = true\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\t/**\n\t\t * Confirmation question for the pending confirm-gated action —\n\t\t * destructive copy for `delete`, a generic confirm otherwise.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tconfirmMessage() {\n\t\t\tif (this.confirmAction && this.confirmAction.op === 'delete') {\n\t\t\t\treturn t('nextcloud-vue', 'Are you sure you want to delete this item? This action cannot be undone.')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Are you sure you want to continue?')\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Entry point for every declarative action trigger (row menu or\n\t\t * footer create button). `object-op` `delete` ALWAYS confirms;\n\t\t * `patch` / `create` confirm only when the action sets\n\t\t * `confirm: true`; everything else dispatches immediately.\n\t\t *\n\t\t * @param {object} action The manifest action.\n\t\t * @param {object|null} row The row for row-scoped actions (null for create).\n\t\t * @return {Promise<void>|void}\n\t\t */\n\t\tonActionTriggered(action, row) {\n\t\t\tif (action && action.type === 'object-op' && (action.op === 'delete' || action.confirm === true)) {\n\t\t\t\tthis.confirmAction = action\n\t\t\t\tthis.confirmRow = row\n\t\t\t\treturn\n\t\t\t}\n\t\t\treturn this.runAction(action, row)\n\t\t},\n\n\t\t/**\n\t\t * Confirm-dialog primary button: run the pending action and report\n\t\t * the outcome back into the dialog's result phase.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onConfirmConfirmed() {\n\t\t\tconst outcome = await this.runAction(this.confirmAction, this.confirmRow)\n\t\t\tconst dialog = this.$refs.confirmDialog\n\t\t\tif (dialog) {\n\t\t\t\tdialog.setResult(outcome.ok ? { success: true } : { error: outcome.error })\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Close/cancel the confirm dialog and clear the pending action.\n\t\t */\n\t\tcloseConfirm() {\n\t\t\tthis.confirmAction = null\n\t\t\tthis.confirmRow = null\n\t\t},\n\n\t\t/**\n\t\t * Dispatch an action. Non-mutating types go through the page's\n\t\t * pre-bound `cnDispatchAction` (falling back to a bare\n\t\t * `dispatchAction`); for `handler` actions the row is appended as the\n\t\t * final handler argument so row context reaches the registry function.\n\t\t * `object-op` actions dispatch via the shared object store — a\n\t\t * rejected (RBAC) write surfaces `opError` and mutates NO local state;\n\t\t * a success clears the error and refreshes the self-fetched rows.\n\t\t *\n\t\t * @param {object} action The manifest action.\n\t\t * @param {object|null} row The target row (null for create).\n\t\t * @return {Promise<{ok: boolean, error?: string}>}\n\t\t */\n\t\tasync runAction(action, row) {\n\t\t\tif (!action) {\n\t\t\t\treturn { ok: false, error: '' }\n\t\t\t}\n\t\t\tif (action.type !== 'object-op') {\n\t\t\t\tconst wrapped = (!action.type || action.type === 'handler')\n\t\t\t\t\t? { ...action, args: [...(action.args || []), row] }\n\t\t\t\t\t: action\n\t\t\t\tif (typeof this.cnDispatchAction === 'function') {\n\t\t\t\t\tthis.cnDispatchAction(wrapped)\n\t\t\t\t} else {\n\t\t\t\t\tdispatchAction(wrapped, { router: this.$router || null })\n\t\t\t\t}\n\t\t\t\treturn { ok: true }\n\t\t\t}\n\n\t\t\tconst store = this.getObjectStore()\n\t\t\tconst result = await dispatchAction(action, {\n\t\t\t\tobjectStore: store,\n\t\t\t\tsource: this.source,\n\t\t\t\trow,\n\t\t\t})\n\t\t\tconst ok = !(result === null || result === false || result === undefined)\n\t\t\tif (!ok) {\n\t\t\t\tconst error = this.readStoreError(store)\n\t\t\t\tthis.opError = error\n\t\t\t\treturn { ok: false, error }\n\t\t\t}\n\t\t\tthis.opError = ''\n\t\t\tthis.refresh()\n\t\t\t/**\n\t\t\t * @event object-op Emitted after a successful declarative mutation\n\t\t\t * so a host that supplies external `rows` can refetch them.\n\t\t\t * @type {{action: object, row: (object|null), result: unknown}}\n\t\t\t */\n\t\t\tthis.$emit('object-op', { action, row, result })\n\t\t\treturn { ok: true }\n\t\t},\n\n\t\t/**\n\t\t * Re-run the active fetch: the endpoint binding (force-refetch past\n\t\t * the shared cache) or the inner CnDataTable's self-fetch. No-op when\n\t\t * the widget is fed external rows.\n\t\t */\n\t\trefresh() {\n\t\t\tif (this.endpointActive) {\n\t\t\t\tthis.epRefetch()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst table = this.$refs.dataTable\n\t\t\tif (this.selfFetchActive && table && typeof table.fetchData === 'function') {\n\t\t\t\ttable.fetchData()\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The shared object store instance, or null when no Pinia is active\n\t\t * (`dispatchAction` then warns and no-ops).\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tgetObjectStore() {\n\t\t\ttry {\n\t\t\t\treturn useObjectStore()\n\t\t\t} catch {\n\t\t\t\treturn null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Human-readable message for the last store error on this widget's\n\t\t * source type (RBAC rejection, network failure, …).\n\t\t *\n\t\t * @param {object|null} store The object store used for the dispatch.\n\t\t * @return {string}\n\t\t */\n\t\treadStoreError(store) {\n\t\t\ttry {\n\t\t\t\tif (store && this.source) {\n\t\t\t\t\tconst type = resolveObjectOpType(store, this.source)\n\t\t\t\t\tconst err = store.errors && store.errors[type]\n\t\t\t\t\tif (err && err.message) {\n\t\t\t\t\t\treturn err.message\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// fall through to the generic message\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'The operation was rejected')\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-widget-object-table {\n\twidth: 100%;\n}\n\n.cn-widget-object-table__footer {\n\tdisplay: flex;\n\tgap: 4px;\n\tpadding: 4px 8px;\n\tborder-top: 1px solid var(--color-border);\n}\n\n.cn-widget-object-table__error {\n\tcolor: var(--color-text-error, var(--color-error-text));\n\tfont-size: 0.85em;\n\tmargin: 4px 8px 0;\n}\n</style>\n"],"names":["_normalizeProps","_guardReactiveProps","_createElementVNode","_createSlots","_withCtx","_renderSlot","_createVNode","_openBlock","_createElementBlock","_Fragment","_renderList","_toDisplayString"],"mappings":";;AA6CO,MAAA,UAAA,GAAA,EAAA,KAAK,EAAA,wBAAA,GAAA;;;AAkBN,EAAA,KAAA,EAAA,gCAAA;;;;;;;;;;;;;;;iEApBY,CAAA,WAAA,GAAA,mBAAA,GAAA,iBAAA,CAAA,EAAAA,cAAA,CAAAC,kBAAA,CACR,MAAW,CAAA,WAAA,GAAA,EAAA,GAAA,EAAA,KAAA,EAAU,MAAM,CAAA,KAAA,EAAA,QAAA,EAAC,MAAQ,CAAA,QAAA,EAAA,gBAAA,EAAE,MAAgB,CAAA,gBAAA,EAAA,KAAA,EAAA,IAAA,EAAA,UAAA,EAAc,MAAA,CAAA,SAAA,EAAA,CAAA,CAAA,EAAA;qBAC1E;AAAA,MAAAC,kBAAA,QAAA;mBAaa,CAAA,sBAAA,aAAA,EAZD,GAAG,EAAC,WAAW,EAAA,EAAA,EAAA,GAAI,QAAA,CAAA,UAAA,EAAA,GAAA,IAAA,CAAA,MAAA,EAAA,CAAA,EAAAC,WAAA,CAAA,EAAA,CAAA,EAAA,CAAA,gBAAA,EAAA;qBAKD,QAAK,CAAA,oBAAA,EAAA,CAAjB,CAAC,EAAE,IAAI,KAAA;;oBAAU,IAAA;AAElC,cAAA,EAAA,EAAAC,OAAA,CAAA,CAFkC,QAAA,KAAA;gBAElCC,UAAU,CAAA,IAAA,CAAA,MAAA,EAAA,IAAA,EAAAL,cAAA,CAAAC,kBAAA,CAAA,QAAA,CAAA,CAAA,CAAA;;;;AAED,UAAA,CAAA,QAAA,CAAA,gBAAA,CAAA,MAAA,GAAA,CAAA;;sBAAA,aAAA;AACT,gBAAA,EAAA,EAAAG,OAAA,CAAA,CAAG,EADM,GAAA,EAAA,KAAA;kBACTE,WAAG,CAAA,uBAAA,EAAA;AAAA,oBAAA,OAAA,EAAA,QAAA,CAAA,gBAAA;AAAA,oBAAA,GAAA,EAAA,GAAA;;;;;;;AAOF,QAAA,CAAA,QAAA,CAAA,aAAA,CAAA,MAAA,GAAA,CAAA;AAAH,aAAAC,SAAA,EAAA,EAAAC,kBAAA,CAYM,OAZN,UAYM,EAAA;kDADF,CAAAC,QAAA,EAAA,IAAA,EAAAC,UAAA,CATW,QAAA,CAAA,aAAA,EAAA,CAAN,MAAM,KAAA;gDASX,CAAA,mBAAA,EAAA;AARD,kBAAA,GAAG,EAAE,MAAM,CAAC,EAAE,IAAG,MAAA,CAAA,KAAA;AAClB,kBAAA,OAAO,EAAC,UAAS;kBAChB,aAAW,EAAA,CAAA,iBAAA,EAAoB,MAAA,CAAA,EAAA,IAAA,QAAA,CAAA,CAAA;kBAC/B,OAAK,EAAA,MAAA,KAAE,QAAe,CAAA,iBAAA,CAAA,MAAA,EAAA,IAAA,CAAA,CAAA;;mCAGZ,MACX;oCADW,GACX,GAAAC,eAAA,CAAG,MAAM,CAAC,KAAK,CAAA,EAAA,CAAA,YAAA;;;;AAHC,kBAAA,CAAA,MAAM,CAAC,IAAI;;8BAAG,MAAI;oCACjC,MAAgC;0BAAhCL,WAAgC,CAAA,iBAAA,EAAA;4BAAvB,IAAI,EAAE,MAAM,CAAC,IAAI;AAAG,4BAAA,IAAG,EAAA,EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -205,6 +205,7 @@ function loadInMemory(input) {
|
|
|
205
205
|
* @param {object} options Configuration options (endpoint / fetcher /
|
|
206
206
|
* getAppConfigValue overrides).
|
|
207
207
|
* @return {{ manifest: import('vue').Ref<object>, isLoading: import('vue').Ref<boolean>, validationErrors: import('vue').Ref<string[]|null>, unresolvedSentinels: import('vue').Ref<string[]> }}
|
|
208
|
+
* @spec openspec/changes/manifest-validator-self-contained/specs/manifest-validator-self-contained/spec.md#requirement-a-backend-manifest-delta-reaches-the-navigation
|
|
208
209
|
*/
|
|
209
210
|
function loadFromBackend(appId, bundledManifest, options) {
|
|
210
211
|
// Shallow by default (manifest-shallow-reactivity-by-default) — see
|
|
@@ -284,9 +285,13 @@ function loadFromBackend(appId, bundledManifest, options) {
|
|
|
284
285
|
// The shallowRef reassignment is what re-renders consumers; the new
|
|
285
286
|
// object stays shallow (not deep-observed) unless CnAppRoot upgrades it.
|
|
286
287
|
manifest.value = resolved;
|
|
287
|
-
} catch {
|
|
288
|
-
//
|
|
289
|
-
//
|
|
288
|
+
} catch (error) {
|
|
289
|
+
// Any unexpected error leaves the bundled manifest in place, so
|
|
290
|
+
// apps keep working. Say so: a silent catch here once hid a
|
|
291
|
+
// validator that threw on every manifest, and the backend delta
|
|
292
|
+
// never reached the navigation.
|
|
293
|
+
// eslint-disable-next-line no-console
|
|
294
|
+
console.warn('[useAppManifest] Could not resolve the manifest; keeping the bundled manifest.', error);
|
|
290
295
|
} finally {
|
|
291
296
|
isLoading.value = false;
|
|
292
297
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAppManifest.js","sources":["../../../src/composables/useAppManifest.js"],"sourcesContent":["import axios from '@nextcloud/axios'\nimport { generateUrl } from '@nextcloud/router'\nimport { ref, shallowRef } from 'vue'\nimport { mergeManifestDelta } from '../utils/mergeManifestDelta.js'\nimport { resolveManifestSentinels } from '../utils/resolveManifestSentinels.js'\n\n/**\n * Lazily import the manifest validator. The validator plus its compiled Ajv\n * artifact weigh ~340KB minified and most apps never validate at runtime —\n * a static import makes every consumer bundle carry them. The dynamic import\n * splits them into an async chunk fetched on first validation only.\n *\n * @return {Promise<(manifest: object) => { valid: boolean, errors: Array<string> }>} The validateManifest function.\n */\nfunction loadValidator() {\n\treturn import(/* webpackChunkName: \"cn-manifest-validator\" */ '../utils/validateManifest.js')\n\t\t.then((mod) => mod.validateManifest)\n}\n\n/**\n * Composable that loads, resolves, and validates a Conduction app manifest.\n *\n * Two call shapes are supported:\n *\n * 1. **Legacy positional signature** —\n * `useAppManifest(appId, bundledManifest, options?)`.\n * Implements the four-phase flow specified in REQ-JMR-002 of the\n * `json-manifest-renderer` capability + the substitution step from the\n * `manifest-resolve-sentinel` capability:\n *\n * 1. Synchronous bundled load — `bundledManifest` is the immediate value.\n * 2. Async backend merge — fetches `/index.php/apps/{appId}/api/manifest`\n * and deep-merges any 200 response over the bundled manifest. 4xx /\n * 5xx / network errors are silently ignored so apps work without a\n * backend endpoint.\n * 3. Sentinel resolution — `@resolve:<key>` strings under\n * `pages[].config` are substituted with `IAppConfig` values via\n * the `resolveManifestSentinels` utility (see its module docs for\n * the resolution source chain). Unresolved keys surface on the\n * returned `unresolvedSentinels` ref.\n * 4. Validation — the resolved result is validated against\n * `app-manifest.schema.json`. On failure, the bundled manifest is\n * kept and a `console.warn` is emitted with the error list.\n *\n * 2. **In-memory signature** —\n * `useAppManifest({ manifest, validate? })`.\n * Mounts an already-constructed manifest object synchronously, with no\n * backend fetch, no deep-merge, and no sentinel resolution. Designed\n * for virtual-app hosts (e.g. the OpenBuilt app builder) that build\n * their manifests in memory from store state. When `validate: true`,\n * `validateManifest` is called synchronously before returning and any\n * failures populate `validationErrors`; validation is informational —\n * the input manifest is never replaced.\n *\n * The two shapes are discriminated by inspecting `typeof arguments[0]`:\n * - `string` → legacy fetch-and-merge branch\n * - non-null plain `object` → in-memory branch\n *\n * Both branches return the same shape:\n * `{ manifest, isLoading, validationErrors, unresolvedSentinels }`.\n *\n * The returned manifest ref is **shallow by default** (`shallowRef`) —\n * reactivity tracks `.value` reassignment (the hot-swap the future \"app\n * builder\" backend performs) but NOT deep mutation of the manifest's own\n * nested properties, so the whole manifest graph is not deep-observed on\n * boot. Every read-only consumer (`CnAppNav`, `CnPageRenderer`,\n * `CnDashboardPage`, …) only reads the tree and re-renders on the ref swap.\n * `CnAppRoot` upgrades the *same* object to deep-reactive IN PLACE (Vue 2.7\n * `reactive()`, preserving object identity) only when Buildiq in-app\n * editing is available — see `manifest-shallow-reactivity-by-default` and\n * `useManifestEditor.upgradeManifestToEditable`. The object is deliberately\n * NOT `markRaw`'d, because `markRaw` would make that in-place `reactive()`\n * upgrade a silent no-op and break ADR-041 live editing.\n *\n * @param {string | { manifest: object, validate?: boolean }} appIdOrOptions\n * Either a Nextcloud app ID (legacy positional signature) OR an options\n * object whose `manifest` field is the canonical, in-memory manifest\n * (in-memory signature).\n * @param {object} [bundledManifest] Manifest shipped with the app. Only\n * read when `appIdOrOptions` is a string (legacy signature); ignored in\n * the in-memory signature.\n * @param {object} [options] Configuration options. Only read in the\n * legacy signature; ignored in the in-memory signature.\n * @param {string} [options.endpoint] Override the backend fetch URL.\n * Useful for tests and alternative-host deployments.\n * @param {(url: string) => Promise<{ status: number, data: object }>} [options.fetcher] Override the fetch function. Must\n * return a promise resolving to `{ status: number, data: object }`.\n * Defaults to `axios.get` from `@nextcloud/axios` (which inherits the\n * Nextcloud CSRF token automatically).\n * @param {(appId: string, key: string) => Promise<string|null>} [options.getAppConfigValue] Override the\n * IAppConfig resolver consumed by `resolveManifestSentinels`. Useful\n * for tests that want to mount a fixture-driven config map.\n * @return {{ manifest: import('vue').Ref<object>, isLoading: import('vue').Ref<boolean>, validationErrors: import('vue').Ref<string[]|null>, unresolvedSentinels: import('vue').Ref<string[]> }}\n *\n * @example Basic usage (Composition API)\n * const { manifest, isLoading } = useAppManifest('decidesk', bundled)\n *\n * @example Inside an Options API component\n * export default {\n * setup() {\n * return useAppManifest('decidesk', bundled)\n * },\n * }\n *\n * @example Custom endpoint and fetcher (e.g. for tests)\n * useAppManifest('decidesk', bundled, {\n * endpoint: '/custom/manifest/url',\n * fetcher: (url) => Promise.resolve({ status: 200, data: { ... } }),\n * })\n *\n * @example With sentinel resolution + admin warning surface\n * const { manifest, unresolvedSentinels } = useAppManifest('softwarecatalog', bundled)\n * // unresolvedSentinels.value is e.g. ['voorzieningen_register']\n * // when that IAppConfig key is unset on the tenant.\n *\n * @example In-memory manifest (virtual-app host, e.g. OpenBuilt)\n * const builderManifest = buildManifestFromStore()\n * const { manifest, isLoading } = useAppManifest({ manifest: builderManifest })\n * // isLoading.value === false immediately; no HTTP fetch is issued.\n *\n * @example In-memory manifest with pre-mount validation\n * const { manifest, validationErrors } = useAppManifest({\n * manifest: builderManifest,\n * validate: true,\n * })\n * // validationErrors.value is null on success or string[] on failure.\n * // Validation is informational — the manifest is mounted either way.\n */\nexport function useAppManifest(appIdOrOptions, bundledManifest, options = {}) {\n\t// REQ-IMM-001 / REQ-IMM-004 — discriminate the call shape on the\n\t// first argument: a string enters the legacy fetch-and-merge branch;\n\t// a non-null plain object enters the new in-memory branch.\n\tif (isPlainObject(appIdOrOptions)) {\n\t\treturn loadInMemory(appIdOrOptions)\n\t}\n\n\treturn loadFromBackend(appIdOrOptions, bundledManifest, options)\n}\n\n/**\n * In-memory branch — mount a manifest object synchronously without any\n * backend fetch, deep-merge, or sentinel resolution.\n *\n * Implements REQ-IMM-001..REQ-IMM-003 of the\n * `in-memory-app-manifest-loader` capability:\n *\n * - `manifest` ref holds the input object by reference (no clone, no\n * mutation).\n * - `isLoading` is `false` from the first read because nothing is queued.\n * - `validationErrors` is `null` unless `options.validate === true` and\n * `validateManifest` returns a non-empty error list.\n * - `unresolvedSentinels` is always `[]` — sentinel resolution is a\n * backend-merge concern and does not apply to in-memory manifests.\n *\n * Validation is informational, mirroring the legacy branch's policy\n * (REQ-JMR-002 / Decision 2 in the change design): a failure emits a\n * `console.warn` and populates `validationErrors`, but the manifest is\n * never replaced.\n *\n * @param {{ manifest: object, validate?: boolean }} input The options\n * object passed to `useAppManifest`.\n * @return {{ manifest: import('vue').Ref<object>, isLoading: import('vue').Ref<boolean>, validationErrors: import('vue').Ref<string[]|null>, unresolvedSentinels: import('vue').Ref<string[]> }}\n */\nfunction loadInMemory(input) {\n\t// Shallow by default (manifest-shallow-reactivity-by-default): track the\n\t// ref reassignment, not deep mutation of the manifest tree. NOT markRaw'd —\n\t// CnAppRoot upgrades this same object in place via reactive() when Buildiq\n\t// editing is available, which markRaw would silently block.\n\tconst manifest = shallowRef(input.manifest)\n\tconst isLoading = ref(false)\n\tconst validationErrors = ref(null)\n\tconst unresolvedSentinels = ref([])\n\tconst orphanedDeltaPaths = ref([])\n\n\tif (input.validate === true) {\n\t\t// Fire-and-forget: validation here is informational (the manifest is\n\t\t// mounted unchanged either way), so the lazy validator load may\n\t\t// resolve after mount — validationErrors is a ref consumers watch.\n\t\tloadValidator().then((validateManifest) => {\n\t\t\tconst result = validateManifest(input.manifest)\n\t\t\tif (!result.valid) {\n\t\t\t\tvalidationErrors.value = result.errors\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.warn(\n\t\t\t\t\t'[useAppManifest] In-memory manifest failed schema validation; manifest is mounted unchanged (validation is informational).',\n\t\t\t\t\tresult.errors,\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n\n\treturn { manifest, isLoading, validationErrors, unresolvedSentinels, orphanedDeltaPaths }\n}\n\n/**\n * Legacy fetch-and-merge branch — synchronous bundled load, async backend\n * merge, sentinel resolution, validation. Implements REQ-JMR-002 of the\n * `json-manifest-renderer` capability. Preserved verbatim for backwards\n * compatibility with all current consumers (OpenRegister, OpenCatalogi,\n * Dossiq, Pipelinq, Launchpad, Decidiq, Filinq, Larpinq,\n * Stackiq).\n *\n * @param {string} appId Nextcloud app ID.\n * @param {object} bundledManifest The manifest shipped with the app.\n * @param {object} options Configuration options (endpoint / fetcher /\n * getAppConfigValue overrides).\n * @return {{ manifest: import('vue').Ref<object>, isLoading: import('vue').Ref<boolean>, validationErrors: import('vue').Ref<string[]|null>, unresolvedSentinels: import('vue').Ref<string[]> }}\n */\nfunction loadFromBackend(appId, bundledManifest, options) {\n\t// Shallow by default (manifest-shallow-reactivity-by-default) — see\n\t// loadInMemory. The later `manifest.value = resolved` swap still triggers\n\t// reactivity because reassigning the shallowRef itself is tracked.\n\tconst manifest = shallowRef(bundledManifest)\n\tconst isLoading = ref(true)\n\tconst validationErrors = ref(null)\n\tconst unresolvedSentinels = ref([])\n\tconst orphanedDeltaPaths = ref([])\n\n\tconst endpoint = options.endpoint ?? generateUrl(`/apps/${appId}/api/manifest`)\n\tconst fetcher = options.fetcher ?? ((url) => axios.get(url))\n\n\t;(async () => {\n\t\ttry {\n\t\t\t// Phase 1: optional backend merge. The fetch is best-effort —\n\t\t\t// most apps DON'T serve `/api/manifest` (Nextcloud returns the\n\t\t\t// SPA index HTML with a 200, which is not a plain object), so\n\t\t\t// `base` stays the bundled manifest in the common case. Any\n\t\t\t// failure here is swallowed and we proceed with `bundledManifest`.\n\t\t\tlet base = bundledManifest\n\t\t\ttry {\n\t\t\t\tconst response = await fetcher(endpoint)\n\t\t\t\t// Only a 200 with a plain-object body is a real manifest\n\t\t\t\t// response. An HTML string (SPA fallback), a non-200, or a\n\t\t\t\t// missing body all mean \"no backend manifest\" → keep bundled.\n\t\t\t\tif (response && response.status === 200 && isPlainObject(response.data)) {\n\t\t\t\t\t// `delta` mode applies a keyed structural delta to the\n\t\t\t\t\t// bundled manifest (ADR-036 Amendment); default mode\n\t\t\t\t\t// deep-merges as before.\n\t\t\t\t\tif (options.mergeStrategy === 'delta') {\n\t\t\t\t\t\tconst deltaResult = mergeManifestDelta(bundledManifest, response.data)\n\t\t\t\t\t\tbase = deltaResult.manifest\n\t\t\t\t\t\torphanedDeltaPaths.value = deltaResult.orphanedDeltaPaths\n\t\t\t\t\t} else {\n\t\t\t\t\t\tbase = deepMerge(bundledManifest, response.data)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Network / 404 / unauthenticated — keep the bundled manifest.\n\t\t\t}\n\n\t\t\t// Phase 2: sentinel resolution ALWAYS runs, on the bundled (or\n\t\t\t// merged) manifest, INDEPENDENT of whether the backend fetch\n\t\t\t// succeeded. `@resolve:<key>` sentinels resolve from\n\t\t\t// `@nextcloud/initial-state` (zero-network) then a per-key\n\t\t\t// `/api/configs/{key}` fetch — neither depends on `/api/manifest`\n\t\t\t// serving a valid body. Previously this only ran inside the\n\t\t\t// successful-fetch branch, so the >90% of apps that don't serve\n\t\t\t// `/api/manifest` never had their sentinels substituted.\n\t\t\t//\n\t\t\t// Runs BEFORE validation per REQ-MRS-002: the validator MUST\n\t\t\t// NEVER observe an unresolved sentinel. Resolution failures\n\t\t\t// (unset IAppConfig keys) substitute null and accumulate on\n\t\t\t// `unresolvedSentinels`; they do NOT block validation.\n\t\t\tconst { manifest: resolved, unresolved } = await resolveManifestSentinels(base, appId, {\n\t\t\t\tgetAppConfigValue: options.getAppConfigValue,\n\t\t\t})\n\t\t\tunresolvedSentinels.value = unresolved\n\n\t\t\t// Phase 3: validation. On failure, keep the bundled manifest\n\t\t\t// (informational policy — never replace with an invalid doc).\n\t\t\tconst validateManifest = await loadValidator()\n\t\t\tconst result = validateManifest(resolved)\n\t\t\tif (!result.valid) {\n\t\t\t\tvalidationErrors.value = result.errors\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.warn(\n\t\t\t\t\t'[useAppManifest] Resolved manifest failed schema validation; keeping the unresolved bundled manifest.',\n\t\t\t\t\tresult.errors,\n\t\t\t\t)\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// Publish the resolved manifest whenever it differs from the\n\t\t\t// bundled input (sentinels substituted and/or backend merged).\n\t\t\t// The shallowRef reassignment is what re-renders consumers; the new\n\t\t\t// object stays shallow (not deep-observed) unless CnAppRoot upgrades it.\n\t\t\tmanifest.value = resolved\n\t\t} catch {\n\t\t\t// Defensive: any unexpected error leaves the bundled manifest in\n\t\t\t// place. Apps without a backend endpoint keep working.\n\t\t} finally {\n\t\t\tisLoading.value = false\n\t\t}\n\t})()\n\n\treturn { manifest, isLoading, validationErrors, unresolvedSentinels, orphanedDeltaPaths }\n}\n\n/**\n * Deep-merge `source` into `target`, returning a new object. Plain\n * objects are merged recursively; arrays are replaced (not concatenated)\n * to match typical deep-merge semantics expected by manifest overrides.\n *\n * @param {object} target Base object.\n * @param {object} source Object whose values take precedence.\n * @return {object} New merged object.\n */\nfunction deepMerge(target, source) {\n\tif (!isPlainObject(target)) {\n\t\treturn source\n\t}\n\tif (!isPlainObject(source)) {\n\t\treturn source\n\t}\n\tconst out = { ...target }\n\tfor (const key of Object.keys(source)) {\n\t\tif (isPlainObject(source[key]) && isPlainObject(target[key])) {\n\t\t\tout[key] = deepMerge(target[key], source[key])\n\t\t} else {\n\t\t\tout[key] = source[key]\n\t\t}\n\t}\n\treturn out\n}\n\nfunction isPlainObject(value) {\n\treturn value !== null && typeof value === 'object' && !Array.isArray(value)\n}\n"],"names":["axios"],"mappings":";;;;;;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,aAAa,GAAG;AACzB,CAAC,OAAO,uDAAuD,8BAA8B,CAAC;AAC9F,GAAG,IAAI,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,gBAAgB,CAAC;AACtC,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,cAAc,CAAC,cAAc,EAAE,eAAe,EAAE,OAAO,GAAG,EAAE,EAAE;AAC9E;AACA;AACA;AACA,CAAC,IAAI,aAAa,CAAC,cAAc,CAAC,EAAE;AACpC,EAAE,OAAO,YAAY,CAAC,cAAc,CAAC;AACrC,EAAE;AACF;AACA,CAAC,OAAO,eAAe,CAAC,cAAc,EAAE,eAAe,EAAE,OAAO,CAAC;AACjE,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,YAAY,CAAC,KAAK,EAAE;AAC7B;AACA;AACA;AACA;AACA,CAAC,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,QAAQ,EAAC;AAC5C,CAAC,MAAM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"}
|
|
1
|
+
{"version":3,"file":"useAppManifest.js","sources":["../../../src/composables/useAppManifest.js"],"sourcesContent":["import axios from '@nextcloud/axios'\nimport { generateUrl } from '@nextcloud/router'\nimport { ref, shallowRef } from 'vue'\nimport { mergeManifestDelta } from '../utils/mergeManifestDelta.js'\nimport { resolveManifestSentinels } from '../utils/resolveManifestSentinels.js'\n\n/**\n * Lazily import the manifest validator. The validator plus its compiled Ajv\n * artifact weigh ~340KB minified and most apps never validate at runtime —\n * a static import makes every consumer bundle carry them. The dynamic import\n * splits them into an async chunk fetched on first validation only.\n *\n * @return {Promise<(manifest: object) => { valid: boolean, errors: Array<string> }>} The validateManifest function.\n */\nfunction loadValidator() {\n\treturn import(/* webpackChunkName: \"cn-manifest-validator\" */ '../utils/validateManifest.js')\n\t\t.then((mod) => mod.validateManifest)\n}\n\n/**\n * Composable that loads, resolves, and validates a Conduction app manifest.\n *\n * Two call shapes are supported:\n *\n * 1. **Legacy positional signature** —\n * `useAppManifest(appId, bundledManifest, options?)`.\n * Implements the four-phase flow specified in REQ-JMR-002 of the\n * `json-manifest-renderer` capability + the substitution step from the\n * `manifest-resolve-sentinel` capability:\n *\n * 1. Synchronous bundled load — `bundledManifest` is the immediate value.\n * 2. Async backend merge — fetches `/index.php/apps/{appId}/api/manifest`\n * and deep-merges any 200 response over the bundled manifest. 4xx /\n * 5xx / network errors are silently ignored so apps work without a\n * backend endpoint.\n * 3. Sentinel resolution — `@resolve:<key>` strings under\n * `pages[].config` are substituted with `IAppConfig` values via\n * the `resolveManifestSentinels` utility (see its module docs for\n * the resolution source chain). Unresolved keys surface on the\n * returned `unresolvedSentinels` ref.\n * 4. Validation — the resolved result is validated against\n * `app-manifest.schema.json`. On failure, the bundled manifest is\n * kept and a `console.warn` is emitted with the error list.\n *\n * 2. **In-memory signature** —\n * `useAppManifest({ manifest, validate? })`.\n * Mounts an already-constructed manifest object synchronously, with no\n * backend fetch, no deep-merge, and no sentinel resolution. Designed\n * for virtual-app hosts (e.g. the OpenBuilt app builder) that build\n * their manifests in memory from store state. When `validate: true`,\n * `validateManifest` is called synchronously before returning and any\n * failures populate `validationErrors`; validation is informational —\n * the input manifest is never replaced.\n *\n * The two shapes are discriminated by inspecting `typeof arguments[0]`:\n * - `string` → legacy fetch-and-merge branch\n * - non-null plain `object` → in-memory branch\n *\n * Both branches return the same shape:\n * `{ manifest, isLoading, validationErrors, unresolvedSentinels }`.\n *\n * The returned manifest ref is **shallow by default** (`shallowRef`) —\n * reactivity tracks `.value` reassignment (the hot-swap the future \"app\n * builder\" backend performs) but NOT deep mutation of the manifest's own\n * nested properties, so the whole manifest graph is not deep-observed on\n * boot. Every read-only consumer (`CnAppNav`, `CnPageRenderer`,\n * `CnDashboardPage`, …) only reads the tree and re-renders on the ref swap.\n * `CnAppRoot` upgrades the *same* object to deep-reactive IN PLACE (Vue 2.7\n * `reactive()`, preserving object identity) only when Buildiq in-app\n * editing is available — see `manifest-shallow-reactivity-by-default` and\n * `useManifestEditor.upgradeManifestToEditable`. The object is deliberately\n * NOT `markRaw`'d, because `markRaw` would make that in-place `reactive()`\n * upgrade a silent no-op and break ADR-041 live editing.\n *\n * @param {string | { manifest: object, validate?: boolean }} appIdOrOptions\n * Either a Nextcloud app ID (legacy positional signature) OR an options\n * object whose `manifest` field is the canonical, in-memory manifest\n * (in-memory signature).\n * @param {object} [bundledManifest] Manifest shipped with the app. Only\n * read when `appIdOrOptions` is a string (legacy signature); ignored in\n * the in-memory signature.\n * @param {object} [options] Configuration options. Only read in the\n * legacy signature; ignored in the in-memory signature.\n * @param {string} [options.endpoint] Override the backend fetch URL.\n * Useful for tests and alternative-host deployments.\n * @param {(url: string) => Promise<{ status: number, data: object }>} [options.fetcher] Override the fetch function. Must\n * return a promise resolving to `{ status: number, data: object }`.\n * Defaults to `axios.get` from `@nextcloud/axios` (which inherits the\n * Nextcloud CSRF token automatically).\n * @param {(appId: string, key: string) => Promise<string|null>} [options.getAppConfigValue] Override the\n * IAppConfig resolver consumed by `resolveManifestSentinels`. Useful\n * for tests that want to mount a fixture-driven config map.\n * @return {{ manifest: import('vue').Ref<object>, isLoading: import('vue').Ref<boolean>, validationErrors: import('vue').Ref<string[]|null>, unresolvedSentinels: import('vue').Ref<string[]> }}\n *\n * @example Basic usage (Composition API)\n * const { manifest, isLoading } = useAppManifest('decidesk', bundled)\n *\n * @example Inside an Options API component\n * export default {\n * setup() {\n * return useAppManifest('decidesk', bundled)\n * },\n * }\n *\n * @example Custom endpoint and fetcher (e.g. for tests)\n * useAppManifest('decidesk', bundled, {\n * endpoint: '/custom/manifest/url',\n * fetcher: (url) => Promise.resolve({ status: 200, data: { ... } }),\n * })\n *\n * @example With sentinel resolution + admin warning surface\n * const { manifest, unresolvedSentinels } = useAppManifest('softwarecatalog', bundled)\n * // unresolvedSentinels.value is e.g. ['voorzieningen_register']\n * // when that IAppConfig key is unset on the tenant.\n *\n * @example In-memory manifest (virtual-app host, e.g. OpenBuilt)\n * const builderManifest = buildManifestFromStore()\n * const { manifest, isLoading } = useAppManifest({ manifest: builderManifest })\n * // isLoading.value === false immediately; no HTTP fetch is issued.\n *\n * @example In-memory manifest with pre-mount validation\n * const { manifest, validationErrors } = useAppManifest({\n * manifest: builderManifest,\n * validate: true,\n * })\n * // validationErrors.value is null on success or string[] on failure.\n * // Validation is informational — the manifest is mounted either way.\n */\nexport function useAppManifest(appIdOrOptions, bundledManifest, options = {}) {\n\t// REQ-IMM-001 / REQ-IMM-004 — discriminate the call shape on the\n\t// first argument: a string enters the legacy fetch-and-merge branch;\n\t// a non-null plain object enters the new in-memory branch.\n\tif (isPlainObject(appIdOrOptions)) {\n\t\treturn loadInMemory(appIdOrOptions)\n\t}\n\n\treturn loadFromBackend(appIdOrOptions, bundledManifest, options)\n}\n\n/**\n * In-memory branch — mount a manifest object synchronously without any\n * backend fetch, deep-merge, or sentinel resolution.\n *\n * Implements REQ-IMM-001..REQ-IMM-003 of the\n * `in-memory-app-manifest-loader` capability:\n *\n * - `manifest` ref holds the input object by reference (no clone, no\n * mutation).\n * - `isLoading` is `false` from the first read because nothing is queued.\n * - `validationErrors` is `null` unless `options.validate === true` and\n * `validateManifest` returns a non-empty error list.\n * - `unresolvedSentinels` is always `[]` — sentinel resolution is a\n * backend-merge concern and does not apply to in-memory manifests.\n *\n * Validation is informational, mirroring the legacy branch's policy\n * (REQ-JMR-002 / Decision 2 in the change design): a failure emits a\n * `console.warn` and populates `validationErrors`, but the manifest is\n * never replaced.\n *\n * @param {{ manifest: object, validate?: boolean }} input The options\n * object passed to `useAppManifest`.\n * @return {{ manifest: import('vue').Ref<object>, isLoading: import('vue').Ref<boolean>, validationErrors: import('vue').Ref<string[]|null>, unresolvedSentinels: import('vue').Ref<string[]> }}\n */\nfunction loadInMemory(input) {\n\t// Shallow by default (manifest-shallow-reactivity-by-default): track the\n\t// ref reassignment, not deep mutation of the manifest tree. NOT markRaw'd —\n\t// CnAppRoot upgrades this same object in place via reactive() when Buildiq\n\t// editing is available, which markRaw would silently block.\n\tconst manifest = shallowRef(input.manifest)\n\tconst isLoading = ref(false)\n\tconst validationErrors = ref(null)\n\tconst unresolvedSentinels = ref([])\n\tconst orphanedDeltaPaths = ref([])\n\n\tif (input.validate === true) {\n\t\t// Fire-and-forget: validation here is informational (the manifest is\n\t\t// mounted unchanged either way), so the lazy validator load may\n\t\t// resolve after mount — validationErrors is a ref consumers watch.\n\t\tloadValidator().then((validateManifest) => {\n\t\t\tconst result = validateManifest(input.manifest)\n\t\t\tif (!result.valid) {\n\t\t\t\tvalidationErrors.value = result.errors\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.warn(\n\t\t\t\t\t'[useAppManifest] In-memory manifest failed schema validation; manifest is mounted unchanged (validation is informational).',\n\t\t\t\t\tresult.errors,\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n\n\treturn { manifest, isLoading, validationErrors, unresolvedSentinels, orphanedDeltaPaths }\n}\n\n/**\n * Legacy fetch-and-merge branch — synchronous bundled load, async backend\n * merge, sentinel resolution, validation. Implements REQ-JMR-002 of the\n * `json-manifest-renderer` capability. Preserved verbatim for backwards\n * compatibility with all current consumers (OpenRegister, OpenCatalogi,\n * Dossiq, Pipelinq, Launchpad, Decidiq, Filinq, Larpinq,\n * Stackiq).\n *\n * @param {string} appId Nextcloud app ID.\n * @param {object} bundledManifest The manifest shipped with the app.\n * @param {object} options Configuration options (endpoint / fetcher /\n * getAppConfigValue overrides).\n * @return {{ manifest: import('vue').Ref<object>, isLoading: import('vue').Ref<boolean>, validationErrors: import('vue').Ref<string[]|null>, unresolvedSentinels: import('vue').Ref<string[]> }}\n * @spec openspec/changes/manifest-validator-self-contained/specs/manifest-validator-self-contained/spec.md#requirement-a-backend-manifest-delta-reaches-the-navigation\n */\nfunction loadFromBackend(appId, bundledManifest, options) {\n\t// Shallow by default (manifest-shallow-reactivity-by-default) — see\n\t// loadInMemory. The later `manifest.value = resolved` swap still triggers\n\t// reactivity because reassigning the shallowRef itself is tracked.\n\tconst manifest = shallowRef(bundledManifest)\n\tconst isLoading = ref(true)\n\tconst validationErrors = ref(null)\n\tconst unresolvedSentinels = ref([])\n\tconst orphanedDeltaPaths = ref([])\n\n\tconst endpoint = options.endpoint ?? generateUrl(`/apps/${appId}/api/manifest`)\n\tconst fetcher = options.fetcher ?? ((url) => axios.get(url))\n\n\t;(async () => {\n\t\ttry {\n\t\t\t// Phase 1: optional backend merge. The fetch is best-effort —\n\t\t\t// most apps DON'T serve `/api/manifest` (Nextcloud returns the\n\t\t\t// SPA index HTML with a 200, which is not a plain object), so\n\t\t\t// `base` stays the bundled manifest in the common case. Any\n\t\t\t// failure here is swallowed and we proceed with `bundledManifest`.\n\t\t\tlet base = bundledManifest\n\t\t\ttry {\n\t\t\t\tconst response = await fetcher(endpoint)\n\t\t\t\t// Only a 200 with a plain-object body is a real manifest\n\t\t\t\t// response. An HTML string (SPA fallback), a non-200, or a\n\t\t\t\t// missing body all mean \"no backend manifest\" → keep bundled.\n\t\t\t\tif (response && response.status === 200 && isPlainObject(response.data)) {\n\t\t\t\t\t// `delta` mode applies a keyed structural delta to the\n\t\t\t\t\t// bundled manifest (ADR-036 Amendment); default mode\n\t\t\t\t\t// deep-merges as before.\n\t\t\t\t\tif (options.mergeStrategy === 'delta') {\n\t\t\t\t\t\tconst deltaResult = mergeManifestDelta(bundledManifest, response.data)\n\t\t\t\t\t\tbase = deltaResult.manifest\n\t\t\t\t\t\torphanedDeltaPaths.value = deltaResult.orphanedDeltaPaths\n\t\t\t\t\t} else {\n\t\t\t\t\t\tbase = deepMerge(bundledManifest, response.data)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Network / 404 / unauthenticated — keep the bundled manifest.\n\t\t\t}\n\n\t\t\t// Phase 2: sentinel resolution ALWAYS runs, on the bundled (or\n\t\t\t// merged) manifest, INDEPENDENT of whether the backend fetch\n\t\t\t// succeeded. `@resolve:<key>` sentinels resolve from\n\t\t\t// `@nextcloud/initial-state` (zero-network) then a per-key\n\t\t\t// `/api/configs/{key}` fetch — neither depends on `/api/manifest`\n\t\t\t// serving a valid body. Previously this only ran inside the\n\t\t\t// successful-fetch branch, so the >90% of apps that don't serve\n\t\t\t// `/api/manifest` never had their sentinels substituted.\n\t\t\t//\n\t\t\t// Runs BEFORE validation per REQ-MRS-002: the validator MUST\n\t\t\t// NEVER observe an unresolved sentinel. Resolution failures\n\t\t\t// (unset IAppConfig keys) substitute null and accumulate on\n\t\t\t// `unresolvedSentinels`; they do NOT block validation.\n\t\t\tconst { manifest: resolved, unresolved } = await resolveManifestSentinels(base, appId, {\n\t\t\t\tgetAppConfigValue: options.getAppConfigValue,\n\t\t\t})\n\t\t\tunresolvedSentinels.value = unresolved\n\n\t\t\t// Phase 3: validation. On failure, keep the bundled manifest\n\t\t\t// (informational policy — never replace with an invalid doc).\n\t\t\tconst validateManifest = await loadValidator()\n\t\t\tconst result = validateManifest(resolved)\n\t\t\tif (!result.valid) {\n\t\t\t\tvalidationErrors.value = result.errors\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.warn(\n\t\t\t\t\t'[useAppManifest] Resolved manifest failed schema validation; keeping the unresolved bundled manifest.',\n\t\t\t\t\tresult.errors,\n\t\t\t\t)\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// Publish the resolved manifest whenever it differs from the\n\t\t\t// bundled input (sentinels substituted and/or backend merged).\n\t\t\t// The shallowRef reassignment is what re-renders consumers; the new\n\t\t\t// object stays shallow (not deep-observed) unless CnAppRoot upgrades it.\n\t\t\tmanifest.value = resolved\n\t\t} catch (error) {\n\t\t\t// Any unexpected error leaves the bundled manifest in place, so\n\t\t\t// apps keep working. Say so: a silent catch here once hid a\n\t\t\t// validator that threw on every manifest, and the backend delta\n\t\t\t// never reached the navigation.\n\t\t\t// eslint-disable-next-line no-console\n\t\t\tconsole.warn('[useAppManifest] Could not resolve the manifest; keeping the bundled manifest.', error)\n\t\t} finally {\n\t\t\tisLoading.value = false\n\t\t}\n\t})()\n\n\treturn { manifest, isLoading, validationErrors, unresolvedSentinels, orphanedDeltaPaths }\n}\n\n/**\n * Deep-merge `source` into `target`, returning a new object. Plain\n * objects are merged recursively; arrays are replaced (not concatenated)\n * to match typical deep-merge semantics expected by manifest overrides.\n *\n * @param {object} target Base object.\n * @param {object} source Object whose values take precedence.\n * @return {object} New merged object.\n */\nfunction deepMerge(target, source) {\n\tif (!isPlainObject(target)) {\n\t\treturn source\n\t}\n\tif (!isPlainObject(source)) {\n\t\treturn source\n\t}\n\tconst out = { ...target }\n\tfor (const key of Object.keys(source)) {\n\t\tif (isPlainObject(source[key]) && isPlainObject(target[key])) {\n\t\t\tout[key] = deepMerge(target[key], source[key])\n\t\t} else {\n\t\t\tout[key] = source[key]\n\t\t}\n\t}\n\treturn out\n}\n\nfunction isPlainObject(value) {\n\treturn value !== null && typeof value === 'object' && !Array.isArray(value)\n}\n"],"names":["axios"],"mappings":";;;;;;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,aAAa,GAAG;AACzB,CAAC,OAAO,uDAAuD,8BAA8B,CAAC;AAC9F,GAAG,IAAI,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,gBAAgB,CAAC;AACtC,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,cAAc,CAAC,cAAc,EAAE,eAAe,EAAE,OAAO,GAAG,EAAE,EAAE;AAC9E;AACA;AACA;AACA,CAAC,IAAI,aAAa,CAAC,cAAc,CAAC,EAAE;AACpC,EAAE,OAAO,YAAY,CAAC,cAAc,CAAC;AACrC,EAAE;AACF;AACA,CAAC,OAAO,eAAe,CAAC,cAAc,EAAE,eAAe,EAAE,OAAO,CAAC;AACjE,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,YAAY,CAAC,KAAK,EAAE;AAC7B;AACA;AACA;AACA;AACA,CAAC,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,QAAQ,EAAC;AAC5C,CAAC,MAAM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"}
|