Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
db9fada
feat(manifest): pilot — route Decisions through CnPageRenderer (Tier 2)
rubenvdlinde Apr 28, 2026
d49810f
fix(setup): wire register import + add components.registers block
rubenvdlinde Apr 28, 2026
6bfe0a9
refactor(stack): use canonical objectStore + main.js bootstrap fixes
rubenvdlinde Apr 28, 2026
dc16087
fix(ui): align dashboard + main menu with the canonical NC pattern
rubenvdlinde Apr 28, 2026
93feaf4
fix(dashboard): widget styling matches procest / pipelinq
rubenvdlinde Apr 28, 2026
13245d9
feat(manifest): full Tier 4 adoption — entire app shell from manifest
rubenvdlinde Apr 28, 2026
232900c
feat(manifest): add Documentation (href) and Settings menu entries
rubenvdlinde Apr 28, 2026
5d90278
fix(manifest): Documentation in main + valid Dashboard icon
rubenvdlinde Apr 28, 2026
8bd78c3
fix(manifest): pin Documentation to footer next to Settings
rubenvdlinde Apr 28, 2026
e2193b4
chore: bump version to 0.1.4 for cache busting
rubenvdlinde Apr 28, 2026
c936e51
refactor(stores): consolidate onto canonical useObjectStore (ADR-022)
rubenvdlinde Apr 28, 2026
bf594bd
refactor(controllers): drop redundant Meeting CRUD wrapper
rubenvdlinde Apr 28, 2026
905fa61
feat(meetings): adopt x-openregister-lifecycle, drop bespoke transiti…
rubenvdlinde Apr 29, 2026
acfc5ec
chore(meetings): bump schema version + use FQCN for guard `requires`
rubenvdlinde Apr 29, 2026
ec03f6c
chore(meetings): drop unused MeetingTransitionGuard registerService
rubenvdlinde Apr 29, 2026
218f72b
feat(meetings): add x-openregister-aggregations to ActionItem schema
rubenvdlinde Apr 29, 2026
0d609e4
feat(meetings): add x-openregister-calculations to ActionItem schema
rubenvdlinde Apr 29, 2026
d692c00
feat(meetings): add x-openregister-notifications to Meeting schema
rubenvdlinde Apr 29, 2026
70af1f4
feat(motion+minutes): adopt x-openregister-lifecycle, drop bespoke tr…
rubenvdlinde Apr 29, 2026
ff016cf
feat(meetings): enable calendar provider on Meeting schema
rubenvdlinde Apr 29, 2026
5496c40
feat(action-item): add daysOpen calculation using @self.created
rubenvdlinde Apr 29, 2026
a533e78
feat(action-item): add statusBadge virtual calculation
rubenvdlinde Apr 29, 2026
e8b1812
feat(analytics): migrate ActionItemAnalyticsService.getSummary to dec…
rubenvdlinde Apr 29, 2026
c49a644
feat(notifications-v2 pilot): add scheduled, threshold, and persisten…
rubenvdlinde Apr 29, 2026
e2d4aa0
feat(besluiten-management): relocate spec from openregister
rubenvdlinde Apr 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Vrij en open source onder de EUPL-1.2-licentie.

**Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl.
]]></description>
<version>0.1.0</version>
<version>0.1.8</version>
<licence>agpl</licence>
<author mail="info@conduction.nl" homepage="https://www.conduction.nl/">Conduction</author>
<namespace>Decidesk</namespace>
Expand Down
22 changes: 9 additions & 13 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
// Minutes endpoints — specific routes must precede the wildcard catch-all.
// @spec openspec/changes/p2-minutes-and-decisions/tasks.md#task-1
['name' => 'minutes#generateDraft', 'url' => '/api/minutes/{minutesId}/generate-draft', 'verb' => 'POST'],
['name' => 'minutes#transition', 'url' => '/api/minutes/{minutesId}/transition', 'verb' => 'POST'],
// Lifecycle transitions go through OpenRegister's POST /api/objects/{id}/transition
// (declared via x-openregister-lifecycle on the Minutes schema).

// ALV minutes endpoints.
// @spec openspec/changes/p2-minutes-and-decisions-core-t3/tasks.md#task-3
Expand All @@ -43,16 +44,11 @@
// @spec openspec/changes/p2-minutes-and-decisions/tasks.md#task-6.2
['name' => 'decision#publish', 'url' => '/api/decisions/{decisionId}/publish', 'verb' => 'POST'],

// Meeting CRUD operations (task 1.4).
['name' => 'meeting#index', 'url' => '/api/meetings', 'verb' => 'GET'],
['name' => 'meeting#create', 'url' => '/api/meetings', 'verb' => 'POST'],
['name' => 'meeting#show', 'url' => '/api/meetings/{id}', 'verb' => 'GET'],
['name' => 'meeting#update', 'url' => '/api/meetings/{id}', 'verb' => 'PUT'],
['name' => 'meeting#destroy', 'url' => '/api/meetings/{id}', 'verb' => 'DELETE'],

// Meeting lifecycle transitions.
['name' => 'meeting#lifecycle', 'url' => '/api/meetings/{id}/lifecycle', 'verb' => 'POST'],

// Meeting CRUD goes through the generic openregister endpoint
// (`/apps/openregister/api/objects?register=decidesk&schema=meeting`).
// Lifecycle transitions go through OpenRegister's
// `POST /api/objects/{id}/transition` (declared via the
// `x-openregister-lifecycle` annotation on the Meeting schema).

// Agenda lifecycle routes (task-1.3) — specific routes BEFORE wildcard catch-all.
['name' => 'agenda#publish', 'url' => '/api/agendas/{meetingId}/publish', 'verb' => 'POST'],
Expand All @@ -62,13 +58,13 @@
['name' => 'agenda#reorder', 'url' => '/api/agendas/{meetingId}/reorder', 'verb' => 'PUT'],

// Motion lifecycle and co-signature routes (specific before wildcard).
['name' => 'motion#transition', 'url' => '/api/motions/{id}/transition', 'verb' => 'POST'],
// Lifecycle transitions go through OpenRegister's POST /api/objects/{id}/transition
// (declared via x-openregister-lifecycle on Motion + Amendment schemas).
['name' => 'motion#coSignRequest', 'url' => '/api/motions/{id}/co-sign-request', 'verb' => 'POST'],
['name' => 'motion#coSignConfirm', 'url' => '/api/motions/{id}/co-sign-confirm', 'verb' => 'POST'],
['name' => 'motion#budgetImpact', 'url' => '/api/motions/{id}/budget-impact', 'verb' => 'POST'],

// Amendment lifecycle routes (specific before wildcard).
['name' => 'motion#amendmentTransition', 'url' => '/api/amendments/{id}/transition', 'verb' => 'POST'],

// Voting round routes (specific before wildcard).
['name' => 'voting#open', 'url' => '/api/voting-rounds', 'verb' => 'POST'],
Expand Down
48 changes: 48 additions & 0 deletions docs/manifest-pilot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Manifest renderer pilot (Tier 2)

This branch routes the **Decisions** and **DecisionDetail** routes through `@conduction/nextcloud-vue`'s manifest renderer instead of mounting the view components directly. The rest of the app (App.vue, MainMenu, OpenRegister-installed gate, every other route) is unchanged.

## What changed

| File | Change |
|---|---|
| [src/manifest.json](../src/manifest.json) | New. Declares `Decisions` and `DecisionDetail` as `type: "custom"` pages mapped to registry component names. |
| [src/customComponents.js](../src/customComponents.js) | New. Registry mapping `DecisionsView` / `DecisionDetailView` → the existing `Decisions.vue` / `DecisionDetail.vue`. |
| [src/App.vue](../src/App.vue) | `setup()` calls `useAppManifest('decidesk', bundledManifest)`; `provide()` exposes `cnManifest`, `cnCustomComponents`, `cnTranslate` to descendants. The visible shell (NcContent → MainMenu → router-view) is untouched. |
| [src/router/index.js](../src/router/index.js) | `Decisions` and `DecisionDetail` routes now mount `CnPageRenderer`. The renderer matches by `$route.name === page.id`, dispatches by `type`, and resolves `component` against the registry. All other routes still mount their view components directly. |

## Why type:"custom" for routes that look like index/detail pages

The renderer's built-in `type: "index"` / `type: "detail"` paths only forward `page.config` as props to the corresponding `CnIndexPage` / `CnDetailPage`. Decidesk's existing list and detail views (`Decisions.vue` etc.) wire up data via the `useListView` / `useDetailView` composables and include slot overrides (`CnSchemaFormDialog` inside `#create-dialog`). Wrapping them via the custom-component registry keeps full app-side control while still routing through the manifest.

A future iteration of the renderer can grow a `type: "index-with-store"` (or extend `config` with a composable factory) so manifest authors don't need a wrapper Vue file at all. Until then, the registry pattern is the right tool for views with composable-driven data loading.

## Running the pilot in dev (until `nextcloud-vue` ships PR #89)

The pilot uses components introduced in [`ConductionNL/nextcloud-vue#89`](https://github.com/ConductionNL/nextcloud-vue/pull/89) (`useAppManifest`, `CnPageRenderer`, `validateManifest`). Until that PR merges and a new `@conduction/nextcloud-vue` beta is published, dev requires the local-alias mode:

```bash
# 1. In the nextcloud-vue checkout, switch to the renderer feature branch:
cd ../nextcloud-vue
git checkout feature/json-manifest-renderer

# 2. In decidesk, build with useLocalLib enabled:
cd ../decidesk
USE_LOCAL_LIB=1 npm run dev
```

`webpack.config.js` already aliases `@conduction/nextcloud-vue` → `../nextcloud-vue/src` when `USE_LOCAL_LIB=1`. Once the PR merges and `@conduction/nextcloud-vue` publishes a beta with the new exports, bump `package.json`'s dependency version and drop the local alias.

## What this pilot proves

- `useAppManifest` loads, validates, and exposes a real-world manifest in a real app context (decidesk + OpenRegister).
- `CnPageRenderer` correctly dispatches by `$route.name === page.id` for both list and detail routes.
- `provide()` from a non-`CnAppRoot` parent (just plain `App.vue`) is a viable Tier 2 wiring — apps can adopt the renderer without taking the full shell.
- The `customComponents` registry pattern is a clean bailout for views with composable-backed data loading.

## Out of scope (deliberately)

- Any change to non-Decisions routes.
- Replacing `MainMenu` with `CnAppNav` (App.vue wiring would still need the manifest's `menu[]`; today it's empty).
- Replacing the OpenRegister-installed gate with `CnDependencyMissing`. The gate currently uses `useSettingsStore` which is more app-aware than the generic `useAppStatus(appId)` capability check; both are valid, and the generic version can land later.
- Migrating decidesk fully to Tier 4 (`CnAppRoot` shell). That's a separate change once Tier 2 has soaked.
17 changes: 17 additions & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@
use OCA\Decidesk\Controller\MinutesController;
use OCA\Decidesk\Controller\ProjectionController;
use OCA\Decidesk\Controller\VotingBehaviourController;
use OCA\Decidesk\Lifecycle\MeetingTransitionGuard;
use OCA\Decidesk\Listener\DeepLinkRegistrationListener;
use OCA\Decidesk\Listener\MinutesTransitionListener;
use OCA\OpenRegister\Event\ObjectTransitionedEvent;
use OCA\Decidesk\Repair\InitializeSettings;
use OCA\Decidesk\Service\ActionItemAnalyticsService;
use OCA\Decidesk\Service\ActionItemExtractionService;
Expand Down Expand Up @@ -90,6 +93,13 @@
listener: DeepLinkRegistrationListener::class
);

// Apply Minutes-specific bookkeeping (approvedAt, signedBy) when the
// platform fires ObjectTransitionedEvent on the minutes schema.
$context->registerEventListener(
event: ObjectTransitionedEvent::class,

Check failure on line 99 in lib/AppInfo/Application.php

View workflow job for this annotation

GitHub Actions / quality / PHP Quality (psalm)

UndefinedClass

lib/AppInfo/Application.php:99:20: UndefinedClass: Class, interface or enum named OCA\OpenRegister\Event\ObjectTransitionedEvent does not exist (see https://psalm.dev/019)

Check failure on line 99 in lib/AppInfo/Application.php

View workflow job for this annotation

GitHub Actions / quality / PHP Quality (psalm)

UndefinedClass

lib/AppInfo/Application.php:99:20: UndefinedClass: Class, interface or enum named OCA\OpenRegister\Event\ObjectTransitionedEvent does not exist (see https://psalm.dev/019)
listener: MinutesTransitionListener::class

Check failure on line 100 in lib/AppInfo/Application.php

View workflow job for this annotation

GitHub Actions / quality / PHP Quality (phpstan)

Parameter $listener of method OCP\AppFramework\Bootstrap\IRegistrationContext::registerEventListener() expects class-string<OCP\EventDispatcher\IEventListener<OCP\EventDispatcher\Event>>, 'OCA\\Decidesk\\Listener\\MinutesTransitionListener' given.

Check failure on line 100 in lib/AppInfo/Application.php

View workflow job for this annotation

GitHub Actions / quality / PHP Quality (phpstan)

Parameter $listener of method OCP\AppFramework\Bootstrap\IRegistrationContext::registerEventListener() expects class-string<OCP\EventDispatcher\IEventListener<OCP\EventDispatcher\Event>>, 'OCA\\Decidesk\\Listener\\MinutesTransitionListener' given.
);

// Initialize register and schemas on install/upgrade.
$context->registerRepairStep(InitializeSettings::class);

Expand Down Expand Up @@ -187,6 +197,8 @@
return new AnalyticsController(
request: $c->get(\OCP\IRequest::class),
analyticsService: $c->get(ActionItemAnalyticsService::class),
userSession: $c->get(\OCP\IUserSession::class),
groupManager: $c->get(\OCP\IGroupManager::class),
);
}
);
Expand Down Expand Up @@ -291,6 +303,11 @@
}
);

// MeetingTransitionGuard is autowired by Nextcloud's server
// container — its constructor takes WorkflowService + QuorumService,
// both autowireable. OpenRegister's LifecycleGuardRegistry resolves
// it by FQCN from the server container; no manual registration here.

}//end register()

/**
Expand Down
Loading
Loading