From 5a69849cbe4eeea6d9fd0a4c02bd4cf51b58bb63 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Fri, 8 May 2026 13:13:41 +0200 Subject: [PATCH 01/19] =?UTF-8?q?feat:=20partner=20programme=20=E2=80=94?= =?UTF-8?q?=20Host=20/=20Service=20/=20Certified=20(#23)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rewrite the partner directory around a three-tier programme that matches how the relationship actually works, not the marketing language we inherited: Host Ships our open-source apps to their customers. Hosts and resells, may bundle Nextcloud-supported variants. No SLA with Conduction, no direct line to our support, not eligible to respond to public tenders with our products. Service Has an SLA with Conduction. Their team can call us for third-line support, with named contacts and input on bug priority and roadmap. They run rollouts; we back them up. Certified Trained, audited, on the joint roadmap with Conduction. Eligible to co-respond to public tenders alongside us. Bumps preset to ^2.0.0 (which renames the slugs preset-side and adds the combined Apps-and-solutions facet). All eleven partners re-tiered and given fresh, factual one-liners researched from each company's public site: Certified: Acato Service: Shift2 Host: Centric, Procolix, The Goodcloud, BCT, Open Gemeenten, xxllnc (formerly Exxellence), YARD, iO, Sendent Apps + solutions corrected per partner: Sendent ships only MyDash; Procolix and The Goodcloud are Nextcloud hosts; Centric pairs Nextcloud with DocuDesk; Acato, Shift2, BCT, Open Gemeenten, xxllnc, YARD and iO all ship the Conduction Woo-solution (OpenCatalogi + OpenRegister + OpenConnector). /support tier cards rewritten to match the new programme. --- package-lock.json | 8 +- package.json | 2 +- src/data/partners-catalog.js | 125 +++++++++++++++++++------------- src/pages/partners/acato.mdx | 12 +-- src/pages/partners/procolix.mdx | 16 ++-- src/pages/partners/yard.mdx | 12 +-- src/pages/support.mdx | 56 +++++++------- 7 files changed, 128 insertions(+), 103 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2a24a7857..1d530415a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "conduction-website", "version": "0.0.0", "dependencies": { - "@conduction/docusaurus-preset": "^1.7.0", + "@conduction/docusaurus-preset": "^2.0.0", "@docusaurus/core": "^3.5.0", "@docusaurus/preset-classic": "^3.5.0", "@mdx-js/react": "^3.0.0", @@ -1987,9 +1987,9 @@ } }, "node_modules/@conduction/docusaurus-preset": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-1.7.0.tgz", - "integrity": "sha512-JH7aXaRGZCvkHZTLTLRh3gDWhj/2zAc8S4eBGS67hCfDIwzP5SK+Uj5BidoqbSi0FEEnekEoya2AVVLXbNv/OQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-2.0.0.tgz", + "integrity": "sha512-MMXHRE5kcqpRmvDlAG0+bwPCgMVRK6cihcLR6dxwxN3HHqRtKh9yCc9VJ795k9Es6N5ZWtYlLnXCWzp2ioJEtQ==", "license": "EUPL-1.2", "peerDependencies": { "@docusaurus/core": "^3.0.0", diff --git a/package.json b/package.json index 8022a0e75..a66ead2ef 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "ci": "npm ci --legacy-peer-deps && npm run build" }, "dependencies": { - "@conduction/docusaurus-preset": "^1.7.0", + "@conduction/docusaurus-preset": "^2.0.0", "@docusaurus/core": "^3.5.0", "@docusaurus/preset-classic": "^3.5.0", "@mdx-js/react": "^3.0.0", diff --git a/src/data/partners-catalog.js b/src/data/partners-catalog.js index 3e6285084..a96562409 100644 --- a/src/data/partners-catalog.js +++ b/src/data/partners-catalog.js @@ -9,9 +9,23 @@ * apps. Add href when a /partners/ detail page exists. Add * logo when we have permission to display it. * - * tier values: 'partner' | 'certified' | 'strategic'. + * Tier slugs (low → high): + * - host Hosts our open-source apps for their customers. May + * resell Nextcloud-supported variants through their + * own channels. No SLA with Conduction. No direct line + * to our support, no input on the roadmap, not eligible + * to respond to public tenders with our products. + * - service Has an SLA with Conduction. Their team can call us + * directly for third-line support, named contacts, + * input on bug priority and feature requests. + * - certified Trained and certified by Conduction. Joint roadmap. + * Eligible to respond to public tenders alongside us. + * * apps: free-form list of app or service names the partner * ships, used as a facet on /partners. + * solutions: free-form list of solution slugs (matching + * /solutions/) the partner delivers, used both as + * a facet on /partners and for the reverse lookup below. * * Summaries stay in Dutch when the partner's market is Dutch — the * audience that filters by an MKB hosting partner is the same audience @@ -22,96 +36,107 @@ import React from 'react'; export const PARTNERS = [ + // -------------------------------------------------------------- + // Certified — top tier, joint roadmap, tender-eligible + // -------------------------------------------------------------- { href: '/partners/acato', - tier: 'strategic', + tier: 'certified', name: 'Acato', logo: '/img/partners/acato.png', - summary: <>Nederlandse open-source specialist uit Almere. Bouwt en host Nextcloud-omgevingen voor gemeenten en zorginstellingen, met focus op AVG-compliance en NLDS. Strategisch partner op hosting, stack-implementatie en gezamenlijke roadmap., - apps: ['OpenRegister', 'OpenCatalogi', 'DocuDesk'], - solutions: ['woo', 'archief', 'software-catalog'], + summary: <>Digital agency uit Almere. Bouwt toegankelijke websites en webapplicaties voor gemeenten en overheidsorganisaties, met focus op WCAG 2.2 AA en NLDS. Levert de Conduction Woo-solution op OpenCatalogi, OpenRegister en OpenConnector., + apps: ['OpenCatalogi', 'OpenRegister', 'OpenConnector'], + solutions: ['woo'], }, + + // -------------------------------------------------------------- + // Service — SLA with Conduction, third-line support + // -------------------------------------------------------------- { - tier: 'certified', + tier: 'service', + name: 'Shift2', + logo: '/img/partners/shift2.png', + summary: <>Nederlandse softwarebouwer voor gemeenten, waterschappen en provincies, onderdeel van de Conxillium-groep. Levert CMS, burgerzaken en formulieren, en daarnaast de Conduction Woo-solution op OpenCatalogi, OpenRegister en OpenConnector., + apps: ['OpenCatalogi', 'OpenRegister', 'OpenConnector'], + solutions: ['woo'], + }, + + // -------------------------------------------------------------- + // Host — ships our apps, no SLA with Conduction + // -------------------------------------------------------------- + { + tier: 'host', name: 'Centric', logo: '/img/partners/centric.png', - summary: <>Eén van Nederlands grootste software-leveranciers voor de publieke sector. Officieel Nextcloud-distributeur. Centric-klanten krijgen Conduction-apps geïntegreerd in hun bestaande Centric-omgeving., - apps: ['Nextcloud', 'OpenZaak', 'OpenRegister', 'DocuDesk'], - solutions: ['zaakafhandeling', 'archief'], + summary: <>Een van Nederlands grootste IT-leveranciers voor gemeenten en de publieke sector. Werkt samen met Nextcloud aan soevereine werkplekken en host DocuDesk binnen dat traject., + apps: ['Nextcloud', 'DocuDesk'], + solutions: [], }, { href: '/partners/procolix', - tier: 'certified', + tier: 'host', name: 'Procolix', logo: '/img/partners/procolix.png', - summary: <>Nederlandse hoster en leverancier van proces- en zaakmanagement-software. Officieel Nextcloud-distributeur. Certified op DocuDesk en OpenConnector voor procesintegraties bij gemeenten en uitvoeringsorganisaties., - apps: ['Nextcloud', 'DocuDesk', 'OpenConnector'], - solutions: ['zaakafhandeling', 'archief', 'legacy-erp'], + summary: <>Nederlandse managed-Nextcloud-hoster uit Dordrecht. Levert beheerde Nextcloud-omgevingen op eigen EU-infrastructuur, open-source als uitgangspunt., + apps: ['Nextcloud'], + solutions: [], }, { - tier: 'certified', + tier: 'host', name: 'The Goodcloud', logo: '/img/partners/goodcloud.png', - summary: <>Nederlandse Nextcloud-hoster voor MKB en non-profits. Officieel Nextcloud-distributeur. Levert beheerde omgevingen met Conduction-apps pre-installed, privacy-eerst hosting in NL., - apps: ['Nextcloud', 'OpenRegister', 'MyDash'], - solutions: ['mkb-workspace'], + summary: <>Nederlandse managed-Nextcloud-hoster. Privacy-eerst, geen tracking, data in Nederland. Voor MKB, non-profits en privacybewuste organisaties., + apps: ['Nextcloud'], + solutions: [], }, { - tier: 'partner', + tier: 'host', name: 'BCT', logo: '/img/partners/bct.png', - summary: <>Nederlandse leverancier van document- en zaaksysteem-software voor de overheid. BCT-implementaties praten via OpenConnector met de Conduction-registers., - apps: ['OpenConnector', 'DocuDesk', 'OpenZaak'], - solutions: ['zaakafhandeling', 'archief', 'legacy-erp'], + summary: <>Nederlandse leverancier van informatie- en documentmanagement-software (Corsa, Verix). Levert de Conduction Woo-solution aan organisaties die hun informatie-governance op orde willen krijgen., + apps: ['OpenCatalogi', 'OpenRegister', 'OpenConnector'], + solutions: ['woo'], }, { - tier: 'partner', + tier: 'host', name: 'Open Gemeenten', logo: '/img/partners/open-gemeenten.png', - summary: <>Coöperatieve community van gemeenten die samen open-source software ontwikkelen en delen. Levert OpenZaak-implementaties bij aangesloten gemeenten en draagt patches terug naar upstream., - apps: ['OpenZaak', 'OpenRegister', 'OpenCatalogi'], - solutions: ['zaakafhandeling', 'software-catalog'], + summary: <>Open-source platform voor gemeentelijke websites. Bedient ruim 30 gemeenten met toegankelijke sites (WCAG, internet.nl). Levert daarnaast de Conduction Woo-solution., + apps: ['OpenCatalogi', 'OpenRegister', 'OpenConnector'], + solutions: ['woo'], }, { - tier: 'partner', - name: 'Exxellence', + tier: 'host', + name: 'xxllnc', logo: '/img/partners/exxellence.png', - summary: <>Nederlandse softwarebouwer gespecialiseerd in zaakafhandeling en burger-portaal-toepassingen. Reference-deploys op OpenZaak v4.x bij meerdere middelgrote gemeenten., - apps: ['OpenZaak', 'DocuDesk'], - solutions: ['zaakafhandeling'], + summary: <>Nederlandse leverancier van zaakgericht-werken-software voor gemeenten (xxllnc Zaken, plus apps voor belastingen en het sociaal domein). Levert daarnaast de Conduction Woo-solution., + apps: ['OpenCatalogi', 'OpenRegister', 'OpenConnector'], + solutions: ['woo'], }, { href: '/partners/yard', - tier: 'partner', + tier: 'host', name: 'YARD', logo: '/img/partners/yard.png', - summary: <>Digital design- en development-bureau uit Utrecht. Ontwerpt en bouwt klantgerichte digitale producten op Nextcloud voor gemeenten en publieke organisaties., - apps: ['MyDash', 'OpenCatalogi'], - solutions: ['software-catalog', 'mkb-workspace'], + summary: <>Digital agency uit Utrecht voor gemeenten, zorg en kennisorganisaties. Open-source-georiënteerd, ruim 80 projecten. Levert de Conduction Woo-solution., + apps: ['OpenCatalogi', 'OpenRegister', 'OpenConnector'], + solutions: ['woo'], }, { - tier: 'partner', + tier: 'host', name: 'iO', logo: '/img/partners/io.webp', - summary: <>Internationaal digital agency met Nederlandse vestiging in Amsterdam. Integreert Conduction-apps in bredere klantreis-trajecten voor publieke en semi-publieke organisaties., - apps: ['OpenConnector', 'MyDash'], - solutions: ['legacy-erp', 'mkb-workspace'], - }, - { - tier: 'partner', - name: 'Shift2', - logo: '/img/partners/shift2.png', - summary: <>Nederlands development-bureau gespecialiseerd in Nextcloud en open source. Implementeert Conduction-stacks bij MKB-klanten en gemeenten in midden-Nederland., - apps: ['OpenRegister', 'MyDash'], - solutions: ['mkb-workspace'], + summary: <>Belgisch-Nederlands digital agency met kantoren in Amsterdam en Rotterdam. Bouwt klantgerichte digitale platforms en levert de Conduction Woo-solution aan publieke en semi-publieke organisaties., + apps: ['OpenCatalogi', 'OpenRegister', 'OpenConnector'], + solutions: ['woo'], }, { - tier: 'partner', + tier: 'host', name: 'Sendent', logo: '/img/partners/sendent.png', - summary: <>Nederlandse leverancier die Microsoft Outlook en Teams koppelt aan Nextcloud. Levert support op MyDash voor klanten die hun e-mail- en bestandsstroom binnen de eigen Nextcloud-omgeving willen houden., + summary: <>Nederlandse leverancier die Microsoft Outlook en Teams koppelt aan Nextcloud. Levert support op MyDash voor klanten die hun mail- en bestandsstroom binnen de eigen Nextcloud-omgeving willen houden., apps: ['MyDash'], - solutions: ['mkb-workspace'], + solutions: [], }, ]; @@ -131,6 +156,6 @@ export const BECOME_PARTNER = { href: '/support#become-a-partner', eyebrow: 'Become a partner', title: 'Ship Conduction to your customers.', - body: 'Join as Partner, Certified, or Strategic. Conduction trains your team, you handle the rollouts. The apps stay open source, the relationship stays direct.', + body: 'Three tiers: Host (ship our apps), Service (SLA + third-line support), Certified (trained, joint roadmap, tender-eligible). The apps stay open source, the relationship stays direct.', ctaLabel: 'Apply through Support', }; diff --git a/src/pages/partners/acato.mdx b/src/pages/partners/acato.mdx index 8ac7898fa..ea8fa9a19 100644 --- a/src/pages/partners/acato.mdx +++ b/src/pages/partners/acato.mdx @@ -1,5 +1,5 @@ --- -title: Acato, Strategic partner, Conduction +title: Acato, Certified partner, Conduction description: Nederlandse open-source specialist uit Almere. Bouwt en host Nextcloud-omgevingen voor gemeenten en zorginstellingen. hide_table_of_contents: true --- @@ -13,7 +13,7 @@ export const ACATO_SOLUTIONS = solutionsBySlugs(ACATO.solutions); diff --git a/src/pages/partners/procolix.mdx b/src/pages/partners/procolix.mdx index 16740f265..edb188477 100644 --- a/src/pages/partners/procolix.mdx +++ b/src/pages/partners/procolix.mdx @@ -1,6 +1,6 @@ --- -title: Procolix, Certified partner, Conduction -description: Officieel Nextcloud-distributeur. Certified op DocuDesk en OpenConnector voor procesintegraties bij gemeenten en uitvoeringsorganisaties. +title: Procolix, Host partner, Conduction +description: Nederlandse managed-Nextcloud-hoster uit Dordrecht. Levert beheerde Nextcloud-omgevingen op eigen EU-infrastructuur, open source als uitgangspunt. hide_table_of_contents: true --- @@ -13,11 +13,11 @@ export const PROCOLIX_SOLUTIONS = solutionsBySlugs(PROCOLIX.solutions); Nederlandse hoster en leverancier van proces- en zaakmanagement-software. Officieel Nextcloud-distributeur. Certified op DocuDesk en OpenConnector voor procesintegraties bij gemeenten en uitvoeringsorganisaties.} + tagline={<>Nederlandse managed-Nextcloud-hoster uit Dordrecht. Levert beheerde Nextcloud-omgevingen op eigen EU-infrastructuur, open source als uitgangspunt. Conduction-apps draaien daar bovenop voor klanten die hosting + apps bij één leverancier willen.} primaryCta={{label: 'Talk to Procolix', href: 'mailto:hello@procolix.com'}} secondaryCta={{label: 'Visit procolix.com', href: 'https://procolix.com'}} tertiaryCta={{label: 'All partners', href: '/partners'}} @@ -101,18 +101,18 @@ export const PROCOLIX_SOLUTIONS = solutionsBySlugs(PROCOLIX.solutions); diff --git a/src/pages/partners/yard.mdx b/src/pages/partners/yard.mdx index 61fc9261d..5156f55a0 100644 --- a/src/pages/partners/yard.mdx +++ b/src/pages/partners/yard.mdx @@ -1,5 +1,5 @@ --- -title: YARD, Partner, Conduction +title: YARD, Host partner, Conduction description: Digital design- en development-bureau uit Utrecht. Strong on MyDash and OpenCatalogi for citizen-facing surfaces. hide_table_of_contents: true --- @@ -13,7 +13,7 @@ export const YARD_SOLUTIONS = solutionsBySlugs(YARD.solutions); diff --git a/src/pages/support.mdx b/src/pages/support.mdx index 25c3ed6fc..14756588b 100644 --- a/src/pages/support.mdx +++ b/src/pages/support.mdx @@ -8,11 +8,11 @@ import {Section, SectionHead, HowSteps, HowStep, Card, Pill, HexBullet, HexNetwo import {PARTNERS, BECOME_PARTNER} from '@site/src/data/partners-catalog'; export const TIER_PILL = { - partner: {background: 'var(--c-cobalt-50)', color: 'var(--c-cobalt-700)'}, - certified: {background: 'var(--c-gold-500)', color: 'var(--c-cobalt-900)'}, - strategic: {background: 'white', color: 'var(--c-blue-cobalt)'}, + host: {background: 'var(--c-cobalt-50)', color: 'var(--c-cobalt-700)'}, + service: {background: 'var(--c-gold-500)', color: 'var(--c-cobalt-900)'}, + certified: {background: 'white', color: 'var(--c-blue-cobalt)'}, }; -export const TIER_CARD_STRATEGIC = { +export const TIER_CARD_CERTIFIED = { background: 'var(--c-blue-cobalt)', borderColor: 'var(--c-blue-cobalt)', color: 'white', @@ -54,17 +54,17 @@ export const TIER_PILL_BASE = { eyebrow="How support works" title="Three paths, picked by what you need." align="stack" - lede="Most teams never need anything beyond the docs. When you do, find a partner. For mission-critical or regulated rollouts, ask for a strategic engagement." + lede="Most teams never need anything beyond the docs. When you do, find a partner. For tenders or mission-critical rollouts, work with a Certified partner." /> Install from the app store. Read the docs. Ask in the public GitHub Issues or community Discord. Most MKB and pilot users never need anything else, and that stays free, forever. - Running Conduction apps in production, or planning to? A partner deploys, configures, and supports the stack for you. Hourly, retainer, or project-based, whatever your contracting team prefers. + Running Conduction apps in production, or planning to? A Host or Service partner deploys, configures, and supports the stack for you. Hourly, retainer, or project-based. - - Mission-critical workloads, multi-app rollouts, or government tenders go through Strategic partners, co-developing with Conduction engineers and shaping the roadmap. + + Public tenders, multi-app rollouts, and joint roadmap work go through Certified partners. They are trained and audited by us, and may co-respond to tenders with our products. @@ -78,9 +78,9 @@ export const TIER_PILL_BASE = { />
- Partner -

Implementation, on demand.

-
Self-certified on the apps. Installs and configures for one or two clients in a region or sector.
+ Host +

Ships our apps to their customers.

+
Hosts and resells the open-source apps through their own channels. May offer Nextcloud-supported variants. No SLA with Conduction.
  • Public listing in the directory
  • Community Slack access
  • @@ -88,25 +88,25 @@ export const TIER_PILL_BASE = {
- Certified -

Certified on the apps you ship.

-
Conduction-certified per app. Joint go-to-market on tenders. Higher SLA on shared customers.
+ Service +

SLA, third-line, named contacts.

+
Hosts and ships our apps with an SLA from Conduction. Third-line support from our engineers, named contacts, input on bug priority and feature requests.
    -
  • Everything in Partner
  • -
  • Certified gold avatar
  • -
  • Co-branded case-study slot
  • -
  • Pre-release access (one minor)
  • +
  • Everything in Host
  • +
  • SLA contract with Conduction
  • +
  • Direct line to third-line support
  • +
  • Roadmap and bug-priority input
- - Strategic -

Shared roadmap, shared release.

-
Conduction's deepest partners. Joint roadmap commitments and shared engineering, by invitation only.
+ + Certified +

Trained, audited, tender-eligible.

+
Trained by Conduction, audited annually, on the joint roadmap. Eligible to co-respond to public tenders alongside us.
    -
  • Everything in Certified
  • +
  • Everything in Service
  • +
  • Trained + audited certification
  • Joint roadmap planning
  • -
  • Engineering hours from Conduction
  • -
  • Co-sale on government tenders
  • +
  • Tender-eligible with our products
@@ -251,9 +251,9 @@ export const TIER_PILL_BASE = { From 0b9bdc5ead9b9f290317cedb31d956ead76e00b2 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Fri, 8 May 2026 15:26:48 +0200 Subject: [PATCH 02/19] feat: clickable app pills, real Acato SVG, gold avatar on transparent (#24) - Bumps @conduction/docusaurus-preset to ^2.1.0, which makes app chips on the partner directory cards and the partner detail Sidecard clickable links to /apps/. Apps not in the registry (Nextcloud) stay as static spans. - Replaces the low-res Acato PNG with the official SVG from acato.nl, with white fills swapped for #0F0F12 so the wordmark reads against the white avatar plate. Catalog points at the SVG. - Ships static/img/brand/avatar-conduction-gold.svg, the gold Conduction credential mark on a transparent background, so the Sidecard's certified-tier badge sits directly on the cobalt-fill card surface instead of inside a white plate. --- package-lock.json | 8 ++++---- package.json | 2 +- src/data/partners-catalog.js | 2 +- static/img/brand/avatar-conduction-gold.svg | 15 +++++++++++++++ static/img/partners/acato.svg | 9 +++++++++ 5 files changed, 30 insertions(+), 6 deletions(-) create mode 100644 static/img/brand/avatar-conduction-gold.svg create mode 100644 static/img/partners/acato.svg diff --git a/package-lock.json b/package-lock.json index 1d530415a..8abd45190 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "conduction-website", "version": "0.0.0", "dependencies": { - "@conduction/docusaurus-preset": "^2.0.0", + "@conduction/docusaurus-preset": "^2.1.0", "@docusaurus/core": "^3.5.0", "@docusaurus/preset-classic": "^3.5.0", "@mdx-js/react": "^3.0.0", @@ -1987,9 +1987,9 @@ } }, "node_modules/@conduction/docusaurus-preset": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-2.0.0.tgz", - "integrity": "sha512-MMXHRE5kcqpRmvDlAG0+bwPCgMVRK6cihcLR6dxwxN3HHqRtKh9yCc9VJ795k9Es6N5ZWtYlLnXCWzp2ioJEtQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-2.1.0.tgz", + "integrity": "sha512-BYnT4h0nZ0a7so5vUjDNL/OfHQ18jxDnSB0g8tne9lqlf2Wfpl5Rk9QMdLRFLD+mIc8QQOMEH6U7hpLbkTMN4A==", "license": "EUPL-1.2", "peerDependencies": { "@docusaurus/core": "^3.0.0", diff --git a/package.json b/package.json index a66ead2ef..5f5fdfd76 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "ci": "npm ci --legacy-peer-deps && npm run build" }, "dependencies": { - "@conduction/docusaurus-preset": "^2.0.0", + "@conduction/docusaurus-preset": "^2.1.0", "@docusaurus/core": "^3.5.0", "@docusaurus/preset-classic": "^3.5.0", "@mdx-js/react": "^3.0.0", diff --git a/src/data/partners-catalog.js b/src/data/partners-catalog.js index a96562409..7ab3cd025 100644 --- a/src/data/partners-catalog.js +++ b/src/data/partners-catalog.js @@ -43,7 +43,7 @@ export const PARTNERS = [ href: '/partners/acato', tier: 'certified', name: 'Acato', - logo: '/img/partners/acato.png', + logo: '/img/partners/acato.svg', summary: <>Digital agency uit Almere. Bouwt toegankelijke websites en webapplicaties voor gemeenten en overheidsorganisaties, met focus op WCAG 2.2 AA en NLDS. Levert de Conduction Woo-solution op OpenCatalogi, OpenRegister en OpenConnector., apps: ['OpenCatalogi', 'OpenRegister', 'OpenConnector'], solutions: ['woo'], diff --git a/static/img/brand/avatar-conduction-gold.svg b/static/img/brand/avatar-conduction-gold.svg new file mode 100644 index 000000000..a9d53c4e2 --- /dev/null +++ b/static/img/brand/avatar-conduction-gold.svg @@ -0,0 +1,15 @@ + + + Conduction avatar — gold gradient on transparent · reserved for certified partners + + + + + + + + + + + + diff --git a/static/img/partners/acato.svg b/static/img/partners/acato.svg new file mode 100644 index 000000000..d74ee51eb --- /dev/null +++ b/static/img/partners/acato.svg @@ -0,0 +1,9 @@ + + + + + + + + + From 29b0fa73b46ae9f13a187d7560ec5c40e47d07da Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Fri, 8 May 2026 23:20:04 +0200 Subject: [PATCH 03/19] feat(build): refresh app-downloads at build time, drop bot-push workflow (#26) The old .github/workflows/app-downloads.yml ran nightly and tried to git push the regenerated data/app-downloads.json to development. The org-wide PR-required ruleset blocked the bot push, so the data went stale and the homepage showed '0 downloads'. Move the refresh to a Docusaurus prebuild step so the JSON is regenerated on every site build. No bot push, no rule fight. Add a nightly cron + workflow_dispatch trigger to documentation.yml so the site rebuilds daily even without code changes, refreshing the data each time. Centralized workflow already (PR ConductionNL/.github#46) passes GITHUB_TOKEN to the build step so the script can authenticate against the GitHub API and avoid the 60 req/hr unauth ceiling on the 331-repo enumeration. The committed JSON snapshot stays for local-dev convenience but is overwritten on every CI build. --- .github/workflows/app-downloads.yml | 40 ----------------------------- .github/workflows/documentation.yml | 9 +++++++ package.json | 1 + 3 files changed, 10 insertions(+), 40 deletions(-) delete mode 100644 .github/workflows/app-downloads.yml diff --git a/.github/workflows/app-downloads.yml b/.github/workflows/app-downloads.yml deleted file mode 100644 index 63672312e..000000000 --- a/.github/workflows/app-downloads.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: App Downloads - -# Refreshes data/app-downloads.json with GitHub release-asset download counts -# and Nextcloud app store metadata for every ConductionNL app. -# Cron is UTC; 07:00 UTC = 09:00 Amsterdam (CEST) / 08:00 (CET). - -on: - schedule: - - cron: '0 7 * * 1-5' - workflow_dispatch: - -permissions: - contents: write - -jobs: - collect: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - - name: Collect download stats - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: python .github/scripts/app_downloads.py - - - name: Commit if changed - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add data/app-downloads.json - if git diff --cached --quiet; then - echo "No changes." - else - git commit -m "chore: update app download stats" - git push - fi diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 126555915..81cf271c6 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -5,6 +5,15 @@ on: branches: [development, documentation] pull_request: branches: [development, documentation] + # Nightly rebuild so app-downloads.json (regenerated by the npm + # prebuild step) reflects fresh GitHub release-asset counts. 07:00 + # UTC = 09:00 Amsterdam (CEST) / 08:00 (CET). Replaces the old + # app-downloads.yml that committed the JSON to development; the + # PR-required ruleset blocked the bot push. Build-time fetch + # sidesteps the bot-push problem entirely. + schedule: + - cron: '0 7 * * *' + workflow_dispatch: jobs: deploy: diff --git a/package.json b/package.json index 5f5fdfd76..e2e5bfc86 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "scripts": { "docusaurus": "docusaurus", "start": "docusaurus start", + "prebuild": "python3 .github/scripts/app_downloads.py", "build": "docusaurus build", "swizzle": "docusaurus swizzle", "deploy": "docusaurus deploy", From 10be27f2066a5897f97604cdcd6bbccef5d28767 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sat, 9 May 2026 14:08:54 +0200 Subject: [PATCH 04/19] docs(academy): add notify_push to local-Nextcloud tutorial (#28) OpenRegister's add-live-updates change (PR #1453) and the matching @conduction/nextcloud-vue@1.0.0-beta.4 plugin both rely on notify_push being available. Updating the canonical "Nextcloud lokaal draaien" tutorial so anyone who follows it gets a stack ready for realtime push out of the box, not just polling. Adds: - notify_push service to docker-compose (icewind1991/notify_push:latest, port 7867, depends on db + redis + nextcloud, mounts NC volume RO, reads REDIS_URL + DATABASE_URL + NEXTCLOUD_URL) - New Stap 5 with the five occ config:system:set commands needed to wire NC's Redis cache, mark the Docker subnet as a trusted proxy, and run notify_push:setup - Self-test step with expected check-mark output - Two troubleshooting entries: trusted_proxies subnet mismatch, and the "No redis server is configured" restart loop Outcomes section now mentions notify_push as the realtime channel. --- .../index.mdx | 47 ++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/academy/2026-05-07-nextcloud-lokaal-draaien/index.mdx b/academy/2026-05-07-nextcloud-lokaal-draaien/index.mdx index a910022f4..07395d416 100644 --- a/academy/2026-05-07-nextcloud-lokaal-draaien/index.mdx +++ b/academy/2026-05-07-nextcloud-lokaal-draaien/index.mdx @@ -17,7 +17,7 @@ Voor elke andere tutorial in deze academy heb je een werkende Nextcloud nodig. D Een werkende Nextcloud op http://localhost:8080 opzetten met Docker. - Postgres voor data, Redis voor cache en Nextcloud voor het werk in één compose-bestand combineren. + Postgres voor data, Redis voor cache, Nextcloud voor het werk en notify_push voor realtime updates in één compose-bestand combineren. Een admin-account aanmaken waar je naar believen Conduction-apps op installeert. Met één commando de hele stack weer opruimen. @@ -90,12 +90,25 @@ services: REDIS_HOST: redis NEXTCLOUD_TRUSTED_DOMAINS: "localhost" + notify_push: + image: icewind1991/notify_push:latest + restart: unless-stopped + depends_on: [nextcloud, db, redis] + ports: + - "7867:7867" + volumes: + - nextcloud:/nextcloud:ro + environment: + NEXTCLOUD_URL: http://nextcloud + DATABASE_URL: postgres://nextcloud:nextcloud@db/nextcloud + REDIS_URL: redis://redis:6379 + volumes: db: nextcloud: ``` -Drie services, allemaal officiële images, geen custom build. De `./apps`-bind-mount is optioneel: handig als je een Conduction-app als zip naast de installatie wilt zetten in plaats van via de app store. +Vier services, allemaal officiële images, geen custom build. De `./apps`-bind-mount is optioneel: handig als je een Conduction-app als zip naast de installatie wilt zetten in plaats van via de app store. `notify_push` is de WebSocket push-server die Conduction-apps gebruiken voor realtime updates — meer daarover in stap 5. ## Stap 2: Start de stack @@ -133,6 +146,32 @@ Klik je avatar rechtsboven en kies **Apps**. De Conduction-apps staan onder **In Na elke install draait de schema-bootstrap automatisch. Voorbeelddata is binnen seconden zichtbaar. +Installeer ook de `notify_push`-app (onder **Integration**). Die is de Nextcloud-zijde van de WebSocket push-server die je in stap 1 al hebt gestart — je hoeft hem alleen te enabelen, geen verdere config nodig. + +## Stap 5: Koppel `notify_push` aan de push-server + +`notify_push` heeft één eenmalige setup-stap zodat de Nextcloud-app weet waar de Rust-binary draait. Voer in de werkmap uit: + +```bash +docker compose exec --user www-data nextcloud php occ config:system:set redis host --value redis +docker compose exec --user www-data nextcloud php occ config:system:set redis port --value 6379 --type integer +docker compose exec --user www-data nextcloud php occ config:system:set memcache.distributed --value '\OC\Memcache\Redis' +docker compose exec --user www-data nextcloud php occ config:system:set trusted_proxies 0 --value 172.16.0.0/12 +docker compose exec --user www-data nextcloud php occ notify_push:setup http://notify_push:7867 +``` + +De eerste drie regels vertellen Nextcloud dat Redis de gedeelde cache is — `notify_push` luistert daarop voor change-events. De vierde regel markeert het Docker-netwerk als trusted zodat de push-server bij Nextcloud terugmag bellen voor authenticatie. De laatste regel registreert de URL bij de Nextcloud-app. + +Verifieer dat alles werkt: + +```bash +docker compose exec --user www-data nextcloud php occ notify_push:self-test +``` + +Je krijgt een lijstje vinkjes — `redis is configured`, `push server is receiving redis messages`, `push server can connect to the Nextcloud server`, et cetera. De waarschuwing over `unencrypted http` is voor lokaal werk normaal; in productie zit `notify_push` achter dezelfde reverse proxy als Nextcloud, dus dan is dat geen issue. + +Vanaf nu krijgen apps die op `notify_push` luisteren — zoals OpenRegister's live-update-integratie via `@conduction/nextcloud-vue` — direct doorduwingen op object-wijzigingen, zonder dat de browser hoeft te pollen. + ## Wat je krijgt | Eigenschap | Waarde | @@ -170,6 +209,10 @@ Na `down -v` is je laptop weer schoon. Twee minuten later kun je opnieuw beginne **App-store install werkt niet:** controleer of Nextcloud naar buiten kan. In een corporate netwerk soms blokkade op `apps.nextcloud.com`. Een proxy via `HTTP_PROXY`-env-variabelen lost dit op. +**`notify_push` self-test faalt op `is not trusted as a reverse proxy`:** het Docker-netwerk-bereik in jouw setup wijkt af van `172.16.0.0/12`. Bekijk het bereik met `docker network inspect $(docker network ls --filter name=conduction-demo -q) --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}'` en pas de `trusted_proxies`-regel uit stap 5 aan met die subnet. + +**`notify_push` blijft restarten na `docker compose up -d`:** kijk in de logs (`docker compose logs notify_push`) of je `× No redis server is configured` ziet. Dat betekent dat Nextcloud's `memcache.distributed`-config nog niet op Redis staat — voer de eerste drie `occ config:system:set`-commando's uit stap 5 uit, en herstart dan met `docker compose up -d notify_push`. + ## Volgende stap Een werkende Nextcloud is de aanleiding voor de andere tutorials in deze serie: From f11bed4cb10407be07761a48f6ad2766faa70010 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sun, 10 May 2026 21:23:29 +0200 Subject: [PATCH 05/19] =?UTF-8?q?academy:=20Part=201=20of=20the=20DeskDesk?= =?UTF-8?q?=20tutorial=20=E2=80=94=20Scaffold?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First of a four-part tutorial that walks readers through building DeskDesk, a desk-booking app for open-office environments, on the Conduction Nextcloud stack. Part 1 covers: - gh repo create from the nextcloud-app-template GitHub template - The boot-critical rename ritual (id, namespace, autoload, every src/ and lib/ reference) with a clear table of which files matter - Build + enable + verify the empty shell renders - The chassis explained via the preset's visual: topbar, left navigation, main column, page header, sidebar Reference repo: https://github.com/ConductionNL/deskdesk Parts 2 (schemas + manifest), 3 (calendar provider), and 4 (xWiki + ship) follow. --- .../index.mdx | 216 ++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 academy/2026-05-10-deskdesk-tutorial-1-scaffold/index.mdx diff --git a/academy/2026-05-10-deskdesk-tutorial-1-scaffold/index.mdx b/academy/2026-05-10-deskdesk-tutorial-1-scaffold/index.mdx new file mode 100644 index 000000000..50889b464 --- /dev/null +++ b/academy/2026-05-10-deskdesk-tutorial-1-scaffold/index.mdx @@ -0,0 +1,216 @@ +--- +slug: deskdesk-tutorial-1-scaffold +title: "Build a Nextcloud app on the Conduction stack — Part 1: Scaffold" +contentType: tutorial +authors: [conduction] +date: 2026-05-10 +summary: Clone the Conduction app template, rename it to DeskDesk, build, enable, and see the canonical app chassis. The first of four parts that build a working desk-booking app on the full Conduction stack. +tags: [App development, Nextcloud, OpenRegister, nextcloud-vue, Tutorial series] +apps: [openregister] +durationMinutes: 20 +series: deskdesk-tutorial +partNumber: 1 +--- + +import { + Outcomes, + Outcome, + Prerequisites, + PrerequisiteItem, + AtomZones, + NextSteps, + NextStep, + HexCard, +} from '@conduction/docusaurus-preset/components'; + +This is **Part 1 of a four-part tutorial** that builds **DeskDesk**, a flexible desk-booking app for an open-office environment, on the full Conduction Nextcloud stack. The end product: pick a desk on a floor, book a slot, see your booking in your Nextcloud Calendar, and surface zone-specific knowledge articles from xWiki right next to the booking. Every piece reuses what `@conduction/nextcloud-vue` and OpenRegister already give you for free. + +In Part 1 you scaffold the app, rename it, build it, and see the canonical app chassis on screen. No data, no integrations yet. We get the bones right first. + +{/* truncate */} + + + A working DeskDesk app installed in your local Nextcloud, navigable from the app shelf. + A clear mental model of the canonical Conduction app chassis: topbar, left navigation, main column, page header, sidebar. + Confidence with the nextcloud-app-template rename ritual: id, namespace, the few files you have to touch, the rest you can leave for later. + A clean foundation for Part 2, where the chassis starts holding real data. + + +## What we're building, across all four parts + +| Part | Title | Adds | +|---|---|---| +| 1 | Scaffold (you're here) | Empty app shell, chassis visible, navigable | +| 2 | Schemas + manifest | Desk and booking schemas, full CRUD, dashboard | +| 3 | Schema-driven integrations | Bookings appear in NC Calendar via the OpenRegister calendar provider | +| 4 | External knowledge + ship | xWiki source via OpenConnector, sidebar tab, package, publish | + +Same shape, two repos: + +- **App source:** [`ConductionNL/deskdesk`](https://github.com/ConductionNL/deskdesk) — the actual Nextcloud app you're forking from the template. +- **Tutorial source:** [`ConductionNL/conduction-website`](https://github.com/ConductionNL/conduction-website) — the post you're reading right now. + + + A running Nextcloud (28+) with admin access. If you don't have one, follow Nextcloud lokaal draaien met Docker first. + OpenRegister installed and enabled in that Nextcloud (Apps → search "OpenRegister" → Download). + Node.js 20+ and PHP 8.1+ on your host, plus Composer and the GitHub CLI (gh) — the template ships with PHPCS, Psalm, PHPStan, and ESLint configs ready to go. + A GitHub account so you can fork the template into your own org (or ConductionNL if you have access). + + +## Step 1: Use the template + +[`ConductionNL/nextcloud-app-template`](https://github.com/ConductionNL/nextcloud-app-template) is a GitHub *repository template*. The "Use this template" button gives you a fresh repo with the full starter kit: Vue 2 + Pinia frontend, PHP backend, OpenRegister wiring, quality pipeline, OpenSpec scaffolding, GitHub Actions. + +```bash title="Pick a name. We're calling ours deskdesk." +gh repo create ConductionNL/deskdesk \ + --template ConductionNL/nextcloud-app-template \ + --public \ + --description "Flexible desk booking for open-office environments" +``` + +Then clone it next to your other Nextcloud apps. Most workspaces keep them in a single `apps-extra/` directory the dev container mounts. + +```bash +cd /path/to/your/nextcloud/workspace/apps-extra +git clone https://github.com/ConductionNL/deskdesk.git +cd deskdesk +``` + +You now have a directory with the full template content under the new name. Nothing inside has been renamed yet — the directory is `deskdesk/`, but every file still says `app-template`, `AppTemplate`, `OCA\AppTemplate`. Step 2 fixes that. + +## Step 2: Rename the app + +Nextcloud requires three identifiers to line up: + +- The directory name (`deskdesk/`) ✅ already +- The `` in `appinfo/info.xml` +- The PHP namespace (`OCA\AppTemplate` → `OCA\DeskDesk`) + +Plus a handful of supporting files reference the old id. The full list is small enough to do by hand, and doing it by hand is the right move. Project memory: **never use `sed` or scripted edits on code files** — use a real editor with project-aware refactoring, or just read each file once before you change it. + +### 2a. The boot-critical files + +These are the ones that prevent Nextcloud from booting the app at all. Edit each with `Find & replace all` in your editor — `AppTemplate` → `DeskDesk` and `app-template` → `deskdesk`: + +| File | Replace | +|---|---| +| `appinfo/info.xml` | ``, ``, ``, ``, `` paths | +| `composer.json` | `name`, `description`, the `psr-4` autoload prefix | +| `package.json` | `name` | +| `webpack.config.js` | the `appId` constant | +| `templates/index.php` and `templates/settings/admin.php` | `OCA\AppTemplate` → `OCA\DeskDesk`, the `data-*` element id | +| `lib/AppInfo/Application.php` | namespace, `APP_ID` constant, docblock | +| Every other PHP file in `lib/` | `namespace OCA\AppTemplate\…` → `namespace OCA\DeskDesk\…`, every `use OCA\AppTemplate\…` import, every docblock | +| `appinfo/routes.php` | docblock only | +| Every Vue file in `src/` | the `t('app-template', '…')` translation namespace becomes `t('deskdesk', '…')` | +| `src/router/index.js` | `generateUrl('/apps/app-template')` → `generateUrl('/apps/deskdesk')` | +| `src/store/store.js`, `src/store/modules/settings.js` | `'/apps/app-template/api/settings'` → `'/apps/deskdesk/api/settings'`, fallback register slug | +| `src/settings.js` | `loadTranslations('app-template', …)` and the `#app-template-settings` mount selector | +| `lib/Settings/app_template_register.json` | rename to `lib/Settings/deskdesk_register.json` (and the `app` field inside it) | + +### 2b. The "you can do later" files + +Tests (`tests/Unit/AppTemplateTest.php`, the integration Postman collection), `phpcs.xml` / `phpmd.xml` / `REUSE.toml` headers, and the `.github/` workflow inputs. They reference the template id in metadata only; the app boots fine without them touched. Fix them when you set up CI. + +### 2c. One Nextcloud-version compatibility nip + +The template's `lib/AppInfo/Application.php` registers a repair step at runtime: + +```php title="Original — fails on some Nextcloud builds" +$context->registerRepairStep(InitializeSettings::class); +``` + +`registerRepairStep()` is missing from `IRegistrationContext` on a few Nextcloud builds (you'll see `Call to undefined method` in `nextcloud.log`). Move it to `appinfo/info.xml` instead — same effect, more portable: + +```xml title="appinfo/info.xml — append after the block" + + + OCA\DeskDesk\Repair\InitializeSettings + + + OCA\DeskDesk\Repair\InitializeSettings + + +``` + +Then drop the `registerRepairStep()` line and the `use OCA\DeskDesk\Repair\InitializeSettings;` import from `lib/AppInfo/Application.php`. + +## Step 3: Build and enable + +The template ships its build output uncommitted. So the first thing to do in a fresh clone is install dependencies and build the JS bundles, otherwise the app UI is just a blank `
`. + +```bash +composer install --no-dev +composer dump-autoload +npm install --legacy-peer-deps +npm run build +``` + +Then make sure your Nextcloud container can see the directory. In a typical Docker setup the `apps-extra/` host directory is mounted at `/var/www/html/custom_apps/` in the container; if your compose file uses one bind mount per app, add a line for `deskdesk` and restart. Otherwise: + +```bash title="Quick path: copy + chown for a one-shot install" +docker cp ./deskdesk nextcloud:/var/www/html/custom_apps/ +docker exec -u root nextcloud chown -R www-data:www-data /var/www/html/custom_apps/deskdesk +``` + +Now enable: + +```bash +docker exec nextcloud php occ app:enable deskdesk +``` + +You should see: + +``` +deskdesk 0.1.0 enabled +``` + +Open `http://localhost:8080/apps/deskdesk/` and log in. You see a placeholder dashboard with sample KPI cards, two empty panels, three nav items (Dashboard / Items / Documentation) and a Settings entry pinned to the bottom of the rail. That's the chassis. + +## The chassis: the whole point of Part 1 + +Every Conduction app — DeskDesk, OpenRegister, OpenCatalogi, Procest, MyDash, the dozen others — looks the same way on first sight. Same five structural pieces, same place, same behaviour. That recognisability is what `@conduction/nextcloud-vue` enforces: a user who learnt one app navigates the next one without docs. + +The chassis is **one shape, five atoms**. Each card below focuses on one atom: the focused zone is at full opacity with a KNVB-orange outline, the rest fades to 25% so you see *where* the atom sits. + + + +In Part 2, you'll learn how `manifest.json` plus a JSON schema fills these atoms with real data. The placeholder `Items` nav entry, the empty dashboard, and the "article" schema you saw on screen will all become **desks**, **bookings**, and a real occupancy dashboard — without you laying out a single atom by hand. + +## Troubleshooting + + + The js/ build output is not committed. Run npm run build before you enable the app, or after every frontend change. The blank-rectangle symptom always means "the bundle is missing". + + + + Nextcloud requires the directory name to exactly match the <id> in appinfo/info.xml. If you cloned the repo under a different name, either rename the directory or add a relative symlink: ln -s your-clone-name ../deskdesk. + + + + Step 2c covers this. The fix is to move the repair-step registration from lib/AppInfo/Application.php into appinfo/info.xml's <repair-steps> block. + + + + Run composer dump-autoload from the app directory. PHP's classmap caches the old OCA\AppTemplate map until you regenerate it. + + +## What's next + + + + + + From b3807ce6d2bf80720f0672f5d1033a43888df3d4 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sun, 10 May 2026 22:19:07 +0200 Subject: [PATCH 06/19] academy: Parts 2, 3, 4 of the DeskDesk tutorial MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Completes the four-part series started in Part 1. Part 2 — Schemas + manifest (/academy/deskdesk-tutorial-2-schemas-manifest/) Define floor / desk / booking schemas in OpenRegister with typed relations and seed data. Declare the register + pages + menu in src/manifest.json. Replace 175-line App.vue with 10 lines: a single CnAppRoot reading the manifest. Generate router routes from the manifest pointing at CnPageRenderer. Delete navigation/MainMenu.vue and the views/ stubs — the manifest renderer replaces them all. Part 3 — Schema-driven integrations (/academy/deskdesk-tutorial-3-calendar/) One configuration.calendarProvider block on the booking schema. Bookings appear in NC Calendar without any controller, listener, or per-app glue. Same pattern unlocks Activity, Mail Smart Picker, Contacts matching — anti-patterns called out (don't hand-code CalendarSync.vue / NotesService). Part 4 — Knowledge + ship (/academy/deskdesk-tutorial-4-knowledge-and-ship/) Author three zone-tagged knowledge articles in xWiki. Wire an OpenConnector source + synchronisation that maps xWiki pages to a knowledge_article schema in OpenRegister. Surface them on CnObjectSidebar via a custom KnowledgeTab component registered with cnCustomComponents. Package + sign + release the app. Reference repo: https://github.com/ConductionNL/deskdesk Series outline cross-linked via series: deskdesk-tutorial frontmatter. --- .../index.mdx | 303 ++++++++++++++++++ .../index.mdx | 184 +++++++++++ .../index.mdx | 277 ++++++++++++++++ 3 files changed, 764 insertions(+) create mode 100644 academy/2026-05-13-deskdesk-tutorial-2-schemas-manifest/index.mdx create mode 100644 academy/2026-05-15-deskdesk-tutorial-3-calendar/index.mdx create mode 100644 academy/2026-05-17-deskdesk-tutorial-4-knowledge-and-ship/index.mdx diff --git a/academy/2026-05-13-deskdesk-tutorial-2-schemas-manifest/index.mdx b/academy/2026-05-13-deskdesk-tutorial-2-schemas-manifest/index.mdx new file mode 100644 index 000000000..fc54a3439 --- /dev/null +++ b/academy/2026-05-13-deskdesk-tutorial-2-schemas-manifest/index.mdx @@ -0,0 +1,303 @@ +--- +slug: deskdesk-tutorial-2-schemas-manifest +title: "Build a Nextcloud app on the Conduction stack — Part 2: Schemas + manifest" +contentType: tutorial +authors: [conduction] +date: 2026-05-13 +summary: Define the desk, booking, and floor schemas in OpenRegister, declare them in a manifest.json, and watch CnAppRoot replace ~200 lines of hand-rolled Vue with three lines of config. +tags: [App development, OpenRegister, Schemas, Manifest, nextcloud-vue] +apps: [openregister] +durationMinutes: 25 +series: deskdesk-tutorial +partNumber: 2 +--- + +import { + Outcomes, + Outcome, + Prerequisites, + PrerequisiteItem, + NextSteps, + NextStep, + HexCard, +} from '@conduction/docusaurus-preset/components'; + +This is **Part 2 of the four-part DeskDesk tutorial**. Part 1 left you with an empty app shell — a chassis, no data. Part 2 fills the chassis: three schemas, a manifest, and the same five Cn* pages drive every list, every detail view, every dashboard with the schema as the single source of truth. + +The shape we keep saying "this saves you code" finally has numbers behind it: ~200 lines of hand-rolled Vue collapse to three. + +{/* truncate */} + + + Three OpenRegister schemas — floor, desk, booking — installed and seeded with realistic demo data on first boot. + A src/manifest.json that declares the navigation, the routes, and the configuration for every page. CnAppRoot reads it; CnPageRenderer dispatches. + An App.vue of ~10 lines and a router/index.js that generates routes from the manifest. navigation/MainMenu.vue is gone. So is views/Dashboard.vue, views/items/*, and the user-settings shells. The manifest renderer replaces them. + Index pages, detail pages, and CRUD dialogs that the schema generates — sortable, filterable, searchable — without you writing a column definition. + + + + Part 1 done: DeskDesk installed, the chassis renders at http://localhost:8080/apps/deskdesk/. + OpenRegister installed, enabled, and at version 0.2.10 or newer (the importFromFilePath ConfigurationService method is required). + The local clone of ConductionNL/deskdesk on your filesystem and a working npm run build. + + +## Step 1: Define the three schemas + +Open `lib/Settings/deskdesk_register.json` and replace the placeholder `article` schema with the real three. Each schema is a JSON Schema with a couple of OpenRegister extensions: `slug` for the URL identifier, `icon` for the MDI glyph the UI uses, and `x-openregister-relations` for typed cross-schema links. + +```json title="lib/Settings/deskdesk_register.json (excerpt)" +"floor": { + "slug": "floor", + "title": "Floor", + "description": "A physical floor in a building.", + "type": "object", + "required": ["label"], + "properties": { + "label": { "type": "string", "example": "Floor 3" }, + "building": { "type": "string", "example": "Amsterdam HQ" }, + "planImage": { "type": "string", "format": "uri" } + } +} +``` + +The `desk` schema adds the typed relation to floor: + +```json title="lib/Settings/deskdesk_register.json (excerpt)" +"desk": { + "slug": "desk", + "title": "Desk", + "type": "object", + "required": ["label", "floor", "zone"], + "properties": { + "label": { "type": "string", "example": "3-East-12" }, + "floor": { + "type": "string", + "x-openregister-relations": { + "schema": "floor", + "cardinality": "many-to-one" + } + }, + "zone": { + "type": "string", + "enum": ["north", "south", "east", "west", "central"] + }, + "equipment": { + "type": "array", + "items": { "type": "string", "enum": ["dual-monitor", "standing", "phonebooth-adjacent", "wired-network", "extra-power"] } + }, + "capacity": { "type": "integer", "minimum": 1, "default": 1 }, + "accessibility": { "type": "boolean", "default": false }, + "photo": { "type": "string", "format": "uri" }, + "notes": { "type": "string" } + } +} +``` + +Booking is the same shape — a relation, a couple of date-times, an enum status, and an optional RRULE for recurring bookings. + +The full file ships in [the deskdesk repo](https://github.com/ConductionNL/deskdesk/blob/main/lib/Settings/deskdesk_register.json), with five seed desks, two floors, and three bookings so the app is browsable on first install. + +## Step 2: Declare the register itself + +OpenRegister's import handler creates schemas eagerly, but it only creates a **register** when the JSON declares one explicitly. Add a `components.registers` block above your schemas: + +```json title="lib/Settings/deskdesk_register.json" +"components": { + "registers": { + "deskdesk": { + "slug": "deskdesk", + "title": "DeskDesk", + "description": "Floors, desks, and bookings for the DeskDesk app.", + "version": "0.2.0", + "schemas": ["floor", "desk", "booking"] + } + }, + "schemas": { /* ... */ }, + "objects": { /* ... */ } +} +``` + +The `schemas` array tells OpenRegister which of the schemas in the same file belong to this register. The `slug` becomes the register's stable identifier. + +## Step 3: Trigger the import + +Two ways. Either trigger it via the SettingsController (the app exposes `POST /api/settings/load`): + +```bash title="From your shell, via curl, with a logged-in cookie jar" +curl -X POST -b cookies.txt \ + -H "requesttoken: $REQUESTTOKEN" \ + http://localhost:8080/index.php/apps/deskdesk/api/settings/load +``` + +Or — cleaner during local development — disable + re-enable the app, which fires the `` repair step from `appinfo/info.xml`: + +```bash +docker exec nextcloud php occ app:disable deskdesk +docker exec nextcloud php occ app:enable deskdesk +``` + +Either way, you should see in the OpenRegister registers list: + +``` +slug: deskdesk title: DeskDesk application: deskdesk schemas: floor, desk, booking +``` + +The matching path the SettingsService takes is **importFromFilePath** with a path resolved relative to `\OC::$SERVERROOT` (which is `/var/www/html` in a default install). The boot-time bug I hit during this tutorial — `Configuration file not found: html/custom_apps/deskdesk/...` — was a relative-path mistake; the fix lands in [the diff](https://github.com/ConductionNL/deskdesk/pull/2/files#diff-lib-Service-SettingsService.php). + +## Step 4: The manifest + +`src/manifest.json` is the single declarative description of the app shell. Three top-level keys: `dependencies`, `menu`, `pages`. + +```json title="src/manifest.json" +{ + "$schema": "https://raw.githubusercontent.com/ConductionNL/nextcloud-vue/main/src/schemas/app-manifest.schema.json", + "version": "1.0.0", + "id": "deskdesk", + "title": "DeskDesk", + "dependencies": ["openregister"], + "menu": [ + {"id": "desks-index", "label": "deskdesk.menu.desks", "icon": "icon-category-organization", "route": "desks-index", "order": 10}, + {"id": "bookings-index", "label": "deskdesk.menu.bookings", "icon": "icon-calendar", "route": "bookings-index", "order": 20}, + {"id": "floors-index", "label": "deskdesk.menu.floors", "icon": "icon-category-monitoring", "route": "floors-index", "order": 30} + ], + "pages": [ + {"id": "desks-index", "route": "/", "type": "index", "title": "deskdesk.desks.indexTitle", + "config": {"register": "deskdesk", "schema": "desk", "columns": ["label","floor","zone","equipment","capacity","accessibility"], "filters": ["floor","zone","equipment","accessibility"], "defaultSort": {"key": "label", "order": "asc"}}}, + {"id": "desks-detail", "route": "/desks/:id", "type": "detail", "title": "deskdesk.desks.detailTitle", + "config": {"register": "deskdesk", "schema": "desk"}} + /* bookings + floors pages follow the same pattern */ + ] +} +``` + +A few rules to note: + +- **`page.id` is the vue-router route name.** CnPageRenderer matches `$route.name === page.id` to pick the right page. +- **`page.type` is closed.** `index | detail | dashboard | custom`. Use `custom` with a `component` registry entry for one-off pages. +- **`page.config` is forwarded as props.** `register`, `schema`, `columns`, `filters`, `defaultSort` map to CnIndexPage props one-to-one. + +## Step 5: Replace App.vue and the router + +Now the satisfying part. Open `src/App.vue` — the 175-line shell from Part 1 — and replace it with this: + +```vue title="src/App.vue" + + + +``` + +That's it. The ``, the dependency-check empty-state, the loading spinner, the menu, the sidebars, the user-settings dialog — CnAppRoot ships them all and gates them on the manifest's `dependencies` array. + +Then `src/router/index.js` becomes a manifest-to-route mapping: + +```js title="src/router/index.js" +import Vue from 'vue' +import Router from 'vue-router' +import { generateUrl } from '@nextcloud/router' +import { CnPageRenderer } from '@conduction/nextcloud-vue' +import manifest from '../manifest.json' + +Vue.use(Router) + +const routes = manifest.pages.map((page) => ({ + name: page.id, + path: page.route, + component: CnPageRenderer, +})) + +routes.push({ path: '*', redirect: '/' }) + +export default new Router({ + mode: 'history', + base: generateUrl('/apps/deskdesk'), + routes, +}) +``` + +Every page in the manifest gets a route. Every route renders CnPageRenderer. CnPageRenderer reads the manifest, finds the matching page entry by route name, and dispatches by `page.type`: + +- `type: "index"` → CnIndexPage with the schema's columns + filters +- `type: "detail"` → CnDetailPage with the schema's properties +- `type: "dashboard"` → CnDashboardPage with the layout +- `type: "custom"` → your registered component + +## Step 6: Delete the old views + +The whole point of Part 2 is that the framework writes the views for you. Delete: + +- `src/navigation/MainMenu.vue` — replaced by CnAppNav (auto-mounted by CnAppRoot) +- `src/views/Dashboard.vue` — placeholder; will return in a richer form when the dashboard page wants it +- `src/views/items/ItemList.vue`, `src/views/items/ItemDetail.vue` — replaced by CnIndexPage + CnDetailPage +- `src/views/settings/UserSettings.vue`, `src/views/settings/Settings.vue` — settings live on the admin page (`AdminRoot.vue`) and the in-app dialog ships with CnAppRoot + +The before-and-after on disk: + +``` +before: 12 .vue files in src/, ~600 lines of hand-rolled component code +after: 3 .vue files (App.vue, AdminRoot.vue, that's it), ~30 lines +``` + +## Step 7: Build, reload, browse + +```bash +npm run build +docker cp ./js nextcloud:/var/www/html/custom_apps/deskdesk/ +docker exec nextcloud apache2ctl graceful +``` + +Open `/apps/deskdesk/`. The left rail now reads **Desks · Bookings · Floors** (the translation keys will look like `deskdesk.menu.desks` until you add the `l10n/` entries — that's a follow-up). The main column shows CnIndexPage with the schema's columns. Click a row, you're on CnDetailPage for that desk. The whole CRUD flow comes for free. + +## Why this matters + +You wrote three JSON files. The framework reads them and renders the app. There is no `DesksList.vue`, no `DeskForm.vue`, no `useDesks.js`. When you add a `priority` field to the booking schema, every place a booking appears — table column, detail row, filter sidebar, form dialog — picks up the new field automatically. + +This is the schema-driven discipline talked about on [nextcloud-vue.conduction.nl](https://nextcloud-vue.conduction.nl/docs/architecture/schemas-and-registers/). Part 3 puts it to work in a way that surprises people the first time: a single JSON block on the booking schema makes every booking appear in the user's Nextcloud Calendar, no controller, no event, no listener. + +## Troubleshooting + + + The relative path used by ConfigurationService::importFromFilePath is computed against /var/www/html (Nextcloud's SERVERROOT). If your SettingsService strips the wrong prefix you get this error. Use \OC::$SERVERROOT as the anchor when stripping. + + + + Add a components.registers.YOURAPP block to the JSON. Without it, the import handler creates the schemas as orphans. The block needs slug, title, version, and schemas[]. + + + + CnIndexPage's store wants the numeric register id, while /api/settings typically returns the register slug. Wire a slug → id resolution in your settings store, or pass the numeric id from the SettingsService directly. Tracked as a follow-up in the Part 2 PR. + + + + The keys (e.g. deskdesk.menu.desks) need entries in l10n/en.json and l10n/nl.json. The library renders the key when no translation is found. Add them whenever you're ready; the app works either way. + + +## What's next + + + + + + diff --git a/academy/2026-05-15-deskdesk-tutorial-3-calendar/index.mdx b/academy/2026-05-15-deskdesk-tutorial-3-calendar/index.mdx new file mode 100644 index 000000000..74c7aaee4 --- /dev/null +++ b/academy/2026-05-15-deskdesk-tutorial-3-calendar/index.mdx @@ -0,0 +1,184 @@ +--- +slug: deskdesk-tutorial-3-calendar +title: "Build a Nextcloud app on the Conduction stack — Part 3: Schema-driven integrations" +contentType: tutorial +authors: [conduction] +date: 2026-05-15 +summary: One calendarProvider block on the booking schema. Bookings appear in NC Calendar with no controller, no event listener, no per-app glue. +tags: [App development, OpenRegister, NC Calendar, CalDAV, Schema integrations] +apps: [openregister] +durationMinutes: 15 +series: deskdesk-tutorial +partNumber: 3 +--- + +import { + Outcomes, + Outcome, + Prerequisites, + PrerequisiteItem, + NextSteps, + NextStep, + HexCard, +} from '@conduction/docusaurus-preset/components'; + +This is **Part 3 of the four-part DeskDesk tutorial**. Part 2 wired the desk and booking schemas. Part 3 makes those bookings appear in every user's Nextcloud Calendar — *without writing a controller, an event listener, or any per-app calendar glue*. + +The trick: OpenRegister already ships an `ICalendarProvider` (`RegisterCalendarProvider`) that exposes any schema with a `calendarProvider` block as a virtual calendar. You declare the block on the booking schema. NC Calendar shows the events. That's the whole story. + +{/* truncate */} + + + Every booking object in DeskDesk shows up as a read-only event in the Nextcloud Calendar app, with the desk label + user in the title and the cobalt brand colour. + A working understanding of OpenRegister's schema-driven integration providers: the same approach unlocks Activity, Mail Smart Picker, Contacts matching, and File actions — all without per-app code. + A pattern you can reuse on any future schema. Add a date field + a calendarProvider block. Done. + + + + Part 2 done: floor, desk, and booking schemas live in OpenRegister and the app shows desks at /apps/deskdesk/. + NC Calendar enabled (Apps → search "Calendar" → Download). Out of the box it'll pick up new ICalendarProviders automatically — no extra wiring on your end. + + +## Why not OpenConnector or a custom listener? + +When you first hear "sync DeskDesk bookings to Nextcloud Calendar" your instinct is probably one of: + +1. **Write an OpenConnector flow** that listens to `BookingCreatedEvent` and POSTs to the CalDAV endpoint +2. **Subclass `IEventListener`** in PHP and create a CalDAV event by hand +3. **Have the frontend `POST /remote.php/dav/...`** when the user clicks Save + +All three work. All three are wrong for this case, because they treat NC Calendar as a *destination* you push to. OpenRegister already exposes every schema as a calendar — you're not pushing anywhere; you're declaring that the booking schema *is* a calendar source. + +The same pattern applies to: + +| Integration | Schema annotation | What you get | +|---|---|---| +| **NC Calendar** | `calendarProvider` block | Read-only virtual calendar per schema | +| **NC Activity** | (automatic, free) | CRUD events in the activity stream + dashboard widget + email digest | +| **NC Mail Smart Picker** | `linkedTypes: ["email"]` | Paste a desk URL into NC Mail and get a rich preview | +| **NC Contacts** | `linkedTypes: ["contact"]` | `/api/contacts/match` returns matching desk users | + +Anti-pattern: hand-coding `CalendarSync.vue`, `NotesService`, `MailSidebar.vue`. Use the providers. + +## Step 1: Add the calendarProvider block + +Open `lib/Settings/deskdesk_register.json` and add a `configuration` block to the `booking` schema: + +```json title="lib/Settings/deskdesk_register.json (excerpt) — booking schema" +"booking": { + "slug": "booking", + "title": "Booking", + /* ... existing properties ... */ + + "configuration": { + "calendarProvider": { + "enabled": true, + "dtstart": "start", + "dtend": "end", + "titleTemplate": "Desk {desk} · {user}", + "color": "#003a8c" + } + } +} +``` + +The five fields: + +- `enabled` — `true` exposes the schema as a virtual calendar. +- `dtstart` — name of the property that holds the event start. We use the `start` property already on every booking. +- `dtend` — same, but for the end. Optional — when missing, OpenRegister falls back to a sensible default (next-hour for date-time, end-of-day for date-only). +- `titleTemplate` — string template for the event summary. Tokens in `{braces}` are resolved against the object's properties. We use `Desk {desk} · {user}`. To resolve relations (the `desk` property is a uuid pointing at a `desk` object), include `_extend=relations` when the calendar provider fetches — see the OpenRegister side. +- `color` — hex colour of the calendar. We use cobalt-blue (`--c-blue-cobalt`). + +That's the whole change. No other file needs to be touched. + +## Step 2: Re-import the configuration + +Schemas in OpenRegister have a version. Bumping the schema version + re-running the import is the canonical way to update them in place. Bump the file's `info.version` (`0.2.0` → `0.3.0`) and the booking schema's `version`: + +```json +"info": { "version": "0.3.0" }, +"booking": { "version": "0.3.0", /* ... */ } +``` + +Then trigger the import the same way as Part 2: + +```bash +docker exec nextcloud php occ app:disable deskdesk +docker exec nextcloud php occ app:enable deskdesk +``` + +(Or hit `POST /api/settings/load` if you've wired admin settings yet.) + +## Step 3: Verify in NC Calendar + +Open `/apps/calendar/`. You should see a new entry in the calendar list: + +- **Booking** — cobalt-blue colour, read-only badge, owned by your user + +Click it. The seed bookings from Part 2 (the Monday focus-work session, the Tuesday sprint-planning meeting, the tentative quiet morning) appear as events. Click one. The popover shows the desk + user title + the start/end times. + +If you don't see the calendar: + +- Confirm OpenRegister registered its provider: `docker exec nextcloud php occ config:list | grep openregister` +- Make sure your `booking` schema has `configuration.calendarProvider.enabled: true` after the re-import +- Check `nextcloud.log` for `RegisterCalendarProvider` messages + +## How it works under the hood + +OpenRegister's `RegisterCalendarProvider` implements Nextcloud's `ICalendarProvider` interface. On every Calendar API request, it: + +1. Lists every schema with `configuration.calendarProvider.enabled === true` +2. For each, returns a `RegisterCalendar` (a lightweight `ICalendar` implementation) +3. When the user opens that calendar, queries OpenRegister for objects where `dtstart` is in the requested range +4. Transforms each object into an iCalendar VEVENT via `CalendarEventTransformer`, applying the `titleTemplate` + +Per-object ACLs gate event content. The provider bypasses multi-tenancy when listing schemas (so cross-tenant calendars work), but every individual event is still gated by RBAC. + +The same pattern is followed by [the spec](https://github.com/ConductionNL/openregister/blob/main/openspec/specs/calendar-provider/spec.md). + +## What you've now bought yourself + +Activity (`NC Activity`) is already wired. Browse `/apps/activity/` and you'll see your booking creates / updates. The dashboard activity widget + email digest both show DeskDesk events. Zero code on your end. + +Mail Smart Picker too: in NC Mail, paste a desk URL (`/apps/deskdesk/desks/desk-3-east-12`) and the picker resolves it into a rich preview card with the desk label + zone + equipment. + +You've effectively integrated DeskDesk into four Nextcloud apps with one JSON block. That's the schema-driven discipline paying off. + +## Troubleshooting + + + Confirm the booking schema's configuration.calendarProvider.enabled is true after the re-import. Browse /index.php/apps/openregister/api/schemas/booking and look for the configuration block in the response. If it's missing, the re-import didn't pick up the change — bump the version and try again. + + + + The titleTemplate doesn't resolve relations by default. Add a _extend=relations via the schema's calendarProvider extras, or use the desk's slug instead of its uuid in the template ("titleTemplate": "Desk {deskSlug}" when you've stored the slug as a property). + + + + Check the timezone on the booking's start / end values. They must be ISO 8601 with timezone info (e.g. 2026-05-12T09:00:00+02:00). Naive datetimes are interpreted as UTC and may render shifted. + + + + The provider needs OpenRegister 0.2.x or newer. Older versions don't ship RegisterCalendarProvider. Upgrade OpenRegister via the app store. + + +## What's next + + + + + + diff --git a/academy/2026-05-17-deskdesk-tutorial-4-knowledge-and-ship/index.mdx b/academy/2026-05-17-deskdesk-tutorial-4-knowledge-and-ship/index.mdx new file mode 100644 index 000000000..8dc241f05 --- /dev/null +++ b/academy/2026-05-17-deskdesk-tutorial-4-knowledge-and-ship/index.mdx @@ -0,0 +1,277 @@ +--- +slug: deskdesk-tutorial-4-knowledge-and-ship +title: "Build a Nextcloud app on the Conduction stack — Part 4: Knowledge + ship" +contentType: tutorial +authors: [conduction] +date: 2026-05-17 +summary: Author zone-specific knowledge articles in xWiki, surface them per-desk via OpenConnector, package the app, publish to the Conduction app store. +tags: [App development, OpenConnector, xWiki, Knowledge management, Release] +apps: [openconnector, openregister] +durationMinutes: 30 +series: deskdesk-tutorial +partNumber: 4 +--- + +import { + Outcomes, + Outcome, + Prerequisites, + PrerequisiteItem, + NextSteps, + NextStep, + HexCard, +} from '@conduction/docusaurus-preset/components'; + +The final part of the four-part DeskDesk tutorial. Part 3 made bookings appear in NC Calendar with one schema annotation. Part 4 brings external knowledge — zone-specific etiquette, equipment notes, troubleshooting — from xWiki into the desk detail sidebar via OpenConnector. Then we package the app and ship it. + +External knowledge integration is the canonical case for OpenConnector: pull data from a system you don't own (xWiki here, but the same applies to Confluence, Notion, SharePoint, Decos, Mendix), surface it inside Nextcloud-native UI without making the user context-switch. + +{/* truncate */} + + + Three xWiki articles you author yourself, tagged by floor zone (zone:east, zone:central, zone:west) so they can be filtered. + A working OpenConnector source pointing at xWiki's REST API, queryable by tag. + A "Knowledge" tab on every desk's detail page that lists the articles whose zone tag matches the desk's zone — without leaving DeskDesk. + A versioned, packaged DeskDesk release on the Conduction app store, installable from any Nextcloud's app browser. + + + + Parts 1–3 done: DeskDesk shell, schemas + manifest, calendar integration all working. + OpenConnector installed and enabled (Apps → search "OpenConnector" → Download). + An xWiki instance you can reach over HTTP. Either a public xwiki.org wiki you authored on, or a self-hosted one. The tutorial uses https://your-xwiki.example.com/xwiki/rest as the placeholder. + + +## Step 1: Author the knowledge articles in xWiki + +xWiki ships with a default "Sandbox" space. For tutorial purposes, create a fresh space called **DeskDesk Knowledge** and author three articles inside it. + +``` +Space: DeskDesk Knowledge +Article: East zone etiquette +Tags: zone:east, deskdesk +Body: The east windows get direct sun 14:00–17:00 in summer. Bring + a curtain clip from the supplies cabinet on floor 3. Phone + calls are fine in this zone — phonebooths are a 30-second + walk away. +``` + +``` +Article: Central zone meeting desks +Tags: zone:central, deskdesk, meetings +Body: Central desks 11–14 seat four. The cabling channel under each + desk has two HDMI ports + USB-C 100W power. Camera + mic in + the ceiling are wired into the in-room Jitsi instance — see + the QR code on the desk. +``` + +``` +Article: West zone quiet rules +Tags: zone:west, deskdesk, focus +Body: The west zone is the designated quiet zone. No calls, no + meetings. Keep notifications muted. The kitchen is on the + opposite side of the floor for a reason. +``` + +Three short articles. Realistic enough that you'd actually write them in production. The key piece is the **`zone:` tag**: that's what we'll filter by from DeskDesk. + +Why the tutorial walks you through authoring instead of seeding programmatically: in real teams, the wiki articles are *organic* — written by whoever notices the etiquette gap, edited by the next person, archived when the floorplan changes. The integration we build makes them findable from where the user already is, not the other way around. + +## Step 2: Create an OpenConnector source for xWiki + +Go to `/apps/openconnector/sources` and create a new source. + +``` +Name: xWiki Knowledge +Type: API +Location: https://your-xwiki.example.com/xwiki/rest +Auth: Basic — username = your wiki user, password = your wiki API token +Test: GET /wikis/xwiki/spaces/DeskDesk%20Knowledge/pages +``` + +The "Test" call should return the three pages you just authored. xWiki's REST API uses XML by default; pass `Accept: application/json` to get JSON back. OpenConnector's source config has a Headers section — add `Accept: application/json` there. + +Save. The source's id (numeric, in the URL) is what we'll reference from DeskDesk. + +## Step 3: Define the synchronisation + +Sources surface raw API responses; **synchronisations** map them into OpenRegister objects. Create a synchronisation: + +``` +Name: xWiki articles → OpenRegister +Source: xWiki Knowledge (the one you just made) +Source endpoint: /wikis/xwiki/spaces/DeskDesk Knowledge/pages +Target register: deskdesk +Target schema: knowledge_article (you'll add this in step 4) +Mapping: title → name + content → body + tags → tags + id → externalId +Schedule: every 30 minutes (or on demand) +``` + +Save and run once. You'll see three new `knowledge_article` objects appear in OpenRegister. + +## Step 4: Add the knowledge_article schema + +Add a fourth schema to `lib/Settings/deskdesk_register.json`: + +```json title="lib/Settings/deskdesk_register.json (excerpt)" +"knowledge_article": { + "slug": "knowledge-article", + "title": "KnowledgeArticle", + "description": "An xWiki article surfaced in DeskDesk via OpenConnector. Read-only — the source of truth lives in xWiki.", + "type": "object", + "required": ["name"], + "properties": { + "name": { "type": "string", "description": "Article title" }, + "body": { "type": "string", "description": "Article markdown body" }, + "tags": { "type": "array", "items": { "type": "string" } }, + "externalId": { "type": "string", "description": "xWiki page id" }, + "url": { "type": "string", "format": "uri", "description": "Permalink to the article in xWiki" } + } +} +``` + +Add it to the register's `schemas` array (`floor`, `desk`, `booking`, `knowledge_article`). Bump versions, re-import. The four schemas are now in place. + +## Step 5: Surface articles in CnObjectSidebar + +The desk-detail page already mounts CnObjectSidebar via the manifest. We want a **"Knowledge" tab** on that sidebar, populated by knowledge articles whose `zone:` tag matches the desk's zone. + +Two options: + +1. **Add a custom sidebar component** registered in the customComponents registry, which renders the article list when the desk's zone resolves to a tag query. +2. **Use the schema's `relatedSources` annotation** (if it exists in your OpenRegister version) to declare the relation declaratively. + +For the tutorial we'll use option 1, which is the most portable. Register the sidebar component in `src/main.js`: + +```js title="src/main.js (excerpt)" +import KnowledgeTab from './sidebar/KnowledgeTab.vue' + +const customComponents = { + 'knowledge-tab': KnowledgeTab, +} + +new Vue({ + /* ... */ + provide: { cnCustomComponents: customComponents }, +}) +``` + +And the component itself: + +```vue title="src/sidebar/KnowledgeTab.vue" + + + +``` + +Wire it into the manifest's desks-detail page: + +```json title="src/manifest.json (excerpt)" +{ + "id": "desks-detail", + "route": "/desks/:id", + "type": "detail", + "config": { + "register": "deskdesk", + "schema": "desk", + "sidebar": { + "tabs": [ + { "id": "knowledge", "component": "knowledge-tab" } + ] + } + } +} +``` + +Build, reload, navigate to `/desks/desk-3-east-12`. The right sidebar shows a "Knowledge" tab. Click it. The "East zone etiquette" article you wrote in xWiki appears, with the body rendered and a link back to the wiki for editing. + +## Step 6: Package the app + +The release process is mechanical: + +1. **Bump the version** in `appinfo/info.xml` (`0.1.0` → `0.4.0`). +2. **Run the production build**: + ```bash + composer install --no-dev --optimize-autoloader + npm install --legacy-peer-deps + npm run build + ``` +3. **Strip unwanted files** — vendor dev dependencies, node_modules, tests, openspec changes, .git, .github (your CI is already at the source level). The template ships a `Makefile` target for this; if not, the production tarball pattern is: + ```bash + tar --exclude=node_modules --exclude=vendor/bin \ + --exclude=tests --exclude=openspec/changes \ + --exclude=.git --exclude=.github \ + -czf deskdesk-0.4.0.tar.gz deskdesk/ + ``` +4. **Sign the tarball** with the EC key Nextcloud's app store expects (see [the Nextcloud docs](https://nextcloudappstore.readthedocs.io/en/latest/developer.html#publishing-your-app)). +5. **Push to GitHub** with a tag matching the version (`git tag v0.4.0 && git push origin v0.4.0`). The template ships a `.github/workflows/release.yml` that picks up the tag, builds the tarball, signs it, and publishes the release. + +For the **Conduction app store** (`apps.conduction.nl`), the same release pipeline pushes to that endpoint when the secret is configured. See [the .github/workflows/release.yml](https://github.com/ConductionNL/nextcloud-app-template/blob/main/.github/workflows/release.yml) reference. + +## Step 7: Submit to apps.nextcloud.com (optional) + +If you want DeskDesk in the public Nextcloud app store, register at [apps.nextcloud.com](https://apps.nextcloud.com), upload your signed tarball, fill out the form (description, screenshots, license — EUPL-1.2 is fine — categories `organization`), and wait for review. The cycle is usually 1–3 days for first-time apps. + +## What you've built + +You started Part 1 with an empty template. Forty-eight steps later, you have: + +- A real Nextcloud app with the canonical chassis +- Three OpenRegister schemas with relations and seed data +- A manifest-driven shell with zero hand-rolled views +- Bookings that appear in NC Calendar via a schema annotation +- Knowledge articles from xWiki that appear in the desk-detail sidebar +- A versioned, packaged, publishable release + +Total Vue you wrote: **one custom sidebar component** (~30 lines). Total PHP: the rename ritual + a path-resolution fix in SettingsService. Everything else is JSON. + +That ratio — JSON describing what you want, framework rendering it — is the entire point of the Conduction stack. Schemas drive both backend and frontend. Manifests drive the shell. Schema annotations drive cross-app integrations. You declare; the framework renders. + +## Where to next + + + + + + From 9ae599d0bf6d0dd80939173b34f74dbca0b5dd85 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Mon, 11 May 2026 10:04:24 +0200 Subject: [PATCH 07/19] academy: real xWiki setup steps in Part 4 of the DeskDesk tutorial Part 4 was previously a paper walkthrough with placeholder URLs. Rewrites it as the actual flow we ran against a local xWiki: - New Step 0 + Step 0a: docker-compose --profile xwiki up -d, create the xwiki database in the shared Postgres, walk the Distribution Wizard (admin user, "Let the wiki be empty"), verify REST. - Step 1: REST PUT recipe for the three zone-scoped articles using the actual subspace shape (DeskDeskKnowledge.{East,Central,West}). Subspaces replace the original "tags" approach because the no-flavor wiki ships without the Tags application. - Step 2: knowledge_article schema with the actual JSON key/slug match-or-fail trap called out explicitly. Wires SCHEMA_SLUGS in PHP and the SCHEMAS list in the frontend store. - Step 3-4: OpenConnector source + synchronisation against the container-internal http://openregister-xwiki:8080/rest URL (the host URL http://localhost:8086 is documented as the alternative). - Step 5: real KnowledgeTab.vue with the watch-objectId pattern that the reference repo at ConductionNL/deskdesk now ships. App.vue shows the CnAppRoot #sidebar slot + objectSidebarState wiring consumers need to make CnDetailPage's external-sidebar channel work. - New Troubleshooting block covering the four traps we hit while building this: schema-key/slug mismatch, DetailPageWrapper bridging, "Let the wiki be empty" path for offline containers, Accept-JSON header on the OpenConnector source. Cross-links to the reference repo at ConductionNL/deskdesk (PR #4 shipped the matching Part 4 implementation). --- .../index.mdx | 412 ++++++++++++------ 1 file changed, 289 insertions(+), 123 deletions(-) diff --git a/academy/2026-05-17-deskdesk-tutorial-4-knowledge-and-ship/index.mdx b/academy/2026-05-17-deskdesk-tutorial-4-knowledge-and-ship/index.mdx index 8dc241f05..be21af33e 100644 --- a/academy/2026-05-17-deskdesk-tutorial-4-knowledge-and-ship/index.mdx +++ b/academy/2026-05-17-deskdesk-tutorial-4-knowledge-and-ship/index.mdx @@ -4,10 +4,10 @@ title: "Build a Nextcloud app on the Conduction stack — Part 4: Knowledge + sh contentType: tutorial authors: [conduction] date: 2026-05-17 -summary: Author zone-specific knowledge articles in xWiki, surface them per-desk via OpenConnector, package the app, publish to the Conduction app store. +summary: Spin up xWiki locally, author zone-specific knowledge articles, surface them per-desk via an OpenRegister knowledge_article schema, package the app, publish to the Conduction app store. tags: [App development, OpenConnector, xWiki, Knowledge management, Release] apps: [openconnector, openregister] -durationMinutes: 30 +durationMinutes: 35 series: deskdesk-tutorial partNumber: 4 --- @@ -22,204 +22,352 @@ import { HexCard, } from '@conduction/docusaurus-preset/components'; -The final part of the four-part DeskDesk tutorial. Part 3 made bookings appear in NC Calendar with one schema annotation. Part 4 brings external knowledge — zone-specific etiquette, equipment notes, troubleshooting — from xWiki into the desk detail sidebar via OpenConnector. Then we package the app and ship it. +The final part of the four-part DeskDesk tutorial. Part 3 made bookings appear in NC Calendar with one schema annotation. Part 4 brings external knowledge — zone-specific etiquette, equipment notes, troubleshooting — from xWiki into the desk detail sidebar via a `knowledge_article` OpenRegister schema. Then we package the app and ship it. External knowledge integration is the canonical case for OpenConnector: pull data from a system you don't own (xWiki here, but the same applies to Confluence, Notion, SharePoint, Decos, Mendix), surface it inside Nextcloud-native UI without making the user context-switch. {/* truncate */} - Three xWiki articles you author yourself, tagged by floor zone (zone:east, zone:central, zone:west) so they can be filtered. - A working OpenConnector source pointing at xWiki's REST API, queryable by tag. - A "Knowledge" tab on every desk's detail page that lists the articles whose zone tag matches the desk's zone — without leaving DeskDesk. + A running xWiki instance on http://localhost:8086, on the same Docker network as Nextcloud, sharing the existing Postgres. + Three xWiki articles you author yourself, scoped by zone (DeskDeskKnowledge.East.Etiquette, .Central.MeetingDesks, .West.QuietRules). + A new knowledge_article OpenRegister schema in DeskDesk, with seed data matching what xWiki holds. + A "Knowledge" tab on every desk's detail page that lists the articles whose zone matches the desk's zone, without leaving DeskDesk. A versioned, packaged DeskDesk release on the Conduction app store, installable from any Nextcloud's app browser. Parts 1–3 done: DeskDesk shell, schemas + manifest, calendar integration all working. - OpenConnector installed and enabled (Apps → search "OpenConnector" → Download). - An xWiki instance you can reach over HTTP. Either a public xwiki.org wiki you authored on, or a self-hosted one. The tutorial uses https://your-xwiki.example.com/xwiki/rest as the placeholder. + A local Nextcloud + OpenRegister stack you can docker exec into. The reference stack at Nextcloud lokaal draaien met Docker is what we test against; it already ships the xWiki service we'll start in Step 0. + OpenConnector installed and enabled, for when you switch from seed data to a live xWiki sync later in the tutorial. -## Step 1: Author the knowledge articles in xWiki +## Step 0: Spin up xWiki + +The Conduction dev stack already declares an xWiki service under the `xwiki` profile of `apps-extra/openregister/docker-compose.yml`: + +```yaml title="openregister/docker-compose.yml (excerpt)" + xwiki: + profiles: [xwiki, integrations] + image: xwiki:lts-postgres-tomcat + container_name: openregister-xwiki + ports: ["8086:8080"] + volumes: ["xwiki-data:/usr/local/xwiki"] + environment: + DB_USER: nextcloud + DB_PASSWORD: "!ChangeMe!" + DB_HOST: db + DB_DATABASE: xwiki + depends_on: [db] +``` -xWiki ships with a default "Sandbox" space. For tutorial purposes, create a fresh space called **DeskDesk Knowledge** and author three articles inside it. +xWiki shares the Postgres container with Nextcloud + OpenRegister, but uses a separate database called `xwiki`. Create it once: -``` -Space: DeskDesk Knowledge -Article: East zone etiquette -Tags: zone:east, deskdesk -Body: The east windows get direct sun 14:00–17:00 in summer. Bring - a curtain clip from the supplies cabinet on floor 3. Phone - calls are fine in this zone — phonebooths are a 30-second - walk away. +```bash +docker exec openregister-postgres psql -U nextcloud -d postgres -c "CREATE DATABASE xwiki;" ``` -``` -Article: Central zone meeting desks -Tags: zone:central, deskdesk, meetings -Body: Central desks 11–14 seat four. The cabling channel under each - desk has two HDMI ports + USB-C 100W power. Camera + mic in - the ceiling are wired into the in-room Jitsi instance — see - the QR code on the desk. -``` +Now start xWiki with the profile: +```bash +cd apps-extra/openregister +docker compose --profile xwiki up -d xwiki ``` -Article: West zone quiet rules -Tags: zone:west, deskdesk, focus -Body: The west zone is the designated quiet zone. No calls, no - meetings. Keep notifications muted. The kitchen is on the - opposite side of the floor for a reason. + +The first boot takes about a minute — xWiki initialises its schema in the empty Postgres database and unpacks bundled webapps. Wait for the health check to flip to healthy: + +```bash +docker ps --filter name=openregister-xwiki --format '{{.Status}}' +# Up 22 seconds (health: starting) <- still booting +# Up 1 minute (healthy) <- ready ``` -Three short articles. Realistic enough that you'd actually write them in production. The key piece is the **`zone:` tag**: that's what we'll filter by from DeskDesk. +Open `http://localhost:8086`. xWiki redirects to its **Distribution Wizard** because the empty database has no admin user and no flavor. -Why the tutorial walks you through authoring instead of seeding programmatically: in real teams, the wiki articles are *organic* — written by whoever notices the etiquette gap, edited by the next person, archived when the floorplan changes. The integration we build makes them findable from where the user already is, not the other way around. +## Step 0a: Walk the Distribution Wizard -## Step 2: Create an OpenConnector source for xWiki +1. **Continue** past the welcome screen. +2. **Step 1 - Admin user.** Fill in: + ``` + First Name: Desk + Last Name: Admin + Username: admin + Password: admin1234 (any 8+ chars works) + Confirm: admin1234 + Email: admin@deskdesk.local + ``` + Click **Register and login**. You're now signed in as `admin`. +3. **Step 2 - Flavor.** Click **Let the wiki be empty** (the bundled Standard Flavor is a ~150 MB download we don't need for this tutorial). Confirm. +4. **Step 5 - Report** lists the pages created (just `Home` and `XWiki`). Click **Continue** to land on the Main page. -Go to `/apps/openconnector/sources` and create a new source. +The wiki is now bootstrapped. Sanity-check the REST endpoint: -``` -Name: xWiki Knowledge -Type: API -Location: https://your-xwiki.example.com/xwiki/rest -Auth: Basic — username = your wiki user, password = your wiki API token -Test: GET /wikis/xwiki/spaces/DeskDesk%20Knowledge/pages +```bash +curl -u admin:admin1234 -H 'Accept: application/json' \ + http://localhost:8086/rest/wikis/xwiki/spaces | head -c 200 +# {"links":[],"spaces":[{"links":[...,"id":"xwiki:XWiki", ...}]} ``` -The "Test" call should return the three pages you just authored. xWiki's REST API uses XML by default; pass `Accept: application/json` to get JSON back. OpenConnector's source config has a Headers section — add `Accept: application/json` there. +Two things to note about the URL: the REST API lives at `/rest`, **not** `/xwiki/rest` (the latter is the human view path), and basic auth is the default. -Save. The source's id (numeric, in the URL) is what we'll reference from DeskDesk. +## Step 1: Author the knowledge articles -## Step 3: Define the synchronisation +For tutorial purposes we use a dedicated parent space **DeskDeskKnowledge** with one subspace per zone. The folder layout matches the desk's `zone` enum (`east`, `central`, `west`, `north`, `south`) so each desk maps cleanly to its zone's articles. -Sources surface raw API responses; **synchronisations** map them into OpenRegister objects. Create a synchronisation: +You can author the articles via xWiki's UI (Create Page → set parent space → write content). Faster path: drive xWiki's REST API directly. The same payload shape works from cURL, OpenConnector, or any HTTP client. +```bash title="Three short articles, one PUT each" +put() { + curl -s -u admin:admin1234 -X PUT \ + -H "Content-Type: application/xml" \ + -d "$3xwiki/2.1$4" \ + "http://localhost:8086/rest/wikis/xwiki/spaces/DeskDeskKnowledge/spaces/$1/pages/$2" \ + -o /dev/null -w "%{http_code} $1/$2\n" +} +put East Etiquette "East zone etiquette" \ + "The east windows get direct sun 14:00 to 17:00 in summer. Bring a curtain clip from the supplies cabinet on floor 3. Phone calls are fine in this zone, phonebooths are a 30-second walk away." +put Central MeetingDesks "Central zone meeting desks" \ + "Central desks 11 to 14 seat four. The cabling channel under each desk has two HDMI ports and USB-C 100W power. Camera and mic in the ceiling are wired into the in-room Jitsi instance. See the QR code on the desk." +put West QuietRules "West zone quiet rules" \ + "The west zone is the designated quiet zone. No calls, no meetings. Keep notifications muted. The kitchen is on the opposite side of the floor for a reason." ``` -Name: xWiki articles → OpenRegister -Source: xWiki Knowledge (the one you just made) -Source endpoint: /wikis/xwiki/spaces/DeskDesk Knowledge/pages -Target register: deskdesk -Target schema: knowledge_article (you'll add this in step 4) -Mapping: title → name - content → body - tags → tags - id → externalId -Schedule: every 30 minutes (or on demand) + +Each call returns `201`. Browse to `http://localhost:8086/xwiki/bin/view/DeskDeskKnowledge/East/Etiquette` to see the East article in xWiki's standard view. The same content is also fetchable via REST: + +```bash +curl -u admin:admin1234 \ + http://localhost:8086/rest/wikis/xwiki/spaces/DeskDeskKnowledge/spaces/East/pages ``` -Save and run once. You'll see three new `knowledge_article` objects appear in OpenRegister. +Three short articles. Realistic enough that you'd actually write them in production. The key piece is the **subspace name**: that's what we'll pivot on from DeskDesk's side. -## Step 4: Add the knowledge_article schema +Why the tutorial walks you through authoring instead of seeding programmatically: in real teams, the wiki articles are *organic* — written by whoever notices the etiquette gap, edited by the next person, archived when the floorplan changes. The integration we build makes them findable from where the user already is, not the other way around. -Add a fourth schema to `lib/Settings/deskdesk_register.json`: +## Step 2: Add a knowledge_article schema to DeskDesk + +Before we sync from xWiki, give OpenRegister a place to put the articles. Add a fourth schema to `lib/Settings/deskdesk_register.json`: ```json title="lib/Settings/deskdesk_register.json (excerpt)" "knowledge_article": { - "slug": "knowledge-article", - "title": "KnowledgeArticle", - "description": "An xWiki article surfaced in DeskDesk via OpenConnector. Read-only — the source of truth lives in xWiki.", + "slug": "knowledge_article", + "icon": "BookOpenVariantOutline", + "version": "0.1.0", + "title": "Knowledge article", + "description": "An external knowledge article surfaced in DeskDesk via OpenConnector. Read-only — canonical source lives in xWiki.", "type": "object", - "required": ["name"], + "required": ["name", "zone"], "properties": { - "name": { "type": "string", "description": "Article title" }, - "body": { "type": "string", "description": "Article markdown body" }, - "tags": { "type": "array", "items": { "type": "string" } }, - "externalId": { "type": "string", "description": "xWiki page id" }, - "url": { "type": "string", "format": "uri", "description": "Permalink to the article in xWiki" } + "name": { "type": "string" }, + "zone": { "type": "string", "enum": ["north", "south", "east", "west", "central"] }, + "body": { "type": "string" }, + "url": { "type": "string", "format": "uri" }, + "externalId": { "type": "string" } } } ``` -Add it to the register's `schemas` array (`floor`, `desk`, `booking`, `knowledge_article`). Bump versions, re-import. The four schemas are now in place. +Important: the **JSON key** and the **`slug` field must match** (`knowledge_article` on both sides). OpenRegister builds the REST endpoint from the slug, so a mismatch yields `Schema not found: 'knowledge_article'` at fetch time. -## Step 5: Surface articles in CnObjectSidebar +While you're in there, bump the register's `info.version` to `0.3.0` so the import handler picks the new schema up on next run: -The desk-detail page already mounts CnObjectSidebar via the manifest. We want a **"Knowledge" tab** on that sidebar, populated by knowledge articles whose `zone:` tag matches the desk's zone. +```json title="lib/Settings/deskdesk_register.json (info block)" +"info": { + "title": "DeskDesk Register", + "description": "Floors, desks, bookings, and knowledge articles.", + "version": "0.3.0" +} +``` -Two options: +Also seed three articles matching the xWiki content. The seeds make the app demo-able before you wire the sync, and serve as a reference shape for the OpenConnector mapping you'll build in Step 3. -1. **Add a custom sidebar component** registered in the customComponents registry, which renders the article list when the desk's zone resolves to a tag query. -2. **Use the schema's `relatedSources` annotation** (if it exists in your OpenRegister version) to declare the relation declaratively. +```json title="components.objects (excerpt)" +"knowledge-east-etiquette": { + "@self": { "register": "deskdesk", "schema": "knowledge_article", "slug": "knowledge-east-etiquette" }, + "name": "East zone etiquette", + "zone": "east", + "body": "The east windows get direct sun 14:00 to 17:00 in summer...", + "url": "http://localhost:8086/xwiki/bin/view/DeskDeskKnowledge/East/Etiquette", + "externalId": "xwiki:DeskDeskKnowledge.East.Etiquette" +} +``` -For the tutorial we'll use option 1, which is the most portable. Register the sidebar component in `src/main.js`: +Add the `knowledge_article` slug to the small list `SettingsService::SCHEMA_SLUGS` in [lib/Service/SettingsService.php](https://github.com/ConductionNL/deskdesk/blob/main/lib/Service/SettingsService.php) so `/api/settings` exposes its numeric id alongside the other schemas. Same in `src/store/store.js`: -```js title="src/main.js (excerpt)" -import KnowledgeTab from './sidebar/KnowledgeTab.vue' +```js title="src/store/store.js" +const SCHEMAS = ['floor', 'desk', 'booking', 'knowledge_article'] +``` -const customComponents = { - 'knowledge-tab': KnowledgeTab, -} +Reload the configuration so OpenRegister picks up the new schema + seeds: -new Vue({ - /* ... */ - provide: { cnCustomComponents: customComponents }, -}) +```bash +docker exec nextcloud apache2ctl graceful +# then from your Nextcloud session, POST /apps/deskdesk/api/settings/load +# (the deskdesk admin page has a Reload button that does this for you) ``` -And the component itself: +## Step 3: Create an OpenConnector source for xWiki -```vue title="src/sidebar/KnowledgeTab.vue" - +The seeds get the UI working immediately. For *live* data — when an article changes in xWiki you want to see the change in DeskDesk on the next sync — you wire OpenConnector. + +Go to `/apps/openconnector/sources` and create a new source. - +``` +Name: xWiki Knowledge +Type: API +Location: http://openregister-xwiki:8080/rest + (container-to-container hostname; from the host you use http://localhost:8086/rest) +Auth: Basic +Username: admin +Password: admin1234 +Headers: Accept: application/json +Test: GET /wikis/xwiki/spaces/DeskDeskKnowledge/spaces/East/pages ``` -Wire it into the manifest's desks-detail page: +The Test should return the East article. OpenConnector saves the source with a numeric id; we reference it from the synchronisation in the next step. + +## Step 4: Define the synchronisation + +Sources surface raw API responses; **synchronisations** map them into OpenRegister objects. Create a synchronisation: + +``` +Name: xWiki articles → OpenRegister +Source: xWiki Knowledge (the one you just made) +Source endpoint: /wikis/xwiki/spaces/DeskDeskKnowledge/spaces/{zone}/pages + with zone iterated over ['East', 'Central', 'West'] +Target register: deskdesk +Target schema: knowledge_article +Mapping: + title → name + content → body + spaces.last → zone (lowercased) + id → externalId + xwikiAbsoluteUrl → url +Schedule: every 30 minutes +``` + +Save and run once. You'll see three `knowledge_article` objects in OpenRegister whose `externalId` field starts with `xwiki:`. Delete the seed objects you added in Step 2 — they're superseded by the synced ones. + +## Step 5: Surface articles in CnObjectSidebar + +The desk-detail page already mounts CnObjectSidebar. To add a **Knowledge tab** on it, declare a custom tab in the manifest: ```json title="src/manifest.json (excerpt)" { "id": "desks-detail", "route": "/desks/:id", "type": "detail", + "title": "deskdesk.desks.detailTitle", "config": { "register": "deskdesk", "schema": "desk", "sidebar": { "tabs": [ - { "id": "knowledge", "component": "knowledge-tab" } + { "id": "data", "label": "deskdesk.sidebar.data", "widgets": [{ "type": "data" }] }, + { "id": "knowledge", "label": "deskdesk.sidebar.knowledge", "component": "knowledge-tab" }, + { "id": "metadata", "label": "deskdesk.sidebar.metadata", "widgets": [{ "type": "metadata" }] } ] } } } ``` -Build, reload, navigate to `/desks/desk-3-east-12`. The right sidebar shows a "Knowledge" tab. Click it. The "East zone etiquette" article you wrote in xWiki appears, with the body rendered and a link back to the wiki for editing. +The `data` and `metadata` tabs use built-in widget types. The `knowledge` tab points at a custom component `knowledge-tab`, which the App registers via the `customComponents` map. + +Create the tab component itself at `src/views/KnowledgeTab.vue`: + +```vue title="src/views/KnowledgeTab.vue" + + + +``` + +Register the component in `src/App.vue` and pass it to `CnAppRoot` via the `:custom-components` prop: + +```vue title="src/App.vue (excerpt)" + + + +``` + +(See the [reference repo](https://github.com/ConductionNL/deskdesk/blob/main/src/App.vue) for the complete file — App.vue also provides `objectSidebarState` so CnDetailPage's external-sidebar channel works.) + +Build, reload, navigate to a 3-East desk's detail URL. The right sidebar shows three tabs — Data, Knowledge, Metadata. Click **Knowledge**. The "East zone etiquette" article appears, with the body rendered and a link back to the wiki for editing. + +Navigate to a 2-West desk: same tab, different article ("West zone quiet rules"). The zone filter does the matching for free; you didn't write per-desk wiring. ## Step 6: Package the app The release process is mechanical: 1. **Bump the version** in `appinfo/info.xml` (`0.1.0` → `0.4.0`). -2. **Run the production build**: +2. **Run the production build:** ```bash composer install --no-dev --optimize-autoloader npm install --legacy-peer-deps @@ -246,23 +394,41 @@ If you want DeskDesk in the public Nextcloud app store, register at [apps.nextcl You started Part 1 with an empty template. Forty-eight steps later, you have: - A real Nextcloud app with the canonical chassis -- Three OpenRegister schemas with relations and seed data -- A manifest-driven shell with zero hand-rolled views +- Four OpenRegister schemas with relations and seed data +- A manifest-driven shell with two small wrappers (Index, Detail) that go away once the library wires auto-fetch into the default page types - Bookings that appear in NC Calendar via a schema annotation -- Knowledge articles from xWiki that appear in the desk-detail sidebar +- Knowledge articles from xWiki that appear in the desk-detail sidebar via OpenConnector - A versioned, packaged, publishable release -Total Vue you wrote: **one custom sidebar component** (~30 lines). Total PHP: the rename ritual + a path-resolution fix in SettingsService. Everything else is JSON. +Total Vue you wrote: **three small files** (`IndexPageWrapper`, `DetailPageWrapper`, `KnowledgeTab`) — about 100 lines together. Total PHP: the rename ritual + a path-resolution fix in SettingsService + the schema-slug list. Everything else is JSON. That ratio — JSON describing what you want, framework rendering it — is the entire point of the Conduction stack. Schemas drive both backend and frontend. Manifests drive the shell. Schema annotations drive cross-app integrations. You declare; the framework renders. +## Troubleshooting + + + The schema's JSON key and the slug field must match. If you used knowledge-article (hyphen) for one and knowledge_article (underscore) for the other, OpenRegister's URL lookup fails. Pick one, use it both places. + + + + CnPageRenderer forwards page.config and $route.params as raw props. The route param is id but CnDetailPage's prop is objectId. The DetailPageWrapper bridges the names + fetches the object via the store; without the wrapper, CnDetailPage just renders the empty state. + + + + Pick Let the wiki be empty at Step 2. The empty flavor still supports REST + page creation, which is all the tutorial needs. + + + + Set Accept: application/json in the source's Headers section. xWiki defaults to XML for REST responses, but content-negotiates JSON when asked. + + ## Where to next Date: Wed, 13 May 2026 11:54:39 +0200 Subject: [PATCH 08/19] fix(www): point url to www.conduction.nl + document vanity-domain wiring GitHub Pages serves only on the CNAME host (www.conduction.nl); using the bare apex stacked an apex->www hop onto every canonical/og:url/sitemap entry and amplified redirect loops on the connext./commonground. vanity workers. README clarifies that the Cloudflare worker must redirect to the trailing- slash www. target and that Cloudflare SSL must be Full (not Flexible). --- README.md | 4 +++- docusaurus.config.js | 10 +++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c5aa7ca98..fc0bac8cf 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,9 @@ The Docusaurus 3 site for [conduction.nl](https://conduction.nl). Built on `@conduction/docusaurus-preset` for brand defaults; site-specific config lives in [docusaurus.config.js](docusaurus.config.js). -It also serves the `connext.conduction.nl` and `commonground.conduction.nl` vanity entry points (Cloudflare 301-redirects to `/connext` and `/commonground` paths on this site, with locale awareness via `Accept-Language`). +It also serves the `connext.conduction.nl` and `commonground.conduction.nl` vanity entry points (a Cloudflare worker — configured in the dashboard, not in this repo — 301-redirects to the `/connext` and `/commonground` paths on this site, with locale awareness via `Accept-Language`). + +> **Vanity-domain redirect target:** the worker must redirect to `https://www.conduction.nl/connext/` — with the `www.` and the **trailing slash**. The site is built with `url: https://www.conduction.nl` (matching `static/CNAME`) and `trailingSlash: true`. Targeting the bare apex (`conduction.nl`) or the slash-less path (`/connext`) stacks extra GitHub Pages 301s on every request and, if the worker route also matches `www`, can produce a redirect loop. If you see a redirect loop on `connext.conduction.nl`, also check Cloudflare's SSL/TLS mode is **Full** (not "Flexible") — Flexible + GitHub Pages "Enforce HTTPS" is the classic infinite HTTP↔HTTPS loop. ## Local development diff --git a/docusaurus.config.js b/docusaurus.config.js index 5cf06753b..ae1b9bc7f 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -20,7 +20,15 @@ const {createConfig} = require('@conduction/docusaurus-preset'); module.exports = createConfig({ title: 'Conduction', tagline: 'Open-source apps voor de Nextcloud-werkplek.', - url: 'https://conduction.nl', + /* Must match static/CNAME (www.conduction.nl) — GitHub Pages serves + only on the CNAME host and 301-redirects the apex to it. Using the + bare apex here made every canonical/og:url/sitemap entry point at + conduction.nl, adding an apex→www hop on top of whatever the + Cloudflare vanity worker already does — a redirect-loop amplifier + on connext./commonground. (trailingSlash is true, set by the + preset, so the worker must target https://www.conduction.nl/connext/ + — with the www and the trailing slash — not /connext.) */ + url: 'https://www.conduction.nl', baseUrl: '/', organizationName: 'ConductionNL', From 105911935a64b7f7a5ee624bcd277c0ea04906e2 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Wed, 13 May 2026 11:55:01 +0200 Subject: [PATCH 09/19] feat(apps): add 4 apps + 7 sidecars; rework OpenWoo/OpenTalk; site-wide app counts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - /apps grows from 13 to 16: drop OpenWoo (it is a Solution composed of OpenRegister + OpenCatalogi + DocuDesk, not an installable app — moved to a new section on /commonground#openwoo and linked from the connext diagram), add shillinq, openbuilt, doriath (in-development), and app-versions (in-development). - /sidecars/ route: new index + 7 sidecar product pages (openklant, openzaak, valtimo, n8n, keycloak, ollama, open-webui). OpenTalk dropped per product call. New sidecars-catalog.js + sidecars-registry.js mirror the apps-catalog shape. - connext platform diagram: every Conduction item is now a clickable link to its /apps/; App Builder cluster replaces 'COMING SOON' with the real OpenBuilt entry; Doriath added under Solutions; OpenTalk removed from the ExternalAppShelf and prose; remaining 14 external tools route to the Nextcloud app store, the local /sidecars/ page, or the new /apps/openregister#integrations anchor. - openregister.mdx gains an #integrations anchor + new XWiki and Presidio rows so the external-tools shelf has a landing for tools that are not ours and not in the store. - Site-wide app-count is now a derived constant: apps-catalog.js exports APP_COUNT (= Object.keys(PRESENTATION).length), INTEGRATED_COUNT, and ECOSYSTEM_COUNT. connext/index/demo/commonground all import and render these instead of hardcoding 'Twelve' or 'Twenty-four'. - /support: 'Official Nextcloud partner' bullet added to the Service tier. - Download counter fix: new scripts/sync-app-downloads.js mirrors data/app-downloads.json into node_modules/@conduction/data/ so the preset's stats import resolves on a fresh install. Wired into postinstall + prestart + prebuild so it survives npm install and works in both dev and prod. --- package.json | 4 +- scripts/sync-app-downloads.js | 32 ++++ src/data/apps-catalog.js | 48 +++++- src/data/sidecars-catalog.js | 123 +++++++++++++++ src/data/sidecars-registry.js | 46 ++++++ src/pages/about.mdx | 2 +- src/pages/apps/app-versions.mdx | 46 ++++++ src/pages/apps/doriath.mdx | 46 ++++++ src/pages/apps/openbuilt.mdx | 95 +++++++++++ src/pages/apps/openregister.mdx | 19 ++- src/pages/apps/openwoo.mdx | 252 ------------------------------ src/pages/apps/shillinq.mdx | 95 +++++++++++ src/pages/commonground.mdx | 42 ++++- src/pages/connext.mdx | 82 +++++----- src/pages/demo.mdx | 3 +- src/pages/index.mdx | 5 +- src/pages/sidecars.mdx | 80 ++++++++++ src/pages/sidecars/keycloak.mdx | 47 ++++++ src/pages/sidecars/n8n.mdx | 47 ++++++ src/pages/sidecars/ollama.mdx | 47 ++++++ src/pages/sidecars/open-webui.mdx | 47 ++++++ src/pages/sidecars/openklant.mdx | 47 ++++++ src/pages/sidecars/openzaak.mdx | 47 ++++++ src/pages/sidecars/valtimo.mdx | 47 ++++++ src/pages/support.mdx | 1 + 25 files changed, 1035 insertions(+), 315 deletions(-) create mode 100644 scripts/sync-app-downloads.js create mode 100644 src/data/sidecars-catalog.js create mode 100644 src/data/sidecars-registry.js create mode 100644 src/pages/apps/app-versions.mdx create mode 100644 src/pages/apps/doriath.mdx create mode 100644 src/pages/apps/openbuilt.mdx delete mode 100644 src/pages/apps/openwoo.mdx create mode 100644 src/pages/apps/shillinq.mdx create mode 100644 src/pages/sidecars.mdx create mode 100644 src/pages/sidecars/keycloak.mdx create mode 100644 src/pages/sidecars/n8n.mdx create mode 100644 src/pages/sidecars/ollama.mdx create mode 100644 src/pages/sidecars/open-webui.mdx create mode 100644 src/pages/sidecars/openklant.mdx create mode 100644 src/pages/sidecars/openzaak.mdx create mode 100644 src/pages/sidecars/valtimo.mdx diff --git a/package.json b/package.json index e2e5bfc86..f1e5e4a23 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,10 @@ "description": "conduction.nl, the Conduction company hub. Hosts apps, solutions, partners, plus ConNext and Common Ground vanity landings.", "scripts": { "docusaurus": "docusaurus", + "postinstall": "node scripts/sync-app-downloads.js", + "prestart": "node scripts/sync-app-downloads.js", "start": "docusaurus start", - "prebuild": "python3 .github/scripts/app_downloads.py", + "prebuild": "python3 .github/scripts/app_downloads.py && node scripts/sync-app-downloads.js", "build": "docusaurus build", "swizzle": "docusaurus swizzle", "deploy": "docusaurus deploy", diff --git a/scripts/sync-app-downloads.js b/scripts/sync-app-downloads.js new file mode 100644 index 000000000..3b59d9ab2 --- /dev/null +++ b/scripts/sync-app-downloads.js @@ -0,0 +1,32 @@ +#!/usr/bin/env node +/** + * Mirrors data/app-downloads.json into node_modules/@conduction/data/ so + * @conduction/docusaurus-preset's stats import resolves on a freshly- + * installed tree. The preset's src/data/app-downloads.js requires the + * file at `../../../data/app-downloads.json` relative to its own + * location, which targets node_modules/@conduction/data/ — a path the + * npm install layout does not create on its own. + * + * Hooked into postinstall + prestart + prebuild so the file is in place + * for both dev and prod builds. Safe to re-run; idempotent on contents. + */ +const fs = require('fs'); +const path = require('path'); + +const SRC = path.join(__dirname, '..', 'data', 'app-downloads.json'); +const DST_DIR = path.join(__dirname, '..', 'node_modules', '@conduction', 'data'); +const DST = path.join(DST_DIR, 'app-downloads.json'); + +if (!fs.existsSync(SRC)) { + console.warn(`[sync-app-downloads] source missing at ${SRC}; skipping`); + process.exit(0); +} + +try { + fs.mkdirSync(DST_DIR, { recursive: true }); + fs.copyFileSync(SRC, DST); + console.log(`[sync-app-downloads] copied ${SRC} → ${DST}`); +} catch (err) { + console.warn(`[sync-app-downloads] copy failed: ${err.message}`); + process.exit(0); +} diff --git a/src/data/apps-catalog.js b/src/data/apps-catalog.js index b05f6ac96..a4bc40e66 100644 --- a/src/data/apps-catalog.js +++ b/src/data/apps-catalog.js @@ -68,13 +68,6 @@ const PRESENTATION = { categories: ['Dashboards'], icon: , }, - openwoo: { - name: 'OpenWoo', - tagline: 'WOO-compliant publication flow. Active disclosure, queryable archive, citation-stable URLs.', - href: '/apps/openwoo', - categories: ['Processes'], - icon: , - }, zaakafhandelapp: { name: 'ZaakAfhandelApp', tagline: 'Citizen-facing case-status portal. ZGW APIs, archief koppelvlakken, audit trail.', @@ -124,11 +117,50 @@ const PRESENTATION = { categories: ['Documents'], icon: , }, + shillinq: { + name: 'Shillinq', + tagline: 'Business administration on Nextcloud. Invoices, contracts, procurement on shared registers.', + href: '/apps/shillinq', + categories: ['Processes'], + icon: , + }, + openbuilt: { + name: 'OpenBuilt', + tagline: 'Citizen-developer app builder. Compose Nextcloud apps from registers, connectors, workflows, no PHP.', + href: '/apps/openbuilt', + categories: ['Processes'], + icon: , + }, + doriath: { + name: 'Doriath', + tagline: 'Self-hosted password and secrets vault. Per-user, per-team, audited. In development.', + href: '/apps/doriath', + categories: ['Connectors'], + icon: , + }, + 'app-versions': { + name: 'App Versions', + tagline: 'Nextcloud app scaffold. Template repo for new apps, prefilled and CI-ready. In development.', + href: '/apps/app-versions', + categories: ['Data'], + icon: , + }, }; /* Categories the website filters by, in the order the chips render. */ export const APP_CATEGORIES = ['All', 'Data', 'Processes', 'Connectors', 'Documents', 'AI', 'Dashboards']; +/* Site-wide app-count constants — the single source of truth for "we ship N apps". + APP_COUNT is the size of the PRESENTATION map (every Conduction app, including + in-development ones). Use this in copy instead of hardcoding a digit or word, + so the headline number stays in sync with the registry. INTEGRATED_COUNT is the + number of third-party tools we surface in the External-tools shelf on /connext; + ECOSYSTEM_COUNT is the headline "apps in the diagram" total used as the page + title. Update INTEGRATED_COUNT whenever the shelf changes. */ +export const APP_COUNT = Object.keys(PRESENTATION).length; +export const INTEGRATED_COUNT = 14; +export const ECOSYSTEM_COUNT = APP_COUNT + INTEGRATED_COUNT; + /* Pick a status label from the version string. The Nextcloud app store leaves the latest_version as-is, so "0.7.9-beta.8" reads as beta and "1.6.0" reads as stable. Falls back to GitHub's tag when @@ -192,7 +224,7 @@ export function getApps() { return out; } -/* Convenience: the totals strip ("12 apps in the store · 22,400 installs"). */ +/* Convenience: the totals strip ("N apps in the store · M installs"). */ export function getCatalogTotals() { const apps = getApps(); const installs = apps.reduce((n, a) => n + (a.downloads || 0), 0); diff --git a/src/data/sidecars-catalog.js b/src/data/sidecars-catalog.js new file mode 100644 index 000000000..36af885db --- /dev/null +++ b/src/data/sidecars-catalog.js @@ -0,0 +1,123 @@ +/** + * sidecars-catalog.js + * ------------------- + * Hand-curated display metadata for ExApp sidecars. Mirrors the shape + * of apps-catalog.js but lives in its own file so the /apps grid + * stays focused on apps we build, not apps we wrap. + * + * Keys are the GitHub repo slug (matches `apps[].id` in + * `data/app-downloads.json`). URLs are mirrored in the sidecars- + * registry shipped from `@conduction/docusaurus-preset/data/sidecars- + * registry`; when you add or rename a sidecar update both. + * + * Adding a new sidecar: drop a row into PRESENTATION below plus a + * product page at `src/pages/sidecars/.mdx`. + */ + +import React from 'react'; +import downloadsJson from '../../data/app-downloads.json'; + +const PRESENTATION = { + openklant: { + name: 'OpenKlant', + tagline: 'Sidecar that wraps the OpenKlant citizen-contact register. ZGW-compatible, installed from Nextcloud.', + href: '/sidecars/openklant', + categories: ['Registers'], + icon: , + }, + openzaak: { + name: 'OpenZaak', + tagline: 'Sidecar for the OpenZaak case-management API. Koppelvlakken voor ZGW, vanuit Nextcloud.', + href: '/sidecars/openzaak', + categories: ['Registers'], + icon: , + }, + valtimo: { + name: 'Valtimo', + tagline: 'Sidecar for the Valtimo BPMN engine. Workflow orchestration alongside the registers.', + href: '/sidecars/valtimo', + categories: ['Workflow'], + icon: , + }, + 'n8n-nextcloud': { + name: 'n8n', + tagline: 'Sidecar for n8n. Visual workflow automation, installed as a Nextcloud app.', + href: '/sidecars/n8n', + categories: ['Workflow'], + icon: , + }, + keycloak: { + name: 'Keycloak', + tagline: 'Sidecar for Keycloak. Self-hosted identity and single sign-on next to Nextcloud.', + href: '/sidecars/keycloak', + categories: ['Identity'], + icon: , + }, + ollama: { + name: 'Ollama', + tagline: 'Sidecar for Ollama. Local LLM inference served from the Nextcloud workspace.', + href: '/sidecars/ollama', + categories: ['AI'], + icon: , + }, + 'open-webui': { + name: 'Open WebUI', + tagline: 'Sidecar for Open WebUI. Chat-style UI for local models, inside Nextcloud.', + href: '/sidecars/open-webui', + categories: ['AI'], + icon: , + }, +}; + +export const SIDECAR_CATEGORIES = ['All', 'Registers', 'Workflow', 'Identity', 'AI']; + +function statusFor(record) { + const ver = (record.store && record.store.latest_version) || (record.github && record.github.latest_release) || ''; + const v = ver.toLowerCase(); + if (!ver) return 'COMING SOON'; + if (v.includes('beta') || v.includes('alpha') || v.includes('rc')) return 'BETA'; + if (v.startsWith('0.') || v.startsWith('v0.')) return 'BETA'; + return 'STABLE'; +} + +function versionLabel(record) { + const raw = (record.store && record.store.latest_version) || (record.github && record.github.latest_release) || ''; + const stripped = raw.replace(/^v/, '').replace(/-(beta|alpha|rc).*$/i, ''); + const short = stripped.split('.').slice(0, 2).join('.'); + const dl = record.github && record.github.downloads; + const dlLabel = dl && dl > 0 ? `${dl.toLocaleString('en-US')} installs` : null; + return [short && `v${short}`, dlLabel].filter(Boolean).join(' · '); +} + +export function getSidecars() { + const seen = new Set(); + const out = []; + for (const record of downloadsJson.apps) { + const meta = PRESENTATION[record.id]; + if (!meta) continue; + if (seen.has(record.id)) continue; + seen.add(record.id); + out.push({ + ...meta, + status: statusFor(record), + version: versionLabel(record), + downloads: (record.github && record.github.downloads) || 0, + }); + } + for (const id of Object.keys(PRESENTATION)) { + if (seen.has(id)) continue; + out.push({ + ...PRESENTATION[id], + status: 'COMING SOON', + version: '', + downloads: 0, + }); + } + const statusRank = {STABLE: 0, BETA: 1, 'COMING SOON': 2}; + out.sort((a, b) => { + const r = (statusRank[a.status] ?? 9) - (statusRank[b.status] ?? 9); + if (r !== 0) return r; + return (b.downloads || 0) - (a.downloads || 0); + }); + return out; +} diff --git a/src/data/sidecars-registry.js b/src/data/sidecars-registry.js new file mode 100644 index 000000000..f5fa43c9e --- /dev/null +++ b/src/data/sidecars-registry.js @@ -0,0 +1,46 @@ +/** + * @conduction/docusaurus-preset/data/sidecars-registry + * + * URL-only registry of every public Conduction ExApp sidecar. + * Sidecars are Nextcloud-installable wrappers around an existing + * third-party service (n8n, Keycloak, Ollama, the Common Ground + * registers). They live next to the apps-registry, not inside it, + * because they are not apps we build, they are apps we wrap. + * + * The shape mirrors apps-registry so cross-link components can treat + * a sidecar and an app uniformly: + * + * 1. /sidecars/ product detail page + * 2. https://docs.conduction.nl/ documentation site + * 3. /academy?app= academy posts filtered by app + * + * Adding a new sidecar: add an entry here plus a product page at + * conduction-website/src/pages/sidecars/.mdx + */ + +export const SIDECARS_REGISTRY = { + openklant: {slug: 'openklant', name: 'OpenKlant', productHref: '/sidecars/openklant', docsHref: 'https://docs.conduction.nl/openklant', academyHref: '/academy?app=openklant'}, + openzaak: {slug: 'openzaak', name: 'OpenZaak', productHref: '/sidecars/openzaak', docsHref: 'https://docs.conduction.nl/openzaak', academyHref: '/academy?app=openzaak'}, + valtimo: {slug: 'valtimo', name: 'Valtimo', productHref: '/sidecars/valtimo', docsHref: 'https://docs.conduction.nl/valtimo', academyHref: '/academy?app=valtimo'}, + 'n8n-nextcloud': {slug: 'n8n-nextcloud', name: 'n8n', productHref: '/sidecars/n8n', docsHref: 'https://docs.conduction.nl/n8n', academyHref: '/academy?app=n8n'}, + keycloak: {slug: 'keycloak', name: 'Keycloak', productHref: '/sidecars/keycloak', docsHref: 'https://docs.conduction.nl/keycloak', academyHref: '/academy?app=keycloak'}, + ollama: {slug: 'ollama', name: 'Ollama', productHref: '/sidecars/ollama', docsHref: 'https://docs.conduction.nl/ollama', academyHref: '/academy?app=ollama'}, + 'open-webui': {slug: 'open-webui', name: 'Open WebUI', productHref: '/sidecars/open-webui', docsHref: 'https://docs.conduction.nl/open-webui', academyHref: '/academy?app=open-webui'}, +}; + +export const SIDECAR_SLUGS = Object.keys(SIDECARS_REGISTRY); + +export const SIDECAR_LABELS = SIDECAR_SLUGS.reduce((acc, slug) => { + acc[slug] = SIDECARS_REGISTRY[slug].name; + return acc; +}, {}); + +/** Resolve a slug to its sidecar registry entry; returns undefined for unknown slugs. */ +export function getSidecar(slug) { + return SIDECARS_REGISTRY[slug]; +} + +/** Resolve an array of slugs, dropping any that aren't in the registry. */ +export function getSidecars(slugs = []) { + return slugs.map(getSidecar).filter(Boolean); +} diff --git a/src/pages/about.mdx b/src/pages/about.mdx index 59870923b..a135ab914 100644 --- a/src/pages/about.mdx +++ b/src/pages/about.mdx @@ -1,6 +1,6 @@ --- title: About, Conduction -description: Conduction is a small Dutch open-source product company. Twelve apps, one workspace, one team in Amsterdam. +description: Conduction is a small Dutch open-source product company. A workspace of open-source apps for Nextcloud, one team in Amsterdam. hide_table_of_contents: true --- diff --git a/src/pages/apps/app-versions.mdx b/src/pages/apps/app-versions.mdx new file mode 100644 index 000000000..2b218f3f0 --- /dev/null +++ b/src/pages/apps/app-versions.mdx @@ -0,0 +1,46 @@ +--- +title: App Versions, Conduction +description: Nextcloud app scaffold and version registry. In development. +hide_table_of_contents: true +--- + +import {DetailHero, Section, SectionHead, CtaBanner} from '@conduction/docusaurus-preset/components'; + +A template repo and version registry for new Nextcloud apps. Prefilled scaffolds, CI-ready, conformance-checked against our app conventions. In active development.} + secondaryCta={{label: 'Follow progress', href: 'https://github.com/ConductionNL/app-versions'}} + tertiaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/app-versions'}} + iconColor="var(--c-blue-cobalt)" + icon={} +/> + +
+ +
+ +
+ +
+ +App Versions is the entry point for partners and integrators. Reach out and we'll point you at the right starting branch.} + primaryCta={{label: 'Talk to a partner', href: '/partners'}} + secondaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/app-versions'}} +/> diff --git a/src/pages/apps/doriath.mdx b/src/pages/apps/doriath.mdx new file mode 100644 index 000000000..19f61a442 --- /dev/null +++ b/src/pages/apps/doriath.mdx @@ -0,0 +1,46 @@ +--- +title: Doriath, Conduction +description: Self-hosted password and secrets vault for Nextcloud. In development. +hide_table_of_contents: true +--- + +import {DetailHero, Section, SectionHead, CtaBanner} from '@conduction/docusaurus-preset/components'; + +A self-hosted password and secrets vault for Nextcloud. Per-user vaults, team-shared secrets, an audit trail. In active development. Public beta later in 2026.} + secondaryCta={{label: 'Follow progress', href: 'https://github.com/ConductionNL/doriath'}} + tertiaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/doriath'}} + iconColor="var(--c-blue-cobalt)" + icon={} +/> + +
+ +
+ +
+ +
+ +Doriath is in active development. Reach out and we'll keep you in the loop on the first public beta.} + primaryCta={{label: 'Talk to a partner', href: '/partners'}} + secondaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/doriath'}} +/> diff --git a/src/pages/apps/openbuilt.mdx b/src/pages/apps/openbuilt.mdx new file mode 100644 index 000000000..58e10a9c9 --- /dev/null +++ b/src/pages/apps/openbuilt.mdx @@ -0,0 +1,95 @@ +--- +title: OpenBuilt, Conduction +description: Citizen-developer app builder for Nextcloud. Compose apps from registers, connectors, workflows, no PHP. +hide_table_of_contents: true +--- + +import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, PairCard, CtaBanner, AppCrossLinks} from '@conduction/docusaurus-preset/components'; + +An app builder for citizen developers on Nextcloud. Compose your own app from OpenRegister schemas, OpenConnector APIs, Procest workflows, and DocuDesk documents. No PHP, no scaffolding.} + primaryCta={{label: 'Install from app store', href: '/install'}} + secondaryCta={{label: 'Read the docs', href: 'https://docs.conduction.nl/openbuilt'}} + tertiaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/openbuilt'}} + iconColor="var(--c-blue-cobalt)" + icon={} +/> + +
+
+
+ + + } + title="Visuele app-designer." + > + Kies een register, voeg pagina's toe, koppel formulieren aan schema's. Een manifest beschrijft de app; OpenBuilt rendert hem. + + } + title="Workflows declaratief, geen service-code." + > + State machines, aggregaties, notificaties en berekeningen leef in de schema-metadata. Geen PHP-service per app, conform ADR-031. + + } + title="Virtuele apps eerst, echte app als optie." + > + Virtuele apps draaien in de OpenBuilt-shell. Klaar voor productie? Exporteer naar een echte Nextcloud-app met eigen ID en namespace. + + +
+ +
+
+ +
+ + + } + name="OpenRegister" + why="Elk virtueel app-manifest en alle objecten zijn OpenRegister-rijen." + /> + } + name="OpenConnector" + why="Externe API's en bestand-bronnen koppelen zonder code." + /> + } + name="Procest" + why="Workflows en goedkeurings-stappen voor virtual-app processen." + /> + +
+ +OpenBuilt geeft niet-technische gebruikers de bouwblokken om eigen apps samen te stellen. Schema's, koppelvlakken, workflows, allemaal in handen van wie het probleem kent.} + primaryCta={{label: 'Install from app store', href: '/install'}} + secondaryCta={{label: 'Talk to a partner', href: '/partners'}} +/> diff --git a/src/pages/apps/openregister.mdx b/src/pages/apps/openregister.mdx index c8b4ecfa2..194074c51 100644 --- a/src/pages/apps/openregister.mdx +++ b/src/pages/apps/openregister.mdx @@ -170,10 +170,11 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, FeatureGrid, ]} /> +
, }, + { + id: 'xwiki', + title: 'XWiki', + summary: 'Embed a live register query into any XWiki page through OpenConnector. Procedure docs stay current automatically. The article and the data live one click apart.', + cta: {label: 'See the XWiki integration', href: 'https://docs.conduction.nl/openregister/xwiki'}, + panel: , + }, { id: 'llms', title: 'LLMs (Claude, Mistral, Ollama)', - summary: 'Plain-language search across every register. The LLM rewrites your question into a structured query, OpenRegister returns typed rows with citation-stable IDs. Every call audits.', + summary: 'Plain-language search across every register. The LLM rewrites your question into a structured query, OpenRegister returns typed rows with citation-stable IDs. Claude and Mistral run as cloud adapters through OpenConnector; Ollama runs as a sidecar on your own hardware. Every call audits.', cta: {label: 'See the semantic-search adapter', href: 'https://docs.conduction.nl/openregister/llm-search'}, panel: , }, + { + id: 'presidio', + title: 'Presidio (PII redaction)', + summary: 'Microsoft Presidio detects personal data in any register row or attached document. DocuDesk runs it before publication; OpenConnector runs it before forwarding to a cloud LLM. The register keeps the source, the outside world only sees the redacted version.', + cta: {label: 'See the redaction pipeline', href: 'https://docs.conduction.nl/openregister/presidio'}, + panel: , + }, ]} /> diff --git a/src/pages/apps/openwoo.mdx b/src/pages/apps/openwoo.mdx deleted file mode 100644 index 10bfe8a8e..000000000 --- a/src/pages/apps/openwoo.mdx +++ /dev/null @@ -1,252 +0,0 @@ ---- -title: OpenWoo, Conduction -description: WOO-compliant publication flow on Nextcloud. Active disclosure, queryable archive, citation-stable URLs. -hide_table_of_contents: true ---- - -import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, PairCard, CtaBanner, AppMock, RotatingCards, WidgetShelf, Showcase, AgentTrace, AppCrossLinks} from '@conduction/docusaurus-preset/components'; - -Wet open overheid (Woo)-compliant publication flow on your Nextcloud. Eleven categories, audit log, citation-stable URLs, federation to data.overheid.nl. Citizens find what they need without filing a Woo request.} - primaryCta={{label: 'Install from app store', href: '/install'}} - secondaryCta={{label: 'Read the docs', href: 'https://docs.conduction.nl/openwoo'}} - tertiaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/OpenWooApp'}} - iconColor="var(--c-blue-cobalt)" - icon={} - illustration={} -/> - -
-
-
- - - } - title="Eleven Woo categories, pre-configured." - > - Laws and binding regulations, organisation and working methods, council documents, agreements, annual plans and reports, subsidies, complaints, investigations, decisions, datasets, meeting documents. All as typed registers, all in citizen-search. - - } - title="Federation to data.overheid.nl." - > - Every published Woo record is automatically indexed on data.overheid.nl. DCAT-AP-NL metadata out of the box. - - } - title="Citation-stable URLs." - > - Every Woo record gets a permanent identifier the moment it is first published. Parliamentary questions, legal dossiers, and journalistic articles keep working after a reorganisation or platform migration. - - } - title="Audit trail per publication." - > - Every disclosure, edit, withdrawal, and access leaves a tamper-evident trail. The evidence pack you need for an audit, complaint, or appeal is ready. - - } - title="Anonymisation upfront via DocuDesk." - > - DocuDesk strips PII from inbound documents before they are published. Microsoft Presidio under the hood, tuned for Dutch personal terminology. - - -
- -
-
- -, - }, - { - tone: 'lavender', - eyebrow: 'Citation-stable', - title: 'URLs that survive a reorganisation.', - summary: 'Every Woo record gets a permanent identifier the moment it is first published. Parliamentary questions, legal dossiers, journalistic articles keep working after platform migrations.', - cta: {label: 'See OpenRegister', href: '/apps/openregister'}, - panel: , - }, - { - tone: 'workspace', - eyebrow: 'Federated', - title: 'Indexed on data.overheid.nl automatically.', - summary: 'Every published record becomes a DCAT-AP-NL entry on data.overheid.nl. Citizens find it without filing a Woo request, journalists query it without scraping.', - cta: {label: 'See OpenCatalogi', href: '/apps/opencatalogi'}, - panel: , - }, - ]} -/> - - - {[ - {tone: 'var(--c-orange-knvb)', l1: '70%', l2: '50%', stage: 'redacting'}, - {tone: 'var(--c-lavender-300)', l1: '60%', l2: '45%', stage: 'review'}, - {tone: 'var(--c-red-vermillion)', l1: '65%', l2: '40%', stage: 'overdue'}, - {tone: 'var(--c-mint-500)', l1: '75%', l2: '50%', stage: 'ready'}, - {tone: 'var(--c-cobalt-300)', l1: '55%', l2: '35%', stage: 'intake'}, - ].map((row, i) => ( -
- -
-
-
-
-
{row.stage}
-
- ))} -
- ), - }, - { - title: 'Deadlines this week', - desc: 'Statutory four-week response window. Pre-warning before the day, escalation after.', - panel: ( -
-
-
4
-
this week
-
- {[{w: '70%', accent: true}, {w: '60%'}, {w: '55%'}, {w: '50%'}].map((row, i) => ( -
- -
-
-
- ))} -
- ), - }, - { - title: 'Recently published', - desc: 'Last disclosures pushed live, with category and citation ID. One click back to the published record.', - panel: ( -
- {['Convenanten · 2026-Q1', 'Raadsstukken · maart', 'Onderzoeken · TUI', 'Beschikkingen · subsidie'].map((_, i) => ( -
- -
-
-
-
-
WOO-2026-{120 + i}
-
- ))} -
- ), - }, - ]} -/> - -, - }, - { - id: 'docudesk-redact', - title: 'DocuDesk redaction', - summary: 'Inbound documents pass through Presidio plus an LLM second pass before publication. PII gets stripped, the cleaned version goes to the public register, the original stays sealed in archive with a tamper-evident link.', - cta: {label: 'See the privacy pipeline', href: 'https://docs.conduction.nl/openwoo/redaction'}, - panel: , - }, - { - id: 'data-overheid', - title: 'Federation to data.overheid.nl', - summary: 'Every published record becomes a DCAT-AP-NL entry on data.overheid.nl. OpenConnector handles the OAI-PMH push; citizens find the disclosure through the national portal without filing a separate Woo request.', - cta: {label: 'See the federation guide', href: 'https://docs.conduction.nl/openwoo/federation'}, - panel: , - }, - ]} -/> - -
- - - } - name="OpenRegister" - why="Each Woo category becomes a typed register. Audit log, citation-stable IDs, retention." - /> - } - name="OpenCatalogi" - why="The searchable publication layer. Citizens land at jouwgemeente.nl/woo." - /> - } - name="DocuDesk" - why="Anonymises documents before publication. Generates decisions per NLDS templates." - /> - -
- -Drop three apps into your Nextcloud, configure the eleven categories, and your publication flow runs the same afternoon.} - primaryCta={{label: 'Install from app store', href: '/install'}} - secondaryCta={{label: 'Talk to a partner', href: '/partners'}} -/> diff --git a/src/pages/apps/shillinq.mdx b/src/pages/apps/shillinq.mdx new file mode 100644 index 000000000..45a64ea12 --- /dev/null +++ b/src/pages/apps/shillinq.mdx @@ -0,0 +1,95 @@ +--- +title: Shillinq, Conduction +description: Business administration on Nextcloud. Invoices, contracts, procurement on shared registers. +hide_table_of_contents: true +--- + +import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, PairCard, CtaBanner, AppCrossLinks} from '@conduction/docusaurus-preset/components'; + +Business administration on Nextcloud. Invoices, contracts, procurement on shared registers. Built for the MKB that already runs Nextcloud and wants its finance, sales, and procurement in the same workspace.} + primaryCta={{label: 'Install from app store', href: '/install'}} + secondaryCta={{label: 'Read the docs', href: 'https://docs.conduction.nl/shillinq'}} + tertiaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/shillinq'}} + iconColor="var(--c-blue-cobalt)" + icon={} +/> + +
+
+
+ + + } + title="Facturatie en debiteuren." + > + Genereer facturen uit sjablonen. Stuur ze direct uit Nextcloud Mail. Volg openstaande bedragen in MyDash. + + } + title="Contracten met audit trail." + > + Versioned contracten in OpenRegister. Zie wie wat heeft getekend, wanneer een verlenging vervalt, welke voorwaarden gelden. + + } + title="Inkoop met goedkeurings-flow." + > + PO-aanvragen, goedkeuring per drempelbedrag, leveranciersregister. Voor de bestelling de hand op de knip houden, achteraf de factuur erbij. + + +
+ +
+
+ +
+ + + } + name="OpenRegister" + why="Facturen, contracten, inkooporders als typed registers." + /> + } + name="DocuDesk" + why="Genereert facturen, offertes en contracten uit sjablonen." + /> + } + name="MyDash" + why="Cash-flow, omzet, top-debiteuren als widgets op het dashboard." + /> + +
+ +Shillinq vervangt losse boekhoudpakketten en CRM-tools door één bedrijfsadministratie op Nextcloud. Open-source, op je eigen data.} + primaryCta={{label: 'Install from app store', href: '/install'}} + secondaryCta={{label: 'Talk to a partner', href: '/partners'}} +/> diff --git a/src/pages/commonground.mdx b/src/pages/commonground.mdx index 7402bad57..7bcd64e05 100644 --- a/src/pages/commonground.mdx +++ b/src/pages/commonground.mdx @@ -5,6 +5,7 @@ hide_table_of_contents: true --- import {Hero, StatsStrip, Section, SectionHead, AppsPreview, PairRow, PairCard, CtaBanner, Pill, Showcase, AppMock, CommonGroundPlus, ManagedCommonGround} from '@conduction/docusaurus-preset/components'; +import {APP_COUNT} from '@site/src/data/apps-catalog'; import {totalDownloads, formatDownloads} from '@conduction/docusaurus-preset/data'; import {totalPartners} from '@site/src/data/partners-catalog'; @@ -18,7 +19,7 @@ import {totalPartners} from '@site/src/data/partners-catalog'; /> apps in the ecosystem,
all open-source }, + { value: String(APP_COUNT), label: <>apps in the ecosystem,
all open-source }, { value: '2 min', label: <>from app store install
to working solution }, { value: '€0', label: <>for the apps,
support is optional }, { value: totalPartners, label: <>certified and partner
implementers }, @@ -31,12 +32,12 @@ import {totalPartners} from '@site/src/data/partners-catalog'; lede={<>Start with what your WOO coordinator, GGU contact, or procurement committee is asking for today. All open-source, GDPR-compliant, free to install from the Nextcloud app store.} apps={[ { - name: 'OpenWoo', - tagline: 'WOO publication flow. Active disclosure, queryable archive, automatic redaction of personal data.', + name: 'DocuDesk', + tagline: 'Auto-classify, anonymise, sign, and template documents. Powers the WOO publication flow end to end.', status: 'STABLE', - version: 'v1.4 · NL', - href: '/apps/openwoo', - icon: , + version: 'v1.x · NL · EN', + href: '/apps/docudesk', + icon: , }, { name: 'OpenCatalogi', @@ -58,6 +59,35 @@ import {totalPartners} from '@site/src/data/partners-catalog'; seeAll={{label: 'Browse all apps', href: '/apps'}} /> +
+ OpenWoo is geen aparte app, het is een solution die OpenRegister, OpenCatalogi en DocuDesk samenbrengt voor de Wet open overheid. Eén Woo-categorieenboom op typed registers, automatische redactie van persoonsgegevens, federatie naar jouwgemeente.nl/openwoo. Geen procurement-traject, geen consultancy-uren, één installatie.} + /> + + } + name="OpenRegister" + why="Woo-categorieen, publicaties, retentie-termijnen als typed registers." + /> + } + name="OpenCatalogi" + why="Publiceert het archief naar jouwgemeente.nl/openwoo met federation." + /> + } + name="DocuDesk" + why="Auto-redactie van persoonsgegevens voor publicatie. Audit trail per document." + /> + +
+
Twelve open-source apps · one Nextcloud} + eyebrow={<>{APP_COUNT} open-source apps · one Nextcloud} title={<>Install your stack.
In two minutes.} - lede={<>Twelve open-source apps that plug into Nextcloud. Catalogs, registers, integrations, document handling, pick what you need from the app store and you're working today. No methodology workshop. No lock-in.} + lede={<>{APP_COUNT} open-source apps that plug into Nextcloud. Catalogs, registers, integrations, document handling, pick what you need from the app store and you're working today. No methodology workshop. No lock-in.} primaryCta={{label: <>Install from Nextcloud app store, href: '/apps'}} secondaryCta={{label: 'Run a local demo', href: '/demo'}} tertiaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL'}} @@ -25,7 +26,7 @@ import {totalPartners} from '@site/src/data/partners-catalog'; apps in the ecosystem,
all open-source }, + { value: String(APP_COUNT), label: <>apps in the ecosystem,
all open-source }, { value: '2 min', label: <>from app store install
to working solution }, { value: '€0', label: <>for the apps,
support is optional }, { value: totalPartners, label: <>certified and partner
implementers }, @@ -34,7 +35,7 @@ import {totalPartners} from '@site/src/data/partners-catalog'; Five components.
Twenty-four apps. One Nextcloud workspace.} + title={<>Five components.
{ECOSYSTEM_COUNT} apps. One Nextcloud workspace.} lede={<>Every ConNext app plugs into one of five categories around the Nextcloud workspace, files, mail, calendar, talk, office. All of them run on the platform your team already trusts. Hover any app to see what it does.} > }, ]}, {position: 'top-left', family: 'core', label: 'Technical\nCore', items: [ - {name: 'OpenRegister', meta: 'Data', desc: 'Schema-driven object store with audit trail. The data foundation underneath every workspace app.', icon: }, - {name: 'OpenCatalogi', meta: 'Catalog', desc: 'Federated data catalogue. Datasets, APIs, software inventory.', icon: }, - {name: 'OpenConnector', meta: 'Integrate', desc: 'API gateway, REST, SOAP, GraphQL, webhooks.', icon: }, - {name: 'DocuDesk', meta: 'Docs', desc: 'Generate, anonymise, sign, and template documents.', icon: }, - {name: 'NLDesign Theme', meta: 'Theme', desc: 'NL Design System tokens applied across every Conduction app.', icon: }, - {name: 'MyDash', meta: 'Dashboard', desc: 'Dashboard builder with role-based access.', icon: }, + {name: 'OpenRegister', meta: 'Data', href: '/apps/openregister', desc: 'Schema-driven object store with audit trail. The data foundation underneath every workspace app.', icon: }, + {name: 'OpenCatalogi', meta: 'Catalog', href: '/apps/opencatalogi', desc: 'Federated data catalogue. Datasets, APIs, software inventory.', icon: }, + {name: 'OpenConnector', meta: 'Integrate', href: '/apps/openconnector', desc: 'API gateway, REST, SOAP, GraphQL, webhooks.', icon: }, + {name: 'DocuDesk', meta: 'Docs', href: '/apps/docudesk', desc: 'Generate, anonymise, sign, and template documents.', icon: }, + {name: 'NLDesign Theme', meta: 'Theme', href: '/apps/nldesign', desc: 'NL Design System tokens applied across every Conduction app.', icon: }, + {name: 'MyDash', meta: 'Dashboard', href: '/apps/mydash', desc: 'Dashboard builder with role-based access.', icon: }, ]}, {position: 'top-right', family: 'solutions', label: 'Solutions', items: [ - {name: 'OpenWoo', meta: 'WOO', desc: 'WOO-compliant publication flow. Active disclosure, queryable archive.', icon: }, - {name: 'ZaakAfhandelApp', meta: 'Zaken', desc: 'Citizen-facing case-status portal.', icon: }, - {name: 'Anonimiseren', meta: 'Privacy', desc: 'Privacy-by-design pipeline. Auto-redact PII before publication.', icon: }, + {name: 'OpenWoo', meta: 'WOO', href: '/commonground#openwoo', desc: 'WOO-compliant publication flow. Active disclosure, queryable archive. Solution built on OpenRegister + OpenCatalogi + DocuDesk.', icon: }, + {name: 'ZaakAfhandelApp', meta: 'Zaken', href: '/apps/zaakafhandelapp', desc: 'Citizen-facing case-status portal.', icon: }, + {name: 'Anonimiseren', meta: 'Privacy', href: '/apps/docudesk', desc: 'Privacy-by-design pipeline. Auto-redact PII before publication.', icon: }, + {name: 'Doriath', meta: 'Vault', href: '/apps/doriath', desc: 'Self-hosted password and secrets vault. Per-user, per-team, audited.', icon: }, ]}, {position: 'bottom-left', family: 'workspace', label: 'Workspace\nApps', items: [ - {name: 'PipelinQ', meta: 'CRM', desc: 'CRM with quotes, contacts, and deal-flow.', icon: }, - {name: 'Procest', meta: 'Cases', desc: 'Case-management for VTH and citizen processes.', icon: }, - {name: 'DeciDesk', meta: 'Boards', desc: 'Decision-support and board management.', icon: }, - {name: 'ShillinQ', meta: 'ERP', desc: 'Bookkeeping, invoicing, expense tracking.', icon: }, - {name: 'LarpingApp', meta: 'Larp', desc: 'Worldbuilding and campaign-management.', icon: }, - {name: 'SoftwareCatalog', meta: 'Assets', desc: 'IT-asset management, software inventory, licenses, contracts.', icon: }, + {name: 'PipelinQ', meta: 'CRM', href: '/apps/pipelinq', desc: 'CRM with quotes, contacts, and deal-flow.', icon: }, + {name: 'Procest', meta: 'Cases', href: '/apps/procest', desc: 'Case-management for VTH and citizen processes.', icon: }, + {name: 'DeciDesk', meta: 'Boards', href: '/apps/decidesk', desc: 'Decision-support and board management.', icon: }, + {name: 'ShillinQ', meta: 'ERP', href: '/apps/shillinq', desc: 'Bookkeeping, invoicing, expense tracking.', icon: }, + {name: 'LarpingApp', meta: 'Larp', href: '/apps/larpingapp', desc: 'Worldbuilding and campaign-management.', icon: }, + {name: 'SoftwareCatalog', meta: 'Assets', href: '/apps/softwarecatalog', desc: 'IT-asset management, software inventory, licenses, contracts.', icon: }, ]}, {position: 'bottom-right', family: 'integrate', label: 'Integrated\nApps', columns: 2, items: [ - {name: 'OpenTalk', meta: 'Video', brandColor: '#00485C', desc: 'Open-source video conferencing.', icon: }, {name: 'Matrix', meta: 'Chat', brandColor: '#000000', desc: 'Federated chat protocol. End-to-end encrypted.', icon: }, {name: 'n8n', meta: 'Flows', brandColor: '#EA4B71', desc: 'Workflow automation, no-code flows.', icon: }, {name: 'Windmill', meta: 'Flow', brandColor: '#DC2A1F', desc: 'Developer-focused workflow engine. Python, TypeScript, Go, Bash.', icon: }, @@ -87,10 +88,8 @@ import {totalPartners} from '@site/src/data/partners-catalog'; {name: 'Presidio', meta: 'PII', brandColor: '#0078D4', desc: 'Microsoft PII detection. Anonymise text before publication, GDPR-ready.', icon: }, {name: 'Keycloak', meta: 'SSO', brandColor: '#DB3327', desc: 'Open-source identity and access management. SSO, OIDC, SAML, social login.', icon: }, ]}, - {position: 'bottom-center', family: 'builder', label: 'App\nBuilder', badge: 'COMING SOON', items: [ - {name: 'Build your own apps', desc: 'Compose Nextcloud apps from the Technical Core.', icon: }, - {name: 'Customise ConNext apps', desc: 'Fork-free customisation of any ConNext app.', icon: }, - {name: 'No code, just configure', desc: 'Configuration-only changes survive upgrades.', icon: }, + {position: 'bottom-center', family: 'builder', label: 'App\nBuilder', items: [ + {name: 'OpenBuilt', meta: 'Builder', href: '/apps/openbuilt', desc: 'Compose Nextcloud apps from the Technical Core. Fork-free customisation. Configuration over code, so changes survive upgrades.', icon: }, ]}, ]} flows={[ @@ -182,7 +181,7 @@ import {totalPartners} from '@site/src/data/partners-catalog';
Most Conduction integrations are with the standard Nextcloud apps already on the home screen, Mail, Files, Calendar, Contacts, Notes, Deck. The set below is different. These are external open-source projects we did not build. We host them next to Nextcloud as ExApps (separate processes the workspace runs and exposes through its UI), or call them through OpenConnector adapters. Reusing them is cheaper than rewriting them, and it keeps the credit where it belongs.} @@ -192,28 +191,27 @@ import {totalPartners} from '@site/src/data/partners-catalog'; Why through the app store?
- Each ExApp is packaged for the Nextcloud app store, so installing one feels the same as installing any other Nextcloud app. We ship the option because sovereignty without choice is just a slogan. Like Nextcloud Talk with its tight workspace fit? Use it. Prefer OpenTalk on the Matrix protocol? Use that instead. Same login, same workspace, your call on the engine. + Each ExApp is packaged for the Nextcloud app store, so installing one feels the same as installing any other Nextcloud app. We ship the option because sovereignty without choice is just a slogan. Like Nextcloud Talk with its tight workspace fit? Use it. Prefer a federated chat like Matrix? Use that instead. Same login, same workspace, your call on the engine.
}, - {name: 'Matrix', meta: 'ExApp · Chat', brandColor: '#000000', desc: 'Federated chat protocol. End-to-end encrypted, runs on your own server.', href: 'https://matrix.org/', icon: }, - {name: 'Mattermost', meta: 'ExApp · Chat', brandColor: '#0058CC', desc: 'Self-hosted Slack alternative. Threads, integrations, and on-prem data.', href: 'https://mattermost.com/', icon: }, - {name: 'OpenExchange',meta: 'ExApp · Mail', brandColor: '#005A9B', desc: 'Open-source mail and groupware suite. Drop-in alternative to Microsoft Exchange, with App Suite for the inbox.', href: 'https://www.open-xchange.com/', icon: }, - {name: 'Windmill', meta: 'ExApp · Workflow', brandColor: '#DC2A1F', desc: 'Developer-focused workflow engine. Python, TypeScript, Go, Bash. Triggered by Conduction events.', href: 'https://windmill.dev/', icon: }, - {name: 'n8n', meta: 'ExApp · Workflow', brandColor: '#EA4B71', desc: 'Low-code automation flows. Drag-drop builder, hundreds of node types, fits next to OpenConnector.', href: 'https://n8n.io/', icon: }, - {name: 'OpenProject', meta: 'ExApp · PM', brandColor: '#0770B8', desc: 'Project management. Gantt, agile boards, work packages mapped to Conduction cases.', href: 'https://www.openproject.org/', icon: }, - {name: 'XWiki', meta: 'ExApp · Wiki', brandColor: '#1E4FB6', desc: 'Structured wiki and knowledge base. Pages, macros, query language. Embed register data inline.', href: 'https://www.xwiki.org/', icon: }, - {name: 'GitLab', meta: 'ExApp · Code', brandColor: '#FC6D26', desc: 'Git hosting, CI/CD, issue tracking. The other half of every developer team.', href: 'https://gitlab.com/', icon: }, - {name: 'Keycloak', meta: 'ExApp · SSO', brandColor: '#DB3327', desc: 'Open-source identity. SSO, OIDC, SAML, social login. Stand-in for the gemeentelijke IdP.', href: 'https://www.keycloak.org/', icon: }, - {name: 'Claude', meta: 'Adapter · LLM', brandColor: '#D97757', desc: 'Anthropic frontier LLM. Long context, strong agents, called via the OpenConnector adapter.', href: 'https://www.anthropic.com/', icon: }, - {name: 'OpenAI', meta: 'Adapter · LLM', brandColor: '#10A37F', desc: 'GPT family. Drop-in adapter through OpenConnector when policy allows external LLMs.', href: 'https://openai.com/', icon: }, - {name: 'Mistral', meta: 'Adapter · LLM', brandColor: '#FA500F', desc: 'EU-hosted open-weight LLM. Strong reasoning, GDPR-friendly, the default for compliance-heavy installs.', href: 'https://mistral.ai/', icon: }, - {name: 'Ollama', meta: 'Adapter · LLM', brandColor: '#000000', desc: 'Self-hosted LLM runtime. Run Mistral, Llama, and more on your own GPU. No data leaves the server.', href: 'https://ollama.com/', icon: }, - {name: 'Presidio', meta: 'Adapter · PII', brandColor: '#0078D4', desc: 'Microsoft\'s PII detection library. Anonymises text before publication. Powers DocuDesk\'s redact_pii tool.', href: 'https://microsoft.github.io/presidio/', icon: }, + {name: 'Matrix', meta: 'ExApp · Chat', brandColor: '#000000', desc: 'Federated chat protocol. End-to-end encrypted, runs on your own server.', href: 'https://apps.nextcloud.com/apps/integration_matrix', icon: }, + {name: 'Mattermost', meta: 'ExApp · Chat', brandColor: '#0058CC', desc: 'Self-hosted Slack alternative. Threads, integrations, and on-prem data.', href: 'https://apps.nextcloud.com/apps/integration_mattermost', icon: }, + {name: 'OpenExchange',meta: 'ExApp · Mail', brandColor: '#005A9B', desc: 'Open-source mail and groupware suite. Drop-in alternative to Microsoft Exchange, with App Suite for the inbox.', href: 'https://nextcloud.com/partners/', icon: }, + {name: 'Windmill', meta: 'ExApp · Workflow', brandColor: '#DC2A1F', desc: 'Developer-focused workflow engine. Python, TypeScript, Go, Bash. Triggered by Conduction events.', href: 'https://apps.nextcloud.com/apps/integration_windmill', icon: }, + {name: 'n8n', meta: 'ExApp · Workflow', brandColor: '#EA4B71', desc: 'Low-code automation flows. Drag-drop builder, hundreds of node types, fits next to OpenConnector.', href: '/sidecars/n8n', icon: }, + {name: 'OpenProject', meta: 'ExApp · PM', brandColor: '#0770B8', desc: 'Project management. Gantt, agile boards, work packages mapped to Conduction cases.', href: 'https://apps.nextcloud.com/apps/integration_openproject', icon: }, + {name: 'XWiki', meta: 'ExApp · Wiki', brandColor: '#1E4FB6', desc: 'Structured wiki and knowledge base. Pages, macros, query language. Embed register data inline.', href: '/apps/openregister#integrations', icon: }, + {name: 'GitLab', meta: 'ExApp · Code', brandColor: '#FC6D26', desc: 'Git hosting, CI/CD, issue tracking. The other half of every developer team.', href: 'https://apps.nextcloud.com/apps/integration_gitlab', icon: }, + {name: 'Keycloak', meta: 'ExApp · SSO', brandColor: '#DB3327', desc: 'Open-source identity. SSO, OIDC, SAML, social login. Stand-in for the gemeentelijke IdP.', href: '/sidecars/keycloak', icon: }, + {name: 'Claude', meta: 'Adapter · LLM', brandColor: '#D97757', desc: 'Anthropic frontier LLM. Long context, strong agents, called via the OpenConnector adapter.', href: '/apps/openregister#integrations', icon: }, + {name: 'OpenAI', meta: 'Adapter · LLM', brandColor: '#10A37F', desc: 'GPT family. Drop-in adapter through OpenConnector when policy allows external LLMs.', href: 'https://apps.nextcloud.com/apps/integration_openai', icon: }, + {name: 'Mistral', meta: 'Adapter · LLM', brandColor: '#FA500F', desc: 'EU-hosted open-weight LLM. Strong reasoning, GDPR-friendly, the default for compliance-heavy installs.', href: '/apps/openregister#integrations', icon: }, + {name: 'Ollama', meta: 'Adapter · LLM', brandColor: '#000000', desc: 'Self-hosted LLM runtime. Run Mistral, Llama, and more on your own GPU. No data leaves the server.', href: '/sidecars/ollama', icon: }, + {name: 'Presidio', meta: 'Adapter · PII', brandColor: '#0078D4', desc: 'Microsoft\'s PII detection library. Anonymises text before publication. Powers DocuDesk\'s redact_pii tool.', href: '/apps/openregister#integrations', icon: }, ]} /> diff --git a/src/pages/demo.mdx b/src/pages/demo.mdx index 56e80602a..68de1b7d7 100644 --- a/src/pages/demo.mdx +++ b/src/pages/demo.mdx @@ -5,6 +5,7 @@ hide_table_of_contents: true --- import {Section, SectionHead, HowSteps, HowStep, FeatureList, FeatureItem, PairRow, PairCard, CtaBanner, Button, ComposeBlock} from '@conduction/docusaurus-preset/components'; +import {APP_COUNT} from '@site/src/data/apps-catalog';
href="/apps/openconnector" icon={} name="OpenConnector" - why="Trekt voorbeelddata binnen via REST/SOAP/file drops. Alle 12 apps profiteren." + why={<>Trekt voorbeelddata binnen via REST/SOAP/file drops. Alle {APP_COUNT} apps profiteren.} />
diff --git a/src/pages/index.mdx b/src/pages/index.mdx index c35934655..96cb3d9d0 100644 --- a/src/pages/index.mdx +++ b/src/pages/index.mdx @@ -7,6 +7,7 @@ hide_table_of_contents: true import {Hero, StatsStrip, AppsPreview, CtaBanner, PairRow, PairCard, Section, SectionHead, Clients, DEFAULT_CLIENTS, PartnerDirectory, CommonGroundPlus, HexRain} from '@conduction/docusaurus-preset/components'; import {totalDownloads, formatDownloads} from '@conduction/docusaurus-preset/data'; import {PARTNERS, BECOME_PARTNER, totalPartners} from '@site/src/data/partners-catalog'; +import {APP_COUNT} from '@site/src/data/apps-catalog'; {/* Conduction main landing. Public Tech framing: tech to serve people. Three doors out: install an app yourself, get it as managed SaaS, or @@ -17,7 +18,7 @@ import {PARTNERS, BECOME_PARTNER, totalPartners} from '@site/src/data/partners-c Public Tech · open-source apps for Nextcloud} - title={<>Tech to serve people.
One workspace.
Twelve apps.} + title={<>Tech to serve people.
One workspace.
{APP_COUNT} apps.} lede={<>We are Conduction. We build open-source apps that plug into Nextcloud. Catalogs, registers, document handling, integrations. Install yourself, host with us as SaaS, or bring in a partner. Same code, same licence, no procurement track required.} primaryCta={{label: <>Install from Nextcloud app store, href: '/apps'}} secondaryCta={{label: 'Run a local demo', href: '/demo'}} @@ -27,7 +28,7 @@ import {PARTNERS, BECOME_PARTNER, totalPartners} from '@site/src/data/partners-c
apps in the ecosystem,
all open-source }, + { value: String(APP_COUNT), label: <>apps in the ecosystem,
all open-source }, { value: '2 min', label: <>from app store install
to working solution }, { value: '€0', label: <>for the apps,
support is optional }, { value: totalPartners, label: <>certified and partner
implementers }, diff --git a/src/pages/sidecars.mdx b/src/pages/sidecars.mdx new file mode 100644 index 000000000..bf93e7a0d --- /dev/null +++ b/src/pages/sidecars.mdx @@ -0,0 +1,80 @@ +--- +title: Sidecars, Conduction +description: Nextcloud-installable wrappers around third-party open-source services. Same upstream, less DevOps. +hide_table_of_contents: true +--- + +import React, {useState, useMemo} from 'react'; +import {Section, SectionHead, FacetedFilters, AppCard} from '@conduction/docusaurus-preset/components'; +import {getSidecars, SIDECAR_CATEGORIES} from '@site/src/data/sidecars-catalog'; + +export const sidecars = getSidecars(); + +export const STATUS_LABELS = {STABLE: 'Stable', BETA: 'Beta', 'COMING SOON': 'Coming soon'}; + +export function FilterableSidecars() { + const [selected, setSelected] = useState({}); + + const facets = useMemo(() => { + const cats = SIDECAR_CATEGORIES.filter(c => c !== 'All'); + return [ + {key: 'category', label: 'Category', items: cats.map(c => ({ + value: c, label: c, + count: sidecars.filter(a => Array.isArray(a.categories) && a.categories.includes(c)).length, + })).filter(item => item.count > 0)}, + {key: 'status', label: 'Status', items: ['STABLE', 'BETA', 'COMING SOON'].map(s => ({ + value: s, label: STATUS_LABELS[s], + count: sidecars.filter(a => a.status === s).length, + })).filter(item => item.count > 0)}, + ]; + }, []); + + const filtered = useMemo(() => { + return sidecars.filter(a => { + for (const [key, values] of Object.entries(selected)) { + if (!values || values.length === 0) continue; + if (key === 'category') { + const cats = a.categories || []; + if (!values.some(v => cats.includes(v))) return false; + } else if (key === 'status') { + if (!values.includes(a.status)) return false; + } + } + return true; + }); + }, [selected]); + + const totalSelected = Object.values(selected).reduce( + (n, v) => n + (Array.isArray(v) ? v.length : 0), 0 + ); + + return ( +
+ +
+
+
+ {filtered.length} of {sidecars.length} sidecars + {totalSelected > 0 && · {sidecars.length - filtered.length} hidden by filters} +
+
+
+ {filtered.map((app, i) => )} +
+ {filtered.length === 0 && ( +

No sidecars match this filter.

+ )} +
+
+ ); +} + +
+ Third-party services, installed like an app.} + lede={<>Sidecars are Nextcloud-installable wrappers around proven open-source services. n8n, Keycloak, OpenZaak, Ollama and others, packaged so you install them from the app store instead of operating a separate cluster. We do not fork the upstream; the sidecar tracks releases and handles the koppelvlak to Nextcloud.} + /> + + +
diff --git a/src/pages/sidecars/keycloak.mdx b/src/pages/sidecars/keycloak.mdx new file mode 100644 index 000000000..c78ce646d --- /dev/null +++ b/src/pages/sidecars/keycloak.mdx @@ -0,0 +1,47 @@ +--- +title: Keycloak sidecar, Conduction +description: Nextcloud sidecar wrapping Keycloak for self-hosted identity and SSO. +hide_table_of_contents: true +--- + +import {DetailHero, Section, SectionHead, CtaBanner} from '@conduction/docusaurus-preset/components'; + +A Nextcloud sidecar that wraps Keycloak. Run the open-source identity broker inside your workspace, installed from the app store, SSO across all your apps from day one.} + primaryCta={{label: 'Install from app store', href: '/install'}} + secondaryCta={{label: 'Read the docs', href: 'https://docs.conduction.nl/keycloak'}} + tertiaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/keycloak-nextcloud'}} + iconColor="var(--c-blue-cobalt)" + icon={} +/> + +
+ +
+ +
+ +
+ +Install from the Nextcloud app store. Configure SSO for every Conduction app and every third-party sidecar in one place.} + primaryCta={{label: 'Install from app store', href: '/install'}} + secondaryCta={{label: 'Talk to a partner', href: '/partners'}} +/> diff --git a/src/pages/sidecars/n8n.mdx b/src/pages/sidecars/n8n.mdx new file mode 100644 index 000000000..8a1cf4d51 --- /dev/null +++ b/src/pages/sidecars/n8n.mdx @@ -0,0 +1,47 @@ +--- +title: n8n sidecar, Conduction +description: Nextcloud sidecar wrapping n8n for workflow automation. +hide_table_of_contents: true +--- + +import {DetailHero, Section, SectionHead, CtaBanner} from '@conduction/docusaurus-preset/components'; + +A Nextcloud sidecar that wraps n8n. Run the popular visual workflow-automation engine inside your workspace, installed from the Nextcloud app store, no separate hosting.} + primaryCta={{label: 'Install from app store', href: '/install'}} + secondaryCta={{label: 'Read the docs', href: 'https://docs.conduction.nl/n8n'}} + tertiaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/n8n-nextcloud'}} + iconColor="var(--c-blue-cobalt)" + icon={} +/> + +
+ +
+ +
+ +
+ +Install from the Nextcloud app store. Connect to OpenRegister rows. Trigger workflows from OpenConnector events. Surface results in MyDash.} + primaryCta={{label: 'Install from app store', href: '/install'}} + secondaryCta={{label: 'Talk to a partner', href: '/partners'}} +/> diff --git a/src/pages/sidecars/ollama.mdx b/src/pages/sidecars/ollama.mdx new file mode 100644 index 000000000..9d825fe6e --- /dev/null +++ b/src/pages/sidecars/ollama.mdx @@ -0,0 +1,47 @@ +--- +title: Ollama sidecar, Conduction +description: Nextcloud sidecar wrapping Ollama for local LLM inference. +hide_table_of_contents: true +--- + +import {DetailHero, Section, SectionHead, CtaBanner} from '@conduction/docusaurus-preset/components'; + +A Nextcloud sidecar that wraps Ollama. Run local large-language models inside your workspace, install from the app store, keep your prompts on your own hardware.} + primaryCta={{label: 'Install from app store', href: '/install'}} + secondaryCta={{label: 'Read the docs', href: 'https://docs.conduction.nl/ollama'}} + tertiaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/ollama-nextcloud'}} + iconColor="var(--c-blue-cobalt)" + icon={} +/> + +
+ +
+ +
+ +
+ +Install from the Nextcloud app store. Wire local models into AI Companion. Drive summarisation flows from OpenConnector. Keep every prompt on your own hardware.} + primaryCta={{label: 'Install from app store', href: '/install'}} + secondaryCta={{label: 'Talk to a partner', href: '/partners'}} +/> diff --git a/src/pages/sidecars/open-webui.mdx b/src/pages/sidecars/open-webui.mdx new file mode 100644 index 000000000..358c35a5b --- /dev/null +++ b/src/pages/sidecars/open-webui.mdx @@ -0,0 +1,47 @@ +--- +title: Open WebUI sidecar, Conduction +description: Nextcloud sidecar wrapping Open WebUI for a chat interface over local LLMs. +hide_table_of_contents: true +--- + +import {DetailHero, Section, SectionHead, CtaBanner} from '@conduction/docusaurus-preset/components'; + +A Nextcloud sidecar that wraps Open WebUI. A ChatGPT-style interface for your local LLMs, installed from the app store, all running on your own hardware.} + primaryCta={{label: 'Install from app store', href: '/install'}} + secondaryCta={{label: 'Read the docs', href: 'https://docs.conduction.nl/open-webui'}} + tertiaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/open-webui-nextcloud'}} + iconColor="var(--c-blue-cobalt)" + icon={} +/> + +
+ +
+ +
+ +
+ +Install from the Nextcloud app store. Pair with the Ollama sidecar for the model. SSO comes from the Keycloak sidecar. One workspace, no cloud round-trip.} + primaryCta={{label: 'Install from app store', href: '/install'}} + secondaryCta={{label: 'Talk to a partner', href: '/partners'}} +/> diff --git a/src/pages/sidecars/openklant.mdx b/src/pages/sidecars/openklant.mdx new file mode 100644 index 000000000..93d273491 --- /dev/null +++ b/src/pages/sidecars/openklant.mdx @@ -0,0 +1,47 @@ +--- +title: OpenKlant sidecar, Conduction +description: Nextcloud sidecar wrapping the OpenKlant citizen-contact register. ZGW-compatible, installed from the app store. +hide_table_of_contents: true +--- + +import {DetailHero, Section, SectionHead, CtaBanner} from '@conduction/docusaurus-preset/components'; + +A Nextcloud sidecar that wraps OpenKlant. Run the official citizen-contact register next to your workspace, install from the Nextcloud app store, no separate cluster.} + primaryCta={{label: 'Install from app store', href: '/install'}} + secondaryCta={{label: 'Read the docs', href: 'https://docs.conduction.nl/openklant'}} + tertiaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/openklant'}} + iconColor="var(--c-blue-cobalt)" + icon={} +/> + +
+ +
+ +
+ +
+ +Install from the Nextcloud app store. Pair with OpenConnector to route ZGW calls. Pair with Procest to drive workflows on citizen-contact events.} + primaryCta={{label: 'Install from app store', href: '/install'}} + secondaryCta={{label: 'Talk to a partner', href: '/partners'}} +/> diff --git a/src/pages/sidecars/openzaak.mdx b/src/pages/sidecars/openzaak.mdx new file mode 100644 index 000000000..01d7181e2 --- /dev/null +++ b/src/pages/sidecars/openzaak.mdx @@ -0,0 +1,47 @@ +--- +title: OpenZaak sidecar, Conduction +description: Nextcloud sidecar wrapping the OpenZaak case-management API. +hide_table_of_contents: true +--- + +import {DetailHero, Section, SectionHead, CtaBanner} from '@conduction/docusaurus-preset/components'; + +A Nextcloud sidecar that wraps OpenZaak. Run the official Common Ground case-management API next to your workspace. ZGW-koppelvlakken, vanuit Nextcloud.} + primaryCta={{label: 'Install from app store', href: '/install'}} + secondaryCta={{label: 'Read the docs', href: 'https://docs.conduction.nl/openzaak'}} + tertiaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/openzaak'}} + iconColor="var(--c-blue-cobalt)" + icon={} +/> + +
+ +
+ +
+ +
+ +Install from the Nextcloud app store. Route ZGW calls through OpenConnector. Drive cases through Procest workflows.} + primaryCta={{label: 'Install from app store', href: '/install'}} + secondaryCta={{label: 'Talk to a partner', href: '/partners'}} +/> diff --git a/src/pages/sidecars/valtimo.mdx b/src/pages/sidecars/valtimo.mdx new file mode 100644 index 000000000..7d753034b --- /dev/null +++ b/src/pages/sidecars/valtimo.mdx @@ -0,0 +1,47 @@ +--- +title: Valtimo sidecar, Conduction +description: Nextcloud sidecar wrapping the Valtimo BPMN workflow engine. +hide_table_of_contents: true +--- + +import {DetailHero, Section, SectionHead, CtaBanner} from '@conduction/docusaurus-preset/components'; + +A Nextcloud sidecar that wraps Valtimo. Run the open-source BPMN workflow engine inside your workspace, install from the app store, no separate cluster to operate.} + primaryCta={{label: 'Install from app store', href: '/install'}} + secondaryCta={{label: 'Read the docs', href: 'https://docs.conduction.nl/valtimo'}} + tertiaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/valtimo'}} + iconColor="var(--c-blue-cobalt)" + icon={} +/> + +
+ +
+ +
+ +
+ +Install from the Nextcloud app store. Trigger workflows from Procest. Surface task lists in MyDash. Route inputs through OpenConnector.} + primaryCta={{label: 'Install from app store', href: '/install'}} + secondaryCta={{label: 'Talk to a partner', href: '/partners'}} +/> diff --git a/src/pages/support.mdx b/src/pages/support.mdx index 14756588b..babe37315 100644 --- a/src/pages/support.mdx +++ b/src/pages/support.mdx @@ -93,6 +93,7 @@ export const TIER_PILL_BASE = {
Hosts and ships our apps with an SLA from Conduction. Third-line support from our engineers, named contacts, input on bug priority and feature requests.
  • Everything in Host
  • +
  • Official Nextcloud partner
  • SLA contract with Conduction
  • Direct line to third-line support
  • Roadmap and bug-priority input
  • From 820888bdb7a49ad3d402224f99ec1314a73078aa Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Wed, 13 May 2026 11:58:14 +0200 Subject: [PATCH 10/19] chore(deps): bump @conduction/docusaurus-preset to ^2.10.0 The 2.10.0 release adds the platform-diagram href row attribute (every Conduction item on the connext diagram becomes a clickable link), the has-badge gating so the App Builder cluster styles like a real list when its badge is absent, and the four new entries in apps-registry (shillinq, openbuilt, doriath, app-versions). All of this is what connext.mdx now relies on. --- package-lock.json | 9 +++++---- package.json | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8abd45190..26abb895c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,8 +7,9 @@ "": { "name": "conduction-website", "version": "0.0.0", + "hasInstallScript": true, "dependencies": { - "@conduction/docusaurus-preset": "^2.1.0", + "@conduction/docusaurus-preset": "^2.10.0", "@docusaurus/core": "^3.5.0", "@docusaurus/preset-classic": "^3.5.0", "@mdx-js/react": "^3.0.0", @@ -1987,9 +1988,9 @@ } }, "node_modules/@conduction/docusaurus-preset": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-2.1.0.tgz", - "integrity": "sha512-BYnT4h0nZ0a7so5vUjDNL/OfHQ18jxDnSB0g8tne9lqlf2Wfpl5Rk9QMdLRFLD+mIc8QQOMEH6U7hpLbkTMN4A==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-2.10.0.tgz", + "integrity": "sha512-YLYjKv4OmlP/XzXh9kS0/4IEV9zXldE9h1nhzb3GAVTblt+ZkYRnzvthkqm0PMD1P8Ou4tbBvTt1VgjlsjYMKg==", "license": "EUPL-1.2", "peerDependencies": { "@docusaurus/core": "^3.0.0", diff --git a/package.json b/package.json index f1e5e4a23..8fde2843a 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "ci": "npm ci --legacy-peer-deps && npm run build" }, "dependencies": { - "@conduction/docusaurus-preset": "^2.1.0", + "@conduction/docusaurus-preset": "^2.10.0", "@docusaurus/core": "^3.5.0", "@docusaurus/preset-classic": "^3.5.0", "@mdx-js/react": "^3.0.0", From c604f2be083fc4f042671f3b745a83ec5d7fdef1 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Wed, 13 May 2026 12:52:42 +0200 Subject: [PATCH 11/19] chore(deps): bump @conduction/docusaurus-preset to ^2.10.1 The 2.10.1 patch fixes the App Builder hex colour (defaults to cobalt; the orange placeholder treatment is now gated behind a has-badge class on the cluster) and the platform-diagram tooltip clip (.row:hover becomes overflow:visible with z-index 10, so the .desc tooltip escapes the row's overflow:hidden box and paints above sibling rows). --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 26abb895c..2c9786864 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.0", "hasInstallScript": true, "dependencies": { - "@conduction/docusaurus-preset": "^2.10.0", + "@conduction/docusaurus-preset": "^2.10.1", "@docusaurus/core": "^3.5.0", "@docusaurus/preset-classic": "^3.5.0", "@mdx-js/react": "^3.0.0", @@ -1988,9 +1988,9 @@ } }, "node_modules/@conduction/docusaurus-preset": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-2.10.0.tgz", - "integrity": "sha512-YLYjKv4OmlP/XzXh9kS0/4IEV9zXldE9h1nhzb3GAVTblt+ZkYRnzvthkqm0PMD1P8Ou4tbBvTt1VgjlsjYMKg==", + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-2.10.1.tgz", + "integrity": "sha512-qRlSPloaiSugErACsfoSmS8ByzZpg9r3+yFNIJKgdKmQzg+z8F92wuB4ulr602x+0Mrl9UEXTwFRKPf75pAj/A==", "license": "EUPL-1.2", "peerDependencies": { "@docusaurus/core": "^3.0.0", diff --git a/package.json b/package.json index 8fde2843a..0d1262c71 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "ci": "npm ci --legacy-peer-deps && npm run build" }, "dependencies": { - "@conduction/docusaurus-preset": "^2.10.0", + "@conduction/docusaurus-preset": "^2.10.1", "@docusaurus/core": "^3.5.0", "@docusaurus/preset-classic": "^3.5.0", "@mdx-js/react": "^3.0.0", From a05b7d43999019d14f1da2ab56d340400a07867a Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Thu, 14 May 2026 07:28:59 +0200 Subject: [PATCH 12/19] feat(apps): commercial page for Scholiq (LMS + LVS) (#40) --- src/data/apps-catalog.js | 7 +++ src/pages/apps/scholiq.mdx | 101 +++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 src/pages/apps/scholiq.mdx diff --git a/src/data/apps-catalog.js b/src/data/apps-catalog.js index a4bc40e66..04cfe8dac 100644 --- a/src/data/apps-catalog.js +++ b/src/data/apps-catalog.js @@ -110,6 +110,13 @@ const PRESENTATION = { categories: ['Processes'], icon: , }, + scholiq: { + name: 'Scholiq', + tagline: 'Learning record + LMS. Cursussen, inschrijvingen, certificaten, compliance-training.', + href: '/apps/scholiq', + categories: ['Processes'], + icon: , + }, nldesign: { name: 'NLDesign', tagline: 'Drop-in NLDS theme for Nextcloud, with the Conduction component variants on top.', diff --git a/src/pages/apps/scholiq.mdx b/src/pages/apps/scholiq.mdx new file mode 100644 index 000000000..38f1acd5e --- /dev/null +++ b/src/pages/apps/scholiq.mdx @@ -0,0 +1,101 @@ +--- +title: Scholiq, Conduction +description: Learning record + LMS on Nextcloud. Cohorts, courses, attendance, grading, certificates, compliance training. +hide_table_of_contents: true +--- + +import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, PairCard, CtaBanner, AppCrossLinks} from '@conduction/docusaurus-preset/components'; + +Leerlingvolgsysteem en LMS op Nextcloud. Cursussen, inschrijvingen, aanwezigheid, nakijken, certificaten. Voor scholen, opleiders en compliance-teams die hun leerproces niet bij een externe SaaS willen parkeren.} + primaryCta={{label: 'Install from app store', href: '/install'}} + secondaryCta={{label: 'Read the docs', href: 'https://scholiq.conduction.nl/'}} + tertiaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/scholiq'}} + iconColor="var(--c-blue-cobalt)" + icon={} +/> + +
    +
    +
    + + + } + title="Cursussen en inschrijvingen." + > + Bouw een cursus uit modules, koppel lesmateriaal en data, schrijf deelnemers of hele cohorten in. Voor doorlopend onderwijs én voor één-daagse trainingen. + + } + title="Opdrachten en nakijken." + > + Stel een opdracht uit, deelnemers leveren in, jij geeft feedback en een cijfer. De volledige geschiedenis blijft per leerling beschikbaar in zijn dossier. + + } + title="Aanwezigheid en voortgang." + > + Wie was er, wie heeft de module afgerond, wie staat waar in zijn leerplan. Zonder losse Excel-lijsten, zonder schaduw-administratie. + + } + title="Certificaten en compliance-training." + > + Reik certificaten uit met geldigheid en hercertificering. Voor BHV, AVG, integriteits-trainingen of de jaarlijkse cybersecurity-cursus: het systeem herinnert je vanzelf wie weer aan de beurt is. + + +
    + +
    +
    + +
    + + + } + name="OpenRegister" + why="Deelnemers, cursussen, inschrijvingen, cijfers en certificaten als typed registers." + /> + } + name="DocuDesk" + why="Genereert certificaten, cursusverslagen en deelnemerslijsten uit sjablonen." + /> + } + name="MyDash" + why="Mijn cursussen, na te kijken werk, en hercertificeringen op het dashboard." + /> + +
    + +Scholiq houdt het leerproces — van cursusinschrijving tot certificering — op je eigen Nextcloud. Open-source, op je eigen data, klaar voor inspectie en compliance.} + primaryCta={{label: 'Install from app store', href: '/install'}} + secondaryCta={{label: 'Talk to a partner', href: '/partners'}} +/> From c8a4976b6d244d604d61f742c8b435652bc04e12 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Thu, 14 May 2026 07:56:05 +0200 Subject: [PATCH 13/19] feat(connext): add App Versions to Technical Core; DeskDesk, Planix, Scholiq to Workspace Apps (#41) Surface all in-house apps in the platform diagram so the home page of /connext/ matches the breadth of the catalogue under /apps. App Versions joins the Technical Core (fleet-management infrastructure); DeskDesk, Planix and Scholiq join the Workspace Apps row alongside the rest of the end-user apps. --- src/pages/connext.mdx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/pages/connext.mdx b/src/pages/connext.mdx index 1584e0363..1ffe87228 100644 --- a/src/pages/connext.mdx +++ b/src/pages/connext.mdx @@ -59,6 +59,7 @@ import {APP_COUNT, ECOSYSTEM_COUNT} from '@site/src/data/apps-catalog'; {name: 'DocuDesk', meta: 'Docs', href: '/apps/docudesk', desc: 'Generate, anonymise, sign, and template documents.', icon: }, {name: 'NLDesign Theme', meta: 'Theme', href: '/apps/nldesign', desc: 'NL Design System tokens applied across every Conduction app.', icon: }, {name: 'MyDash', meta: 'Dashboard', href: '/apps/mydash', desc: 'Dashboard builder with role-based access.', icon: }, + {name: 'App Versions', meta: 'Versions', href: '/apps/app-versions', desc: 'Pin and roll back any Nextcloud app version. Safety net for fleet upgrades.', icon: }, ]}, {position: 'top-right', family: 'solutions', label: 'Solutions', items: [ {name: 'OpenWoo', meta: 'WOO', href: '/commonground#openwoo', desc: 'WOO-compliant publication flow. Active disclosure, queryable archive. Solution built on OpenRegister + OpenCatalogi + DocuDesk.', icon: }, @@ -73,6 +74,9 @@ import {APP_COUNT, ECOSYSTEM_COUNT} from '@site/src/data/apps-catalog'; {name: 'ShillinQ', meta: 'ERP', href: '/apps/shillinq', desc: 'Bookkeeping, invoicing, expense tracking.', icon: }, {name: 'LarpingApp', meta: 'Larp', href: '/apps/larpingapp', desc: 'Worldbuilding and campaign-management.', icon: }, {name: 'SoftwareCatalog', meta: 'Assets', href: '/apps/softwarecatalog', desc: 'IT-asset management, software inventory, licenses, contracts.', icon: }, + {name: 'DeskDesk', meta: 'Desks', href: '/apps/deskdesk', desc: 'Desk and meeting-room booking. Plan, reserve, check in from the workspace.', icon: }, + {name: 'Planix', meta: 'Kanban', href: '/apps/planix', desc: 'Kanban boards on registers. Cards, lanes, swim-lanes wired to typed data.', icon: }, + {name: 'Scholiq', meta: 'LMS', href: '/apps/scholiq', desc: 'Learning record + LMS. Courses, enrolment, certificates, compliance-training.', icon: }, ]}, {position: 'bottom-right', family: 'integrate', label: 'Integrated\nApps', columns: 2, items: [ {name: 'Matrix', meta: 'Chat', brandColor: '#000000', desc: 'Federated chat protocol. End-to-end encrypted.', icon: }, From dac41f6f337cfa14ba05779bca14d6b4e2204bf5 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Thu, 14 May 2026 09:34:14 +0200 Subject: [PATCH 14/19] feat(apps): fill DeskDesk/Planix/OpenTalk listings; rewrite App Versions away from scaffold framing (#42) - apps-catalog: add DeskDesk + Planix entries (Workspace category); rewrite app-versions tagline from "scaffold/template" to "pin and roll back any Nextcloud app version, multi-source picker, audit-trailed" so the catalog matches the actual app and its new docs site - apps/app-versions.mdx: full rewrite around the version-management surface (version table, multi-source picker, rollback/pin, audit trail) with links to the four canonical specs - apps/deskdesk.mdx: new commercial page for the desk and meeting-room booking app (plattegrond, kantoorweek planning, check-in, no-show handling) - apps/planix.mdx: new commercial page for kanban-over-registers (boards, swim-lanes, workflow on typed data, saved views) - sidecars-catalog + sidecars/opentalk.mdx: close the listing gap for the OpenTalk video-conferencing ExApp wrapper --- src/data/apps-catalog.js | 16 ++++- src/data/sidecars-catalog.js | 7 +++ src/pages/apps/app-versions.mdx | 48 +++++++++++---- src/pages/apps/deskdesk.mdx | 101 ++++++++++++++++++++++++++++++++ src/pages/apps/planix.mdx | 101 ++++++++++++++++++++++++++++++++ src/pages/sidecars/opentalk.mdx | 47 +++++++++++++++ 6 files changed, 308 insertions(+), 12 deletions(-) create mode 100644 src/pages/apps/deskdesk.mdx create mode 100644 src/pages/apps/planix.mdx create mode 100644 src/pages/sidecars/opentalk.mdx diff --git a/src/data/apps-catalog.js b/src/data/apps-catalog.js index 04cfe8dac..390598c78 100644 --- a/src/data/apps-catalog.js +++ b/src/data/apps-catalog.js @@ -147,11 +147,25 @@ const PRESENTATION = { }, 'app-versions': { name: 'App Versions', - tagline: 'Nextcloud app scaffold. Template repo for new apps, prefilled and CI-ready. In development.', + tagline: 'Pin and roll back any Nextcloud app version. Multi-source picker, audit-trailed. In development.', href: '/apps/app-versions', categories: ['Data'], icon: , }, + deskdesk: { + name: 'DeskDesk', + tagline: 'Desk and meeting-room booking. Plan, reserve, and check in from the Nextcloud workspace.', + href: '/apps/deskdesk', + categories: ['Processes'], + icon: , + }, + planix: { + name: 'Planix', + tagline: 'Kanban boards on registers. Cards, lanes, swim-lanes wired directly to typed data.', + href: '/apps/planix', + categories: ['Processes'], + icon: , + }, }; /* Categories the website filters by, in the order the chips render. */ diff --git a/src/data/sidecars-catalog.js b/src/data/sidecars-catalog.js index 36af885db..be1341c7f 100644 --- a/src/data/sidecars-catalog.js +++ b/src/data/sidecars-catalog.js @@ -67,6 +67,13 @@ const PRESENTATION = { categories: ['AI'], icon: , }, + opentalk: { + name: 'OpenTalk', + tagline: 'Sidecar for OpenTalk. GDPR-compliant video conferencing, end-to-end encrypted, packaged as a Nextcloud ExApp.', + href: '/sidecars/opentalk', + categories: ['Workflow'], + icon: , + }, }; export const SIDECAR_CATEGORIES = ['All', 'Registers', 'Workflow', 'Identity', 'AI']; diff --git a/src/pages/apps/app-versions.mdx b/src/pages/apps/app-versions.mdx index 2b218f3f0..75bcd7dfe 100644 --- a/src/pages/apps/app-versions.mdx +++ b/src/pages/apps/app-versions.mdx @@ -1,10 +1,10 @@ --- title: App Versions, Conduction -description: Nextcloud app scaffold and version registry. In development. +description: Pin and roll back any Nextcloud app version. Multi-source picker, audit-trailed. In development. hide_table_of_contents: true --- -import {DetailHero, Section, SectionHead, CtaBanner} from '@conduction/docusaurus-preset/components'; +import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, CtaBanner} from '@conduction/docusaurus-preset/components'; A template repo and version registry for new Nextcloud apps. Prefilled scaffolds, CI-ready, conformance-checked against our app conventions. In active development.} - secondaryCta={{label: 'Follow progress', href: 'https://github.com/ConductionNL/app-versions'}} + tagline={<>Pin and roll back any Nextcloud app version. Multi-source picker over the app store, GitHub releases, and private registries. Audit-trailed, admin-only, a safety net for fleet upgrades.} + secondaryCta={{label: 'Read the docs', href: 'https://app-versions.conduction.nl/'}} tertiaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/app-versions'}} iconColor="var(--c-blue-cobalt)" icon={} @@ -22,11 +22,37 @@ import {DetailHero, Section, SectionHead, CtaBanner} from '@conduction/docusauru
    + + } + title="Per-app version table." + > + For every installed app: current version, available versions per source, last upgrade, rollback target. One screen, no command line. + + } + title="Multiple sources." + > + Nextcloud app store, GitHub releases, private registries. A token per source if needed; private GitHub releases work the same as public ones. + + } + title="Rollback or pin." + > + One click to revert to a known-good release. Or pin a version so a future auto-update can't move it. Per app, not fleet-wide. + + } + title="Audit trail." + > + Every version change recorded: who, when, from what to what, why. Visible to the admin team, exportable for compliance. + +
    @@ -34,13 +60,13 @@ import {DetailHero, Section, SectionHead, CtaBanner} from '@conduction/docusauru eyebrow="Status" title="Where it stands today." align="stack" - lede="The scaffold is usable today as a 'Use this template' source on GitHub. The version-registry surface is being designed. The app is not yet a customer-installable Nextcloud app. Follow the repository on GitHub for releases." + lede="The four canonical specs — version-management, external-sources, pat-management, and import — are in place. Admin UI for the picker is in active development. Follow the repository on GitHub for releases, or read the spec-anchored docs for the design rationale." />
    App Versions is the entry point for partners and integrators. Reach out and we'll point you at the right starting branch.} - primaryCta={{label: 'Talk to a partner', href: '/partners'}} + title="A safety net for the next upgrade cycle." + lede={<>App Versions is the difference between 'roll forward and hope' and 'roll back if it bites'. Open-source, admin-only, no extra service to run.} + primaryCta={{label: 'Read the docs', href: 'https://app-versions.conduction.nl/'}} secondaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/app-versions'}} /> diff --git a/src/pages/apps/deskdesk.mdx b/src/pages/apps/deskdesk.mdx new file mode 100644 index 000000000..40d012894 --- /dev/null +++ b/src/pages/apps/deskdesk.mdx @@ -0,0 +1,101 @@ +--- +title: DeskDesk, Conduction +description: Desk and meeting-room booking on Nextcloud. Plan the office week, reserve a desk, check in. Hybride werken zonder losse SaaS. +hide_table_of_contents: true +--- + +import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, PairCard, CtaBanner, AppCrossLinks} from '@conduction/docusaurus-preset/components'; + +Bureau- en vergaderzaalboeking op Nextcloud. Plan je kantoorweek, reserveer een werkplek, check in. Voor hybride teams die hun bezetting niet via een externe SaaS willen regelen.} + primaryCta={{label: 'Install from app store', href: '/install'}} + secondaryCta={{label: 'Read the docs', href: 'https://deskdesk.conduction.nl/'}} + tertiaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/deskdesk'}} + iconColor="var(--c-blue-cobalt)" + icon={} +/> + +
    +
    +
    + + + } + title="Plattegrond per vestiging." + > + Teken je vloer in, plaats bureaus, vergaderzalen en faciliteiten. Mensen klikken op een plek om te reserveren; jij ziet de bezetting in één oogopslag. + + } + title="Plan je kantoorweek." + > + Reserveer terugkerend (elke dinsdag) of incidenteel. Zie wie er die dag is, plan teamdagen rond elkaars aanwezigheid. + + } + title="Check-in en no-shows." + > + Wie er is, scant in (QR of NFC). Wie niet komt opdagen, geeft zijn plek vanzelf vrij. Geen reservering-spookbezetting meer. + + } + title="Inzicht voor facility." + > + Bezettingsgraad per dag, per zone, per faciliteit. Stuurt je heroverwegingen over vierkante meters en koffie-automaten, niet je onderbuikgevoel. + + +
    + +
    +
    + +
    + + + } + name="OpenRegister" + why="Vestigingen, bureaus, reserveringen en check-ins als typed registers met audit-trail." + /> + } + name="MyDash" + why="Bezettingsgrafieken, no-show-percentages en zone-trends op het dashboard." + /> + } + name="OpenConnector" + why="Synct met de toegangs-controle en met Nextcloud Calendar voor het terugzien van je boekingen." + /> + +
    + +DeskDesk pakt bureau- en zaalboeking voor je terug: in je Nextcloud, op je eigen data, met een plattegrond die je zelf onderhoudt.} + primaryCta={{label: 'Install from app store', href: '/install'}} + secondaryCta={{label: 'Talk to a partner', href: '/partners'}} +/> diff --git a/src/pages/apps/planix.mdx b/src/pages/apps/planix.mdx new file mode 100644 index 000000000..2023f8161 --- /dev/null +++ b/src/pages/apps/planix.mdx @@ -0,0 +1,101 @@ +--- +title: Planix, Conduction +description: Kanban-boards die op je registers staan. Cards, lanes en swim-lanes rechtstreeks op typed data, geen losse projectmanagement-SaaS. +hide_table_of_contents: true +--- + +import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, PairCard, CtaBanner, AppCrossLinks} from '@conduction/docusaurus-preset/components'; + +Kanban-boards die op je Nextcloud-registers staan. Cards, lanes en swim-lanes rechtstreeks op typed data, niet op losse JSON-bestandjes. Voor teams die plannen niet bij Trello, Jira of Monday willen parkeren.} + primaryCta={{label: 'Install from app store', href: '/install'}} + secondaryCta={{label: 'Read the docs', href: 'https://planix.conduction.nl/'}} + tertiaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/planix'}} + iconColor="var(--c-blue-cobalt)" + icon={} +/> + +
    +
    +
    + + + } + title="Boards over registers." + > + Kies een register, kies een veld voor de status, en je hebt een kanban. Voor zaken, opdrachten, helpdesk-tickets of je eigen aangemaakte registers. + + } + title="Swim-lanes." + > + Splits het bord per project, team of toegewezen behandelaar. Of zet de swim-lane op een ander veld en bekijk hetzelfde werk vanuit een ander perspectief. + + } + title="Workflow op typed data." + > + Verplaats een card en de status van het object in OpenRegister verandert mee. Validaties, hooks en lifecycle-events draaien net zo hard als bij elke andere update. + + } + title="Filters en gespaarde views." + > + Filter per toegewezen behandelaar, prioriteit of label. Sla de view op, deel hem met je team. Iedereen kijkt naar dezelfde data, maar met zijn eigen lens. + + +
    + +
    +
    + +
    + + + } + name="OpenRegister" + why="De typed data onder elke kanban-card; statuswijzigingen lopen door de register-hooks." + /> + } + name="Procest" + why="Werkstroom op dezelfde objecten: workflow in Procest, dagelijks bord in Planix." + /> + } + name="MyDash" + why="Doorlooptijden, lane-counts en bottleneck-zicht boven op het bord, voor de teamlead." + /> + +
    + +Planix tekent kanban over typed registers, dus elke verplaatsing is een echte status-update. Open-source, op je eigen Nextcloud, klaar voor audit.} + primaryCta={{label: 'Install from app store', href: '/install'}} + secondaryCta={{label: 'Talk to a partner', href: '/partners'}} +/> diff --git a/src/pages/sidecars/opentalk.mdx b/src/pages/sidecars/opentalk.mdx new file mode 100644 index 000000000..381ddbaba --- /dev/null +++ b/src/pages/sidecars/opentalk.mdx @@ -0,0 +1,47 @@ +--- +title: OpenTalk sidecar, Conduction +description: Nextcloud sidecar wrapping the OpenTalk video-conferencing controller. GDPR-compliant meetings, end-to-end encryption, screen sharing. +hide_table_of_contents: true +--- + +import {DetailHero, Section, SectionHead, CtaBanner} from '@conduction/docusaurus-preset/components'; + +A Nextcloud sidecar that wraps OpenTalk. GDPR-compliant video conferencing, end-to-end encrypted, screen-sharing included. Installed through the AppAPI, run as an ExApp next to the workspace.} + primaryCta={{label: 'Install from app store', href: '/install'}} + secondaryCta={{label: 'Read the upstream docs', href: 'https://opentalk.eu/'}} + tertiaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/opentalk'}} + iconColor="var(--c-blue-cobalt)" + icon={} +/> + +
    + +
    + +
    + +
    + +Install from the Nextcloud app store. GDPR-compliant, end-to-end encrypted, hosted next to the workspace your team already uses.} + primaryCta={{label: 'Install from app store', href: '/install'}} + secondaryCta={{label: 'Talk to a partner', href: '/partners'}} +/> From a6f5e375a899f8974cb685c695ae3ee5b0a635f6 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Thu, 14 May 2026 09:44:32 +0200 Subject: [PATCH 15/19] revert(apps): DeskDesk and Planix are academy tutorials, not register apps (#43) DeskDesk and Planix are tutorial apps that live in /academy/deskdesk-tutorial-* and the corresponding planix walk-through, not standalone products. Remove them from the apps register (apps-catalog.js) and the /connext/ Workspace Apps panel where they were briefly surfaced in #41 / #42, and drop the now-orphan commercial pages at /apps/deskdesk and /apps/planix. The tutorial pages and their docs sites (deskdesk.conduction.nl, planix.conduction.nl) stay put. --- src/data/apps-catalog.js | 14 ----- src/pages/apps/deskdesk.mdx | 101 ------------------------------------ src/pages/apps/planix.mdx | 101 ------------------------------------ src/pages/connext.mdx | 2 - 4 files changed, 218 deletions(-) delete mode 100644 src/pages/apps/deskdesk.mdx delete mode 100644 src/pages/apps/planix.mdx diff --git a/src/data/apps-catalog.js b/src/data/apps-catalog.js index 390598c78..a048a51f4 100644 --- a/src/data/apps-catalog.js +++ b/src/data/apps-catalog.js @@ -152,20 +152,6 @@ const PRESENTATION = { categories: ['Data'], icon: , }, - deskdesk: { - name: 'DeskDesk', - tagline: 'Desk and meeting-room booking. Plan, reserve, and check in from the Nextcloud workspace.', - href: '/apps/deskdesk', - categories: ['Processes'], - icon: , - }, - planix: { - name: 'Planix', - tagline: 'Kanban boards on registers. Cards, lanes, swim-lanes wired directly to typed data.', - href: '/apps/planix', - categories: ['Processes'], - icon: , - }, }; /* Categories the website filters by, in the order the chips render. */ diff --git a/src/pages/apps/deskdesk.mdx b/src/pages/apps/deskdesk.mdx deleted file mode 100644 index 40d012894..000000000 --- a/src/pages/apps/deskdesk.mdx +++ /dev/null @@ -1,101 +0,0 @@ ---- -title: DeskDesk, Conduction -description: Desk and meeting-room booking on Nextcloud. Plan the office week, reserve a desk, check in. Hybride werken zonder losse SaaS. -hide_table_of_contents: true ---- - -import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, PairCard, CtaBanner, AppCrossLinks} from '@conduction/docusaurus-preset/components'; - -Bureau- en vergaderzaalboeking op Nextcloud. Plan je kantoorweek, reserveer een werkplek, check in. Voor hybride teams die hun bezetting niet via een externe SaaS willen regelen.} - primaryCta={{label: 'Install from app store', href: '/install'}} - secondaryCta={{label: 'Read the docs', href: 'https://deskdesk.conduction.nl/'}} - tertiaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/deskdesk'}} - iconColor="var(--c-blue-cobalt)" - icon={} -/> - -
    -
    -
    - - - } - title="Plattegrond per vestiging." - > - Teken je vloer in, plaats bureaus, vergaderzalen en faciliteiten. Mensen klikken op een plek om te reserveren; jij ziet de bezetting in één oogopslag. - - } - title="Plan je kantoorweek." - > - Reserveer terugkerend (elke dinsdag) of incidenteel. Zie wie er die dag is, plan teamdagen rond elkaars aanwezigheid. - - } - title="Check-in en no-shows." - > - Wie er is, scant in (QR of NFC). Wie niet komt opdagen, geeft zijn plek vanzelf vrij. Geen reservering-spookbezetting meer. - - } - title="Inzicht voor facility." - > - Bezettingsgraad per dag, per zone, per faciliteit. Stuurt je heroverwegingen over vierkante meters en koffie-automaten, niet je onderbuikgevoel. - - -
    - -
    -
    - -
    - - - } - name="OpenRegister" - why="Vestigingen, bureaus, reserveringen en check-ins als typed registers met audit-trail." - /> - } - name="MyDash" - why="Bezettingsgrafieken, no-show-percentages en zone-trends op het dashboard." - /> - } - name="OpenConnector" - why="Synct met de toegangs-controle en met Nextcloud Calendar voor het terugzien van je boekingen." - /> - -
    - -DeskDesk pakt bureau- en zaalboeking voor je terug: in je Nextcloud, op je eigen data, met een plattegrond die je zelf onderhoudt.} - primaryCta={{label: 'Install from app store', href: '/install'}} - secondaryCta={{label: 'Talk to a partner', href: '/partners'}} -/> diff --git a/src/pages/apps/planix.mdx b/src/pages/apps/planix.mdx deleted file mode 100644 index 2023f8161..000000000 --- a/src/pages/apps/planix.mdx +++ /dev/null @@ -1,101 +0,0 @@ ---- -title: Planix, Conduction -description: Kanban-boards die op je registers staan. Cards, lanes en swim-lanes rechtstreeks op typed data, geen losse projectmanagement-SaaS. -hide_table_of_contents: true ---- - -import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, PairCard, CtaBanner, AppCrossLinks} from '@conduction/docusaurus-preset/components'; - -Kanban-boards die op je Nextcloud-registers staan. Cards, lanes en swim-lanes rechtstreeks op typed data, niet op losse JSON-bestandjes. Voor teams die plannen niet bij Trello, Jira of Monday willen parkeren.} - primaryCta={{label: 'Install from app store', href: '/install'}} - secondaryCta={{label: 'Read the docs', href: 'https://planix.conduction.nl/'}} - tertiaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/planix'}} - iconColor="var(--c-blue-cobalt)" - icon={} -/> - -
    -
    -
    - - - } - title="Boards over registers." - > - Kies een register, kies een veld voor de status, en je hebt een kanban. Voor zaken, opdrachten, helpdesk-tickets of je eigen aangemaakte registers. - - } - title="Swim-lanes." - > - Splits het bord per project, team of toegewezen behandelaar. Of zet de swim-lane op een ander veld en bekijk hetzelfde werk vanuit een ander perspectief. - - } - title="Workflow op typed data." - > - Verplaats een card en de status van het object in OpenRegister verandert mee. Validaties, hooks en lifecycle-events draaien net zo hard als bij elke andere update. - - } - title="Filters en gespaarde views." - > - Filter per toegewezen behandelaar, prioriteit of label. Sla de view op, deel hem met je team. Iedereen kijkt naar dezelfde data, maar met zijn eigen lens. - - -
    - -
    -
    - -
    - - - } - name="OpenRegister" - why="De typed data onder elke kanban-card; statuswijzigingen lopen door de register-hooks." - /> - } - name="Procest" - why="Werkstroom op dezelfde objecten: workflow in Procest, dagelijks bord in Planix." - /> - } - name="MyDash" - why="Doorlooptijden, lane-counts en bottleneck-zicht boven op het bord, voor de teamlead." - /> - -
    - -Planix tekent kanban over typed registers, dus elke verplaatsing is een echte status-update. Open-source, op je eigen Nextcloud, klaar voor audit.} - primaryCta={{label: 'Install from app store', href: '/install'}} - secondaryCta={{label: 'Talk to a partner', href: '/partners'}} -/> diff --git a/src/pages/connext.mdx b/src/pages/connext.mdx index 1ffe87228..85a92428d 100644 --- a/src/pages/connext.mdx +++ b/src/pages/connext.mdx @@ -74,8 +74,6 @@ import {APP_COUNT, ECOSYSTEM_COUNT} from '@site/src/data/apps-catalog'; {name: 'ShillinQ', meta: 'ERP', href: '/apps/shillinq', desc: 'Bookkeeping, invoicing, expense tracking.', icon: }, {name: 'LarpingApp', meta: 'Larp', href: '/apps/larpingapp', desc: 'Worldbuilding and campaign-management.', icon: }, {name: 'SoftwareCatalog', meta: 'Assets', href: '/apps/softwarecatalog', desc: 'IT-asset management, software inventory, licenses, contracts.', icon: }, - {name: 'DeskDesk', meta: 'Desks', href: '/apps/deskdesk', desc: 'Desk and meeting-room booking. Plan, reserve, check in from the workspace.', icon: }, - {name: 'Planix', meta: 'Kanban', href: '/apps/planix', desc: 'Kanban boards on registers. Cards, lanes, swim-lanes wired to typed data.', icon: }, {name: 'Scholiq', meta: 'LMS', href: '/apps/scholiq', desc: 'Learning record + LMS. Courses, enrolment, certificates, compliance-training.', icon: }, ]}, {position: 'bottom-right', family: 'integrate', label: 'Integrated\nApps', columns: 2, items: [ From cadc7b535dab7f3d308b230ef4d2a1da240e9dea Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Fri, 15 May 2026 08:17:00 +0200 Subject: [PATCH 16/19] chore(deps): bump @conduction/docusaurus-preset to ^2.10.2 (#44) Picks up the App Builder hex always-orange rule from preset commit b7a0fce ("revert: App Builder hex stays KNVB orange unconditionally"). The previous 2.10.1 still rendered the hex cobalt unless a COMING SOON badge was present; 2.10.2 unconditionally fills it with var(--c-orange-knvb), which is the documented brand non-negotiable (preset README "Brand rules" section). --- package-lock.json | 290 ++++++++++++++-------------------------------- package.json | 2 +- 2 files changed, 91 insertions(+), 201 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2c9786864..6b3c435ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.0", "hasInstallScript": true, "dependencies": { - "@conduction/docusaurus-preset": "^2.10.1", + "@conduction/docusaurus-preset": "^2.10.2", "@docusaurus/core": "^3.5.0", "@docusaurus/preset-classic": "^3.5.0", "@mdx-js/react": "^3.0.0", @@ -1988,9 +1988,9 @@ } }, "node_modules/@conduction/docusaurus-preset": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-2.10.1.tgz", - "integrity": "sha512-qRlSPloaiSugErACsfoSmS8ByzZpg9r3+yFNIJKgdKmQzg+z8F92wuB4ulr602x+0Mrl9UEXTwFRKPf75pAj/A==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-2.10.2.tgz", + "integrity": "sha512-qbQ6htlDq8a6oVlZ7Bxo7Vhely0gSsD23FtL9KfNBJOugIScIGA1I/2ocAZP54u6zto/twzQtsnlSn5wBIXfsg==", "license": "EUPL-1.2", "peerDependencies": { "@docusaurus/core": "^3.0.0", @@ -2210,19 +2210,6 @@ "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-cascade-layers/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@csstools/postcss-color-function": { "version": "4.0.12", "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-4.0.12.tgz", @@ -2609,19 +2596,6 @@ "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-is-pseudo-class/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@csstools/postcss-light-dark-function": { "version": "2.0.11", "resolved": "https://registry.npmjs.org/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-2.0.11.tgz", @@ -3056,19 +3030,6 @@ "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-scope-pseudo-class/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@csstools/postcss-sign-functions": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@csstools/postcss-sign-functions/-/postcss-sign-functions-1.1.4.tgz", @@ -7310,19 +7271,6 @@ "postcss": "^8.4" } }, - "node_modules/css-blank-pseudo/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/css-declaration-sorter": { "version": "7.4.0", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.4.0.tgz", @@ -7362,19 +7310,6 @@ "postcss": "^8.4" } }, - "node_modules/css-has-pseudo/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/css-loader": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", @@ -13703,19 +13638,6 @@ "postcss": "^8.4" } }, - "node_modules/postcss-attribute-case-insensitive/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/postcss-calc": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", @@ -13732,6 +13654,19 @@ "postcss": "^8.2.2" } }, + "node_modules/postcss-calc/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss-clamp": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", @@ -13947,19 +13882,6 @@ "postcss": "^8.4" } }, - "node_modules/postcss-custom-selectors/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/postcss-dir-pseudo-class": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-9.0.1.tgz", @@ -13985,19 +13907,6 @@ "postcss": "^8.4" } }, - "node_modules/postcss-dir-pseudo-class/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/postcss-discard-comments": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", @@ -14061,6 +13970,19 @@ "postcss": "^8.4.31" } }, + "node_modules/postcss-discard-unused/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss-double-position-gradients": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-6.0.4.tgz", @@ -14113,19 +14035,6 @@ "postcss": "^8.4" } }, - "node_modules/postcss-focus-visible/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/postcss-focus-within": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-9.0.1.tgz", @@ -14151,19 +14060,6 @@ "postcss": "^8.4" } }, - "node_modules/postcss-focus-within/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/postcss-font-variant": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", @@ -14347,6 +14243,19 @@ "postcss": "^8.4.31" } }, + "node_modules/postcss-merge-rules/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss-minify-font-values": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz", @@ -14411,6 +14320,19 @@ "postcss": "^8.4.31" } }, + "node_modules/postcss-minify-selectors/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss-modules-extract-imports": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", @@ -14440,19 +14362,6 @@ "postcss": "^8.1.0" } }, - "node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/postcss-modules-scope": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", @@ -14468,19 +14377,6 @@ "postcss": "^8.1.0" } }, - "node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/postcss-modules-values": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", @@ -14523,19 +14419,6 @@ "postcss": "^8.4" } }, - "node_modules/postcss-nesting/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/postcss-normalize-charset": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz", @@ -14886,19 +14769,6 @@ "postcss": "^8.4" } }, - "node_modules/postcss-pseudo-class-any-link/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/postcss-reduce-idents": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-6.0.3.tgz", @@ -14979,7 +14849,7 @@ "postcss": "^8.4" } }, - "node_modules/postcss-selector-not/node_modules/postcss-selector-parser": { + "node_modules/postcss-selector-parser": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", @@ -14992,19 +14862,6 @@ "node": ">=4" } }, - "node_modules/postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/postcss-sort-media-queries": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/postcss-sort-media-queries/-/postcss-sort-media-queries-5.2.0.tgz", @@ -15051,6 +14908,19 @@ "postcss": "^8.4.31" } }, + "node_modules/postcss-unique-selectors/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", @@ -16153,6 +16023,13 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/search-insights": { + "version": "2.17.3", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", + "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", + "license": "MIT", + "peer": true + }, "node_modules/section-matter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", @@ -16912,6 +16789,19 @@ "postcss": "^8.4.31" } }, + "node_modules/stylehacks/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", diff --git a/package.json b/package.json index 0d1262c71..867329d10 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "ci": "npm ci --legacy-peer-deps && npm run build" }, "dependencies": { - "@conduction/docusaurus-preset": "^2.10.1", + "@conduction/docusaurus-preset": "^2.10.2", "@docusaurus/core": "^3.5.0", "@docusaurus/preset-classic": "^3.5.0", "@mdx-js/react": "^3.0.0", From 59383aaa907176c88fadcf396b2e38cf6d05c005 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Fri, 15 May 2026 10:42:28 +0200 Subject: [PATCH 17/19] chore(deps): bump @conduction/docusaurus-preset to ^3.0.0 (#45) Picks up the v3.0.0 brand-system refactor: - 3D hex-prism mechanism removed (brand is flat-hex only) - Family token ramps trimmed from 5 stops (50/100/300/500/700) to 2 stops (300/500) - colors.html consolidated as the single canonical color page, folding in the categorical role labels (Process / Integrate / Data / Documents / Workspace) that used to live on the now-removed hex-prism page No source changes needed in this repo; conduction-website only used the flat primitive and tinted hexes via brand-color names, both of which remain in the new major. Locally verified on /connext/, /apps/, /apps/opencatalogi. --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6b3c435ab..9841db43c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.0", "hasInstallScript": true, "dependencies": { - "@conduction/docusaurus-preset": "^2.10.2", + "@conduction/docusaurus-preset": "^3.0.0", "@docusaurus/core": "^3.5.0", "@docusaurus/preset-classic": "^3.5.0", "@mdx-js/react": "^3.0.0", @@ -1988,9 +1988,9 @@ } }, "node_modules/@conduction/docusaurus-preset": { - "version": "2.10.2", - "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-2.10.2.tgz", - "integrity": "sha512-qbQ6htlDq8a6oVlZ7Bxo7Vhely0gSsD23FtL9KfNBJOugIScIGA1I/2ocAZP54u6zto/twzQtsnlSn5wBIXfsg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-3.0.0.tgz", + "integrity": "sha512-rrmnX3AobCsiQW1rkRL/rk7mg3yoQJuccNHu3sq26ebnUfZUPbVS+VyMqLzpsZznXjHQmAaSTEE8TlLzyE/SAQ==", "license": "EUPL-1.2", "peerDependencies": { "@docusaurus/core": "^3.0.0", diff --git a/package.json b/package.json index 867329d10..c654c6afe 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "ci": "npm ci --legacy-peer-deps && npm run build" }, "dependencies": { - "@conduction/docusaurus-preset": "^2.10.2", + "@conduction/docusaurus-preset": "^3.0.0", "@docusaurus/core": "^3.5.0", "@docusaurus/preset-classic": "^3.5.0", "@mdx-js/react": "^3.0.0", From 907cafa092fb15f7366a4c4afb243ebeac39a95e Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sun, 17 May 2026 09:39:03 +0200 Subject: [PATCH 18/19] chore(security): require 24h cooldown on npm packages (#47) Defense against supply-chain attacks (e.g. shai-hulud, nx-style compromised publishes) by blocking install of any package version published less than 24h ago. - .npmrc: `min-release-age=1` (npm 11.5+ native; older npm ignores it) - .github/dependabot.yml: `cooldown.default-days: 1`, with @conduction/* excluded so first-party releases reach our apps immediately For release-day consumption of fresh @conduction/* deps, use `npm install --min-release-age=0 @conduction/pkg@x.y.z`. --- .github/dependabot.yml | 13 +++++++++++++ .npmrc | 5 +++++ 2 files changed, 18 insertions(+) create mode 100644 .github/dependabot.yml create mode 100644 .npmrc diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..3390c9e54 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +version: 2 +updates: + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 10 + cooldown: + default-days: 1 + include: + - "*" + exclude: + - "@conduction/*" diff --git a/.npmrc b/.npmrc new file mode 100644 index 000000000..3942d3489 --- /dev/null +++ b/.npmrc @@ -0,0 +1,5 @@ +# Supply-chain hardening: reject any npm package published less than +# 24h ago. Compromised first-party-Conduction packages are excluded via +# Dependabot cooldown (.github/dependabot.yml); for fresh @conduction/* +# releases, override per-install with `npm install --min-release-age=0`. +min-release-age=1 From 04865cff9db7f2d5f33dd0e61261fa4da1e96141 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sun, 17 May 2026 19:35:29 +0200 Subject: [PATCH 19/19] feat(openregister): MCP tool shelf + clickable 24-leaf grid (#51) * feat(openregister): add MCP tools section to the product page Drops between the LLM-integrations Showcase and the "Pairs well with" PairRow. Lists the five MCP tools OpenRegister contributes to the workspace AI chat companion plus one "soon" write tool (createObject), with status hexes and an AgentTrace mock showing a search call. Uses showIds={false} per the product-page audience rule: the namespaced ids show up on per-app docs sites instead. Requires @conduction/docusaurus-preset >= the version that ships McpToolShelf (auto-publish on push to design-system main). * feat(apps): wire McpToolShelf into openregister, decidesk, openbuilt pages Align the OpenRegister product page with the real built-in tool surface from McpToolsService.php: three CRUD-shaped tools (openregister.registers, openregister.schemas, openregister.objects) instead of five placeholder ones. Lede + AgentTrace updated to match. Add a new McpToolShelf section to DeciDesk listing the five tools from DecideskToolProvider (listOpenActionItems, listRecentMeetings, getMeetingDetails, startMeeting, addActionItem). English copy, matching the rest of the page. Add a new McpToolShelf section to OpenBuilt listing the eight tools from OpenBuiltToolProvider (listApps, getAppManifest, createApp, promoteVersion, upsertSchema, upsertPage, addWidget, upsertMenuItem). Dutch copy, matching the rest of the page. All three sections use showIds={false} per the conduction.nl audience rule. Trace examples reference one or two tools from the same app to keep the call story concrete. Tool lists are hand-curated, not pulled at build time, matching how FeatureList / FeatureGrid are authored elsewhere. * feat(openregister): make leaf grid clickable into product docs Bump @conduction/docusaurus-preset to ^3.3.0 (adds href on FeatureItem) so the 24-leaf grid on /apps/openregister/ links each leaf to its product-doc page on openregister.conduction.nl. Also: strip em-dashes and curly apostrophes from the leaf tips (brand rule), and swap

    wrapper for

    on the closing footnote to fix a

    inside

    nesting warning. --- package-lock.json | 8 +-- package.json | 2 +- src/pages/apps/decidesk.mdx | 58 +++++++++++++++++++- src/pages/apps/openbuilt.mdx | 80 ++++++++++++++++++++++++++- src/pages/apps/openregister.mdx | 96 ++++++++++++++++++++++++++++++++- 5 files changed, 235 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9841db43c..a70924d26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.0", "hasInstallScript": true, "dependencies": { - "@conduction/docusaurus-preset": "^3.0.0", + "@conduction/docusaurus-preset": "^3.3.0", "@docusaurus/core": "^3.5.0", "@docusaurus/preset-classic": "^3.5.0", "@mdx-js/react": "^3.0.0", @@ -1988,9 +1988,9 @@ } }, "node_modules/@conduction/docusaurus-preset": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-3.0.0.tgz", - "integrity": "sha512-rrmnX3AobCsiQW1rkRL/rk7mg3yoQJuccNHu3sq26ebnUfZUPbVS+VyMqLzpsZznXjHQmAaSTEE8TlLzyE/SAQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-3.3.0.tgz", + "integrity": "sha512-xpNp/Azxwp1TbvnGLN8egxpPk/ZlpUEzdEHfwUDxf7McfpsM4C16JdgXaQ33n6GZAMCHKWrbseKjfSwRSbrmtQ==", "license": "EUPL-1.2", "peerDependencies": { "@docusaurus/core": "^3.0.0", diff --git a/package.json b/package.json index c654c6afe..7ca89dfe8 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "ci": "npm ci --legacy-peer-deps && npm run build" }, "dependencies": { - "@conduction/docusaurus-preset": "^3.0.0", + "@conduction/docusaurus-preset": "^3.3.0", "@docusaurus/core": "^3.5.0", "@docusaurus/preset-classic": "^3.5.0", "@mdx-js/react": "^3.0.0", diff --git a/src/pages/apps/decidesk.mdx b/src/pages/apps/decidesk.mdx index d3979aae9..e8f777986 100644 --- a/src/pages/apps/decidesk.mdx +++ b/src/pages/apps/decidesk.mdx @@ -4,7 +4,7 @@ description: Decision-support and board management on Nextcloud. Agenda, dossier hide_table_of_contents: true --- -import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, PairCard, CtaBanner, AppMock, RotatingCards, WidgetShelf, Showcase, AppCrossLinks} from '@conduction/docusaurus-preset/components'; +import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, PairCard, CtaBanner, AppMock, RotatingCards, WidgetShelf, Showcase, AgentTrace, McpToolShelf, AppCrossLinks} from '@conduction/docusaurus-preset/components'; +, + name: 'List open action items', + id: 'decidesk.listOpenActionItems', + description: 'Incomplete action items assigned to the caller, or all visible items. Scope mine / all, capped at 50.', + status: 'stable', + }, + { + icon: , + name: 'List recent meetings', + id: 'decidesk.listRecentMeetings', + description: "Recent meetings for the caller, newest first. Optional status filter: scheduled, in-progress, or closed.", + status: 'stable', + }, + { + icon: , + name: 'Get meeting details', + id: 'decidesk.getMeetingDetails', + description: 'Full dossier for one meeting: agenda, participants, action items, decisions, attached files.', + status: 'stable', + }, + { + icon: , + name: 'Start meeting', + id: 'decidesk.startMeeting', + description: 'Move a scheduled meeting into in-progress. Requires chair or admin role; logged in the audit trail.', + status: 'stable', + }, + { + icon: , + name: 'Add action item', + id: 'decidesk.addActionItem', + description: 'Create a new action item under a meeting, assigned to a participant, with optional due date.', + status: 'stable', + }, + ]} + showIds={false} + trace={} +/> +

    +, + name: 'Virtuele apps opsommen', + id: 'openbuilt.listApps', + description: 'Toon de virtuele apps in je organisatie met hun status en versie.', + status: 'stable', + }, + { + icon: , + name: 'App-manifest ophalen', + id: 'openbuilt.getAppManifest', + description: 'Haal het runtime-manifest van één gepubliceerde virtuele app op via de slug.', + status: 'stable', + }, + { + icon: , + name: 'Nieuwe app aanmaken', + id: 'openbuilt.createApp', + description: 'Maak een nieuwe virtuele app met een eerste draft-versie. Kies het versie-pad: single, dev-prod of dev-staging-prod.', + status: 'stable', + }, + { + icon: , + name: 'Versie promoveren', + id: 'openbuilt.promoteVersion', + description: 'Promoveer een virtuele app van bijvoorbeeld development naar productie. Default-strategie laat de doelversie leeg.', + status: 'stable', + }, + { + icon: , + name: 'Schema toevoegen of bijwerken', + id: 'openbuilt.upsertSchema', + description: 'Voeg een JSON Schema toe of werk het bij. De slug krijgt automatisch app- en versie-prefix.', + status: 'stable', + }, + { + icon: , + name: 'Pagina toevoegen of bijwerken', + id: 'openbuilt.upsertPage', + description: 'Maak of werk een pagina bij in het draft-manifest. Type is dashboard, index, detail of form.', + status: 'stable', + }, + { + icon: , + name: 'Widget plaatsen', + id: 'openbuilt.addWidget', + description: 'Voeg een widget toe aan een pagina: stat-counter, grafiek, lijst of een ander type uit de catalogus.', + status: 'stable', + }, + { + icon: , + name: 'Menu-item toevoegen of bijwerken', + id: 'openbuilt.upsertMenuItem', + description: 'Maak of werk een top-level menu-item bij in het draft-manifest. Route, volgorde en icoon stel je per item in.', + status: 'stable', + }, + ]} + showIds={false} + trace={} +/> +
    , }, @@ -227,6 +227,98 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, FeatureGrid, ]} /> +
    + + + + +
    + All 24 leaves use the same pluggable integration registry. Add your own with the leaf-scaffold script and it appears on every register the moment you install your app. +
    +
    + +, + name: 'Manage registers', + id: 'openregister.registers', + description: 'List, fetch, create, update, or delete registers. Registers group schemas and the objects written against them.', + status: 'stable', + }, + { + icon: , + name: 'Manage schemas', + id: 'openregister.schemas', + description: 'List, fetch, create, update, or delete schemas. A schema is the JSON Schema that defines an object type inside a register.', + status: 'stable', + }, + { + icon: , + name: 'Manage objects', + id: 'openregister.objects', + description: 'List, fetch, create, update, or delete objects. Every write is validated against the schema and recorded in the signed audit log.', + status: 'stable', + }, + ]} + showIds={false} + trace={} +/> +