Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
3fe7072
Merge remote-tracking branch 'origin/development' into feature/fronte…
remko48 Aug 21, 2026
d2fc6b8
refactor(menu): declutter the navigation into the old minimal shape
remko48 Aug 21, 2026
1e5863c
Merge remote-tracking branch 'origin/development' into feature/fronte…
remko48 Aug 24, 2026
3b80a70
feat(settings): show the app version in the user-settings dialog
remko48 Aug 24, 2026
5ac6eb7
fix(dashboard): drop the three permanently-blank banner cards
remko48 Aug 24, 2026
24f5893
feat(dashboard): live activity feed, approval queue, KPI colors and f…
remko48 Aug 25, 2026
56cf259
Merge remote-tracking branch 'origin/development' into feature/fronte…
remko48 Aug 25, 2026
26e8f1c
chore(deps): prepare for CnBreadcrumbs — vitest stub for the library …
remko48 Aug 26, 2026
c3aa2ed
feat(vault): old-look single-pane header with breadcrumbs, minimal to…
remko48 Aug 26, 2026
139f3e7
l10n: carry the vault-restyle strings to every required locale
remko48 Aug 26, 2026
7de2f76
feat(vault): navigate folders through the list itself (subfolder rows)
remko48 Aug 26, 2026
8e0bf56
l10n: nav strings for the folder-tree rail in every required locale
remko48 Aug 26, 2026
d9decdd
feat(nav): manifest-driven left rail with recursive folder tree, repl…
remko48 Aug 26, 2026
d1c15b1
l10n: vault-dialog strings in every required locale
remko48 Aug 26, 2026
ff2d019
feat(vault): vault-aware icons and wording, title-row toolbar, full t…
remko48 Aug 26, 2026
e1bf62f
Merge branch 'development' into feature/frontend-improvements
rubenvdlinde Aug 26, 2026
c99a6ff
Merge remote-tracking branch 'origin/development' into feature/fronte…
remko48 Aug 27, 2026
e72ed35
style: fix eslint and prettier violations flagged by CI
remko48 Aug 27, 2026
503312b
fix(l10n): add the Flow nav labels, which arrived untranslated (#458)
rubenvdlinde Aug 27, 2026
07b2690
chore(ci): take the canonical coverage-guard from ConductionNL/.githu…
rubenvdlinde Aug 27, 2026
b0daa67
test(e2e): repair the restyle-broken Playwright specs and hydra gate …
remko48 Aug 27, 2026
5d4093c
Merge pull request #448 from ConductionNL/feature/frontend-improvements
remko48 Aug 27, 2026
4355f52
perf(ci): one Code Quality run per commit, not two (#467)
rubenvdlinde Aug 27, 2026
d4f5eba
fix(release): name doriath as this app's previous App Store id (#464)
rubenvdlinde Aug 27, 2026
cc97993
chore(release): raise the version line above 0.2.0-beta.1 (doriath) (…
rubenvdlinde Aug 27, 2026
a2fa1ab
fix(l10n): translate the two strings #448 added (#466)
rubenvdlinde Aug 27, 2026
7c8f024
chore(deps): hydra-gates 1.10, so the E2E skip-discipline gate can ru…
rubenvdlinde Aug 28, 2026
650f861
fix(ci): a push to development must reach a verdict here too (#474)
rubenvdlinde Aug 28, 2026
02c4486
fix(e2e): move the fixme reason where the gate can read it (#475)
rubenvdlinde Aug 28, 2026
7694771
refactor(manifest): the flow pages are an index and a flow (#471)
rubenvdlinde Aug 28, 2026
df9cf03
docs: add a local demo environment (#480)
rubenvdlinde Aug 28, 2026
c2d7977
docs: make the demo verification command actually pass (#482)
rubenvdlinde Aug 28, 2026
6478ced
feat(walkthrough): a first-visit tour built from this app's own menu …
rubenvdlinde Aug 29, 2026
2277085
fix(walkthrough): withhold the tour until the vault is unlocked (#486)
rubenvdlinde Aug 29, 2026
83f3d26
test(e2e): the walkthrough preference probe is lock-screen chrome, no…
rubenvdlinde Aug 29, 2026
bf03c27
test(e2e): drop the dead allowlist entry, seed the walkthrough marker…
rubenvdlinde Aug 29, 2026
346cebd
chore(deps): @conduction/nextcloud-vue 2.21.0 -> 2.22.1 (#493)
rubenvdlinde Aug 29, 2026
e365363
ci(docs): publish from development, and retire the old hostname (#495)
rubenvdlinde Aug 29, 2026
a4cd851
chore(release): 0.2.1-unstable.20260829130044 (#496)
github-actions[bot] Aug 29, 2026
ba69e07
chore(deps): @conduction/nextcloud-vue 2.22.1 -> 2.24.1 (#497)
rubenvdlinde Aug 29, 2026
6408a54
chore(deps): @conduction/nextcloud-vue 2.24.1 -> 2.24.2 (#499)
rubenvdlinde Aug 29, 2026
5258512
feat(flows): give the flow-detail canvas its sidebar (#492)
rubenvdlinde Aug 30, 2026
4117fc1
chore(sync): record beta's ancestry on development
rubenvdlinde Aug 30, 2026
140bb2a
chore(sync): carry beta back into development
rubenvdlinde Aug 30, 2026
522e91c
Merge pull request #502 from ConductionNL/sync/beta-to-development-20…
rubenvdlinde Aug 30, 2026
4616275
Merge pull request #503 from ConductionNL/sync/beta-ancestry-20260830…
rubenvdlinde Aug 30, 2026
ee31d7f
chore(release): 0.2.2-unstable.20260830082444 (#501)
github-actions[bot] Aug 30, 2026
2d94718
fix(deps): align dexie on 4.4.5 so only one copy loads per page (#505)
rubenvdlinde Aug 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 48 additions & 24 deletions .github/workflows/code-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,46 @@ name: Code Quality

on:
push:
# An ALLOW-LIST of branch prefixes is a gate with a hole in it, and the
# hole is SILENT: a branch matching nothing gets no CI at all, and its last
# visible status is whatever it inherited — indistinguishable, on every
# dashboard, from a branch that passed.
# DEFAULT BRANCHES ONLY. `pull_request` below carries every other branch.
#
# Two live examples, both found 2026-08-14: `perf/**` was uncovered in
# openconnector, where a merge carrying unresolved conflict markers and 84
# failing tests was pushed and nothing ran; and `feat/**` was uncovered in
# openregister — note the list said `feature/**`, so every branch anyone
# named `feat/...` had been running unchecked.
# This was an allow-list of branch prefixes, and that was a gate with a
# SILENT hole: a branch matching nothing got no CI at all, and its last
# visible status was whatever it inherited — indistinguishable, on every
# dashboard, from a branch that passed. Two live examples, both found
# 2026-08-14: `perf/**` was uncovered in openconnector, where a merge
# carrying unresolved conflict markers and 84 failing tests was pushed and
# nothing ran; and `feat/**` was uncovered in openregister, because the
# list said `feature/**`.
#
# Prefixes are added rather than replaced with `**` because this workflow is
# expensive (PHPUnit matrix, Newman, Playwright). The fast structural checks
# DO run on `**` — see merge-hygiene.yml, added in the same change.
# The comment that stood here said adding prefixes was not the durable fix,
# and that the durable fix was to let the pull_request trigger gate it.
# THIS IS THAT CHANGE.
#
# ⚠️ Adding prefixes is not the durable fix; the next invented one is
# uncovered again. The durable fix is branch protection requiring a PR into
# development, which the pull_request trigger below already gates correctly.
# What forced it now: a push to a branch with an open PR ran the SAME 34
# jobs TWICE on the same commit. `concurrency` cannot dedupe them — the
# group is suffixed by event name deliberately (.github#540: a
# default-branch push carries jobs a PR run does not, and a dispatch must
# not be cancellable by a standing release PR), so the two events sit in
# different lanes BY DESIGN and both run to completion. Measured fleet-wide
# 2026-08-25..27, 659 of 2,106 Code Quality runs were that duplicate — 31%
# of the fleet's most expensive workflow, re-deciding a commit another run
# was already deciding. The account ceiling is 60 concurrent jobs (Team
# plan); the fleet was measured at 53 running with 1,528 jobs queued behind
# them, the oldest run 7 hours old and not yet started.
#
# NO BRANCH LOSES ITS FLOOR. merge-hygiene.yml runs on `'**'` — every
# branch anyone pushes, no prefix list to forget — and it is the check
# `development` actually requires. That is the smoke alarm; this workflow
# is the fire brigade and belongs on the PR. Of 668 feature-branch push
# runs in that window, only NINE were on a branch with no PR run beside
# them.
#
# The default branches STAY: their push runs are not duplicates, they are
# the only carrier of Coverage Baseline Check, SBOM and Features Extract,
# none of which run on a pull_request event.
branches:
- main
- development
- feature/**
- feat/**
- bugfix/**
- hotfix/**
- perf/**
- refactor/**
- chore/**
- fix/**
pull_request:
branches: [main, master, development, beta]
workflow_dispatch:
Expand Down Expand Up @@ -103,7 +114,20 @@ concurrency:
# A branch name is not a unique lane when two event types can each produce a
# run for it, so the event is now always part of the key.
group: quality-${{ github.head_ref || github.ref_name }}${{ github.event_name != 'pull_request' && format('-{0}', github.event_name) || '' }}
cancel-in-progress: true

# PUSH RUNS ARE NOT CANCELLED — and this has to be said HERE, not only in the
# shared workflow. .github#597 set `cancel-in-progress` on quality.yml itself,
# but a caller's own concurrency cancels the whole run before the called
# workflow's setting can apply, so that fix reached only the apps that declare
# no concurrency of their own. Measured 2026-08-28 over push runs on
# `development` since #597: 0 of 11 cancelled where the caller was silent, 7 of
# 13 (54%) cancelled where the caller still said `true`.
#
# An integration branch needs a verdict per commit: the run being cancelled is
# the only thing that would have said whether what just landed is sound, and
# its replacement is cancelled too. `pull_request` keeps cancelling, where
# superseding really is correct.
cancel-in-progress: ${{ github.event_name != 'push' }}

# Permission CEILING for the called quality pipeline. GitHub statically
# validates the called workflow's declared job permissions against this
Expand Down
62 changes: 25 additions & 37 deletions .github/workflows/documentation.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
name: Documentation

# Publishes the docs site to the Cloudflare Worker that serves it.
#
# TRIGGERS ON `development`, NOT ON A `documentation` BRANCH. This file used to
# listen on a branch called `documentation`; nobody has pushed to one since
# 2026-05-25, so the site simply stopped being rebuilt while every docs change
# merged to development satisfied its review and published nothing.
on:
push:
branches: [development]
Expand All @@ -9,48 +15,30 @@ on:
jobs:
deploy:
uses: ConductionNL/.github/.github/workflows/documentation.yml@main
# A reusable workflow receives NO secrets by default. Without this block
# `secrets.CF_API_TOKEN` is empty inside the callee, its "Publish to the
# Cloudflare Worker" step skips itself on its own guard, and the run
# finishes GREEN having written only gh-pages — which nothing serves. The
# live site never changes and no check goes red to say so.
#
# Mapped explicitly rather than `secrets: inherit`, because `inherit`
# hands the callee EVERY secret this repo holds — signing cert and key,
# appstore token, deploy keys — for the sake of two Cloudflare values.
# This way only those two cross the boundary.
#
# The exposure above is the ONLY reason for the explicit mapping. The
# names are the same on both sides: the org secrets really are
# `CF_API_TOKEN` / `CF_ACCOUNT_ID` — the names ConductionNL/.github's own
# deploy-docs.yml reads directly, and the names the callee declares under
# `workflow_call.secrets`.
#
# This block used to read `secrets.CLOUDFLARE_API_TOKEN` /
# `secrets.CLOUDFLARE_ACCOUNT_ID`, which are not secrets anywhere in this
# org. Mapping from a name that does not exist is NOT an error — it
# yields an empty string — so the callee's publish step skipped itself on
# its own guard and the run still finished green. Measured on planninq
# run 32760529026: "Publish to the Cloudflare Worker" SKIPPED, the log
# showing `CF_API_TOKEN:` with no value.
# A reusable workflow receives NO secrets by default. Without this block the
# callee's publish step finds CF_API_TOKEN empty, skips itself on its own
# `if:` guard, and the run finishes GREEN having changed nothing -- the
# failure that left the fleet's docs sites on May builds. The names are the
# same on both sides; the org secrets really are CF_API_TOKEN/CF_ACCOUNT_ID.
secrets:
CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
with:
# `keepiq.conduction.nl` resolves as of 2026-08-23 — attached as a second
# custom domain on the SAME `doriath-docs` worker that serves
# `doriath.conduction.nl`. Both hosts answer, so nothing goes dark either
# way. docs-hosts must list BOTH: wrangler reconciles the worker's
# triggers against it, so a host omitted there is removed. Keep this in
# step with docs/static/CNAME.
cname: keepiq.conduction.nl

# doriath.conduction.nl is the retired hostname. It stays in docs-hosts so
# existing links keep resolving, and canonical-host below turns it into a
# 301 rather than a second live copy of every page.
# EVERY host this worker answers on, in FULL: wrangler reconciles the
# worker's triggers against this list, so a host left out is REMOVED and
# goes dark.
docs-hosts: doriath.conduction.nl,keepiq.conduction.nl

# Named explicitly, because the comment above already knows the answer
# and the workflow did not. The callee derives the worker from `cname`
# when not told — `keepiq-docs`, which does not exist. Deploying that
# creates a SECOND worker while both custom domains keep routing to
# `doriath-docs`: every deploy green, reaching nobody. Renaming the
# worker is a Cloudflare-side move, not something this file can perform.
# The ONE hostname this site is reached on. Every other host in
# docs-hosts answers 301 to the same path here. Before this, both hostnames
# served identical content and the retired name stayed as discoverable
# as the current one.
canonical-host: keepiq.conduction.nl
# PINNED. Deriving the name is how a deploy goes green and reaches
# nobody: wrangler creates the derived worker and publishes there while
# the custom domains keep routing to the real one.
worker-name: doriath-docs
9 changes: 9 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,20 @@ concurrency:
group: release-${{ github.ref_name }}
cancel-in-progress: false

# `previous-app-id` is the App Store id this app shipped under before the
# rename. The store keys everything on the id, so `keepiq` is a brand new
# entry starting from nothing -- without this the version line restarts
# BELOW what the app already published as `doriath`, and the store accepts
# that with a 200 and then never offers it to anyone. Drop the input once
# the `doriath` entry is retired.
jobs:
unstable:
if: github.ref == 'refs/heads/development'
uses: ConductionNL/.github/.github/workflows/release.yml@main
with:
release-type: unstable
app-name: keepiq
previous-app-id: doriath
secrets: inherit

beta:
Expand All @@ -42,6 +49,7 @@ jobs:
with:
release-type: beta
app-name: keepiq
previous-app-id: doriath
secrets: inherit

stable:
Expand All @@ -50,4 +58,5 @@ jobs:
with:
release-type: stable
app-name: keepiq
previous-app-id: doriath
secrets: inherit
2 changes: 1 addition & 1 deletion appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Vrij en open source onder de EUPL-1.2-licentie.

**Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl.
]]></description>
<version>0.1.7-beta.20260820211310</version>
<version>0.2.2-unstable.20260830082444</version>
<licence>EUPL-1.2</licence>
<author mail="info@conduction.nl" homepage="https://www.conduction.nl/">Conduction</author>
<namespace>Keepiq</namespace>
Expand Down
12 changes: 6 additions & 6 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

131 changes: 131 additions & 0 deletions docs/Installation/demo-environment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Run a local demo

This page gets a working Keepiq running on your own machine in two commands. You end with archiving and retention over registry objects.

It is a **demo**, not a development environment. Nothing is mounted from a checkout, and that is deliberate — see [What this is not](#what-this-is-not).

## What you need

Docker, with Compose v2.23 or newer. Nothing else — no PHP, no Node, no Nextcloud.

```bash
docker --version
docker compose version
```

If `docker compose version` prints v2.22 or older, upgrade first. The compose file declares its scripts inline via `configs`, and older versions ignore the `content:` field **silently** — which produces an instance with no apps installed and nothing in the logs to explain why.

## Step 1 — get the compose file

```bash
curl -fsSLO https://raw.githubusercontent.com/ConductionNL/keepiq/development/keepiq-compose.yaml
```

A single self-contained file. There is nothing else to fetch and nothing to edit.

## Step 2 — start it

```bash
docker compose -f keepiq-compose.yaml up -d
```

The first run takes a few minutes: it pulls three images and downloads the application archives. Watch it work if you like:

```bash
docker compose -f keepiq-compose.yaml logs -f app-installer
```

You are looking for:

```
==> installing openregister <version>
==> installing thematiq <version>
==> installing integriq <version>
==> installing keepiq <version>
==> apps present: integriq keepiq openregister thematiq
```

Then Nextcloud installs itself and enables the apps **in dependency order**. OpenRegister goes first: it owns the registers and schemas the others declare against, and a leaf app enabled before it finds no register to attach to.

That is done when this returns `"installed":true`:

```bash
curl -s http://localhost:8616/status.php
```

## Step 3 — open the demo

| What | Where |
| --- | --- |
| **Keepiq** | [http://localhost:8616/apps/keepiq/](http://localhost:8616/apps/keepiq/) |
| Admin interface | [http://localhost:8616](http://localhost:8616) — `admin` / `admin` |

## What gets installed, and why more than one app

| App | Why |
| --- | --- |
| `openregister` | **Required.** Every Connext app declares its registers and schemas against OpenRegister. |
| `thematiq` | Optional. Government theming. Absent, the UI renders unthemed rather than wrong. |
| `integriq` | Optional. The connector, for feeding in data from systems you do not control. |
| `keepiq` | The app this page is about. |

That OpenRegister dependency is **not declared** in `appinfo/info.xml` — no app in the fleet declares an `<app>` dependency — so nothing stops the App Store from installing keepiq without it. It would then load, find no register to attach to, and show you an empty app rather than an error. The compose file encodes the dependency the manifest does not.

## Verifying it actually worked

A page loading is not the same as a page working. Nextcloud serves its shell before the app decides whether it has anything to render, so an app URL returns HTTP 200 even when it resolves to nothing at all. A smoke test that checks for a 200 would call that a success.

Check content instead:

```bash
# The app answers. Note the credentials: an app page requires a login, so the
# SAME request without -u returns 401, which is not a broken demo — measured
# on a booted demo while writing this page.
curl -s -o /dev/null -w '%{http_code}\n' -u admin:admin -L "http://localhost:8616/apps/keepiq/"

# OpenRegister has registers — an empty list means the configuration
# was never imported, which is not the same as "nothing configured yet"
curl -s -u admin:admin "http://localhost:8616/apps/openregister/api/registers" | head -c 300
```

## Changing the defaults

The port and every version are overridable:

```bash
DEMO_PORT=9000 \
KEEPIQ_VERSION=1.2.3 \
docker compose -f keepiq-compose.yaml up -d
```

Leaving a version empty resolves the newest release for that app, pre-releases included — which is what most Connext apps still ship, so that is the default.

## Tearing it down

```bash
# Stop, keep the data
docker compose -f keepiq-compose.yaml down

# Stop and delete everything, including the database
docker compose -f keepiq-compose.yaml down -v
```

## What this is not

**It is not a development environment, and it cannot be turned into one by adding a bind mount.**

Nextcloud installs and updates an app by deleting the app directory and extracting a fresh archive over it. Point that at a checkout and an app-store update will delete your working tree — measured on a development machine on 27 August 2026, where `\OC\Updater::upgradeAppStoreApp` fired on a container restart and removed every top-level file from a bind-mounted checkout, including its `.git` directory. Only the subdirectories it lacked permission to unlink survived.

So this compose keeps its apps in a named volume and installs them from release archives. That also happens to be the only thing that works: a release archive is a **complete** app carrying `vendor/` and the built `js/` bundle, while a `git clone` carries neither — and a Nextcloud app with no `vendor/` does not fail loudly. It warns once and keeps loading, so the app appears installed while every service that needs a dependency is quietly absent.

To work *on* these apps rather than *with* them, use the development environment instead.

## Troubleshooting

**`app-installer` exits non-zero.** It could not download an archive. Check the log for the URL it tried; the most common cause is a pinned version with no matching release.

**It stops with `openregister missing; aborting`.** Deliberate. Every other app declares registers against OpenRegister, so a stack without it would start and then fail in a dozen confusing ways instead of one clear one.

**The UI renders unthemed.** Thematiq is not installed or not enabled. Expected, and cosmetic — the theme resolver renders unthemed rather than wrong when it is absent.

**Everything returns 404 or a maintenance page after a restart.** Nextcloud is waiting for an upgrade. Run `docker compose -f keepiq-compose.yaml exec -u www-data nextcloud php occ upgrade`.
Loading
Loading