Skip to content

feat(manifest): auto-wire useListView / useDetailView for type:index/detail pages #90

@rubenvdlinde

Description

@rubenvdlinde

Context

The JSON-driven manifest renderer (PR #89) dispatches pages[].type to a Cn*Page component:

type dispatches to
"index" CnIndexPage
"detail" CnDetailPage
"dashboard" CnDashboardPage
"custom" component looked up in the registry

For index / detail / dashboard, the renderer forwards page.config as props (v-bind="config"). But CnIndexPage / CnDetailPage expect data props (objects, schema, pagination, loading, searchTerm, activeFilters, …) — not just configuration. The data has to be loaded by useListView(type, { objectStore }) somewhere. Today that "somewhere" is a wrapper Vue component the consuming app authors:

<!-- decidesk/src/views/Decisions.vue -->
<template>
  <CnIndexPage
    :title="t('decidesk', 'Besluiten')"
    :schema="schema"
    :objects="objects"
    :loading="loading"
    :pagination="pagination"
    :search-term="searchTerm"
    :active-filters="activeFilters"
    @search="onSearch" @sort="onSort" @filter-change="onFilterChange"
    @page-change="onPageChange" @row-click="onRowClick" >
    <template #create-dialog="{ close }">
      <CnSchemaFormDialog  />
    </template>
  </CnIndexPage>
</template>

<script>
import { CnIndexPage, CnSchemaFormDialog, useListView } from '@conduction/nextcloud-vue'
import { useObjectStore } from '@conduction/nextcloud-vue'

export default {
  setup() {
    const objectStore = useObjectStore()
    return { ...useListView('decision', { objectStore }), objectStore }
  },
  //
}
</script>

To route this through the manifest, the consuming app has to register the wrapper in customComponents and declare the page as type: "custom":

{ "id": "Decisions", "type": "custom", "component": "DecisionsView" }

That works (decidesk pilot — ConductionNL/decidesk#138 — uses this pattern), but it defeats the simplicity of type: "index": every standard list view still needs a hand-written wrapper.

Proposal

Have CnPageRenderer auto-wire the data composable for type: "index" / "detail" when the manifest config carries an OpenRegister object type. New manifest shape:

{
  "id": "Decisions",
  "route": "/decisions",
  "type": "index",
  "title": "decidesk.decisions.title",
  "config": {
    "objectType": "decision",
    "columns": ["title", "outcome", "decisionDate", "isPublished"]
  },
  "slots": {
    "create-dialog": "DecisionCreateDialog"
  }
}

When config.objectType is present, CnPageRenderer (for type: "index"):

  1. Calls useListView(config.objectType, { objectStore }) once on mount, using the canonical useObjectStore (or an injected/prop-supplied custom store).
  2. Spreads the composable's return values onto CnIndexPage as props/listeners.
  3. Forwards slots[] overrides as before (#create-dialog, #row-actions, etc. — already supported by PR feat: implement JSON manifest renderer #89's slot map).
  4. Optionally accepts config.title, config.columns, etc. as direct props.

Same idea for type: "detail" with useDetailView(objectType, { id: $route.params.id }).

The type: "custom" registry escape hatch stays for views that need bespoke logic (e.g. inline edit forms, multi-source data merging).

Why this matters

  • Eliminates the per-view boilerplate decidesk currently has (and procest, pipelinq, opencatalogi, mydash will all hit the same gap).
  • Makes the manifest truly declarative for standard CRUD pages — no Vue file per route.
  • Aligns with ADR-022 ("apps consume OpenRegister abstractions"): apps stop owning the index/detail wiring; the library does it once.
  • Future app builder (the dynamic backend that overrides the manifest) becomes much more useful: admins can create entire CRUD pages by editing JSON, no rebuild required.

Out of scope

  • Apps with non-canonical object stores. The decidesk pilot revealed that some apps still ship their own local objectStore (since removed in decidesk#138 per ADR-022). Auto-wire targets the canonical store; non-canonical stores would need to provide the same contract or stay on type: "custom".
  • Per-app custom mass-actions/dialogs — those continue to flow through the existing slots override map (PR feat: implement JSON manifest renderer #89).

Related

  • PR #89 — JSON manifest renderer (where this auto-wire would land).
  • decidesk#138 — pilot using type: "custom" registry as the current workaround.
  • hydra#194, hydra#195 — CI checks that benefit from richer schema validation when the renderer owns more of the dispatch.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions