diff --git a/.forgejo/workflows/documentation.yml b/.forgejo/workflows/documentation.yml deleted file mode 100644 index 5ccba1ce2..000000000 --- a/.forgejo/workflows/documentation.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Publish docs - -# Docs deploy ONLY from the dedicated `documentation` branch — decoupled from main/development -# so doc edits never trigger releases and code releases never trigger doc builds. No cron. -on: - push: - branches: [documentation] - pull_request: - branches: [documentation] - workflow_dispatch: - -jobs: - build: - uses: Conduction/.github/.forgejo/workflows/documentation-build.yml@main - with: - source-folder: docs - secrets: inherit - - deploy: - needs: build - if: github.event_name != 'pull_request' - uses: Conduction/.github/.forgejo/workflows/documentation-deploy.yml@main - with: - cf-project-name: launchpad-docs - secrets: inherit diff --git a/.forgejo/workflows/l10n-parity.yml b/.forgejo/workflows/l10n-parity.yml deleted file mode 100644 index 3fbbf583a..000000000 --- a/.forgejo/workflows/l10n-parity.yml +++ /dev/null @@ -1,25 +0,0 @@ -# Translation-PARITY hard gate. -# -# Asserts every required locale (nl/de/fr/es/it) is at full parity with the -# English source — no missing keys, no empty values — for both the frontend -# (l10n/*.js) and backend (l10n/*.json) translation sets. Without this, a new -# English string ships and the other languages silently fall back to English. -# Pure Node, no NC, no npm install. -name: l10n-parity - -on: - pull_request: - push: - branches: [main, master, development] - -jobs: - l10n-parity: - name: l10n translation parity (nl/de/fr/es/it) - runs-on: codeberg-small - container: - image: node:20-alpine - steps: - - name: Checkout - uses: https://code.forgejo.org/actions/checkout@v4 - - name: Assert every required locale is at full parity - run: node tests/l10n/check-l10n-parity.js diff --git a/.forgejo/workflows/pre-merge-check-strict.yaml b/.forgejo/workflows/pre-merge-check-strict.yaml deleted file mode 100644 index 863f2b03e..000000000 --- a/.forgejo/workflows/pre-merge-check-strict.yaml +++ /dev/null @@ -1,70 +0,0 @@ -# Pre-merge quality gate — enforced lint + phpcs + all Hydra gates on every PR. -# Required status check on protected branches. -# -# Runner/container mirror the proven release-semrel workflow: codeberg-medium + -# official php:8.3-cli + a base-tooling step. The old code.forgejo.org/oci/ci-php:8.3 -# image 404s ("manifest unknown"), which fast-failed every run at container-pull. -# -# The gate runs `composer lint` + `composer phpcs` directly: check:strict's -# psalm/phpstan/phpmd/test:all are wrapped in `|| echo skipping` so they never -# affect pass/fail (ADR-022 parks static analysis), and running them on the medium -# runner OOMs it. lint+phpcs is the identical enforced gate, fast and deterministic. - -name: pre-merge-check-strict - -on: - pull_request: - branches: - - development - - main - - beta - -jobs: - quality-gates: - runs-on: codeberg-medium - container: - image: php:8.3-cli - timeout-minutes: 15 - steps: - - name: Install base tooling - run: | - apt-get update - apt-get install -y --no-install-recommends \ - git curl ca-certificates gnupg jq unzip zip \ - libzip-dev libpng-dev python3 - # Node is required by actions/checkout@v4 (a JS action) which runs - # inside this php:8.3-cli container; the stock image ships no node. - curl -fsSL https://deb.nodesource.com/setup_20.x | bash - - apt-get install -y --no-install-recommends nodejs - docker-php-ext-install -j"$(nproc)" zip gd - curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer - - - name: Checkout PR - uses: https://github.com/actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Install composer deps - run: composer install --no-interaction --no-progress --prefer-dist --ignore-platform-reqs - - - name: Run lint + phpcs (the enforced gate) - run: | - composer lint - composer phpcs - - - name: Clone Hydra (for gate runner) - uses: https://github.com/actions/checkout@v4 - with: - repository: Conduction/hydra - ref: development - path: .hydra - - - name: Run all Hydra gates (diff-scoped per ADR-020) - run: | - git fetch origin ${{ github.base_ref }}:${{ github.base_ref }} || true - bash .hydra/scripts/run-hydra-gates.sh --scope-to-diff --base origin/${{ github.base_ref }} . - - - name: Gate-19 e2e coverage report (informational) - if: always() - run: | - python3 .hydra/scripts/lib/check_e2e_coverage.py . --mode report || true diff --git a/.forgejo/workflows/release-beta.yml b/.forgejo/workflows/release-beta.yml deleted file mode 100644 index ed8215b18..000000000 --- a/.forgejo/workflows/release-beta.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: Beta Release - -on: - push: - branches: [beta] - workflow_dispatch: - -jobs: - release: - uses: Conduction/.github/.forgejo/workflows/release-semrel-beta.yml@main - with: - app-name: launchpad - secrets: inherit diff --git a/.forgejo/workflows/release-stable.yml b/.forgejo/workflows/release-stable.yml deleted file mode 100644 index 2f54bf1eb..000000000 --- a/.forgejo/workflows/release-stable.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: Stable Release - -on: - push: - branches: [main] - workflow_dispatch: - -jobs: - release: - uses: Conduction/.github/.forgejo/workflows/release-stable.yml@main - with: - app-name: launchpad - secrets: - CODEBERG_TOKEN: ${{ secrets.CODEBERG_TOKEN }} - NEXTCLOUD_SIGNING_KEY: ${{ secrets.NEXTCLOUD_SIGNING_KEY }} - NEXTCLOUD_SIGNING_CERT: ${{ secrets.NEXTCLOUD_SIGNING_CERT }} - NEXTCLOUD_APPSTORE_TOKEN: ${{ secrets.NEXTCLOUD_APPSTORE_TOKEN }} diff --git a/.forgejo/issue_template/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml similarity index 100% rename from .forgejo/issue_template/bug-report.yml rename to .github/ISSUE_TEMPLATE/bug-report.yml diff --git a/.forgejo/issue_template/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml similarity index 100% rename from .forgejo/issue_template/feature-request.yml rename to .github/ISSUE_TEMPLATE/feature-request.yml diff --git a/.forgejo/issue_template/technical-task.yml b/.github/ISSUE_TEMPLATE/technical-task.yml similarity index 100% rename from .forgejo/issue_template/technical-task.yml rename to .github/ISSUE_TEMPLATE/technical-task.yml diff --git a/.forgejo/issue_template/user-story.yml b/.github/ISSUE_TEMPLATE/user-story.yml similarity index 100% rename from .forgejo/issue_template/user-story.yml rename to .github/ISSUE_TEMPLATE/user-story.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d33d37d06..1ffe9d367 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,6 +4,7 @@ updates: directory: "/" schedule: interval: "weekly" + target-branch: "development" open-pull-requests-limit: 10 cooldown: default-days: 1 @@ -29,6 +30,7 @@ updates: directory: "/" schedule: interval: "weekly" + target-branch: "development" open-pull-requests-limit: 10 cooldown: default-days: 2 diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 1b06230df..396725e33 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -68,7 +68,47 @@ on: # Proven in openconnector#1158: its first-ever completed `development` push run # (31048998594) executed Coverage Baseline Check, SBOM and Features Extract. concurrency: - group: quality-${{ github.head_ref || github.ref_name }}${{ (github.event_name == 'push' && (github.ref_name == 'main' || github.ref_name == 'development')) && '-push' || '' }} + # SUFFIXED BY EVENT NAME, not just by `-push`. + # + # The previous expression gave a push on `development` its own lane + # (`-push`) but left EVERYTHING ELSE sharing `quality-development` — and + # that is not a quiet lane: `Sync to Beta` keeps a PR open whose head_ref + # IS `development`, so its run computes the same group and is re-triggered + # on every merge. + # + # A `workflow_dispatch` therefore shared a group with that PR and was + # cancelled by it. Measured on shillinq 2026-08-21: dispatch 32487948678 + # cancelled by pull_request run 32490160836 (head_branch `development`). + # A run someone deliberately asked for could essentially never complete. + # + # That reaches past ad-hoc verification: the fleet gate-drift sweep + # (.github#523) dispatches per app with `--ref development`, because + # `schedule:` cannot choose a branch. Under the old group those runs are + # cancelled and report neither pass nor fail — and a routine that produces + # no verdict is indistinguishable from one that never ran. + # + # This is hermiq's form, already live there. Pull requests keep the bare + # group (so a PR still supersedes its own earlier run); push, dispatch and + # schedule each get their own lane. + # + # THE BRANCH RESTRICTION IS GONE, because it contradicted the sentence above. + # + # The suffix used to apply only when `ref_name` was `main` or `development`, + # so on every OTHER branch push and pull_request computed the SAME group — + # and `cancel-in-progress` made them kill each other. That became reachable + # when the push allow-list widened on 2026-08-14 to include `feat/**`, + # `fix/**`, `perf/**`, `refactor/**` and `chore/**`: those branches now get + # both a push run and a pull_request run for one commit. + # + # `quality / Quality Report` is a `needs:`-gated aggregator and reports + # FAILURE when its dependencies are CANCELLED, so the collision shows up as a + # red gate on a PR that was never actually evaluated — and re-running collides + # the same way. Measured on openregister#2821: a push run left queued and a + # pull_request run cancelled, 18 seconds apart, on one commit. + # + # 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 # Permission CEILING for the called quality pipeline. GitHub statically @@ -216,7 +256,12 @@ jobs: # Measured on this tree before enabling: PASSES, 237 of 279 tracked # frontend files in scope (l10n/ and docs/ excluded via .prettierignore / # .gitignore, which prettier 3 also reads). - frontend-checks: '["check:manifest", "format"]' + # `check:schema-l10n` is a RATCHET, not a gate. Every string inside a form + # comes from the schema and is a key in THIS app's catalogue; an absent key + # renders the English source inside an otherwise translated form, silently. + # The fleet had 30,459 such strings, so this records the current count and + # fails only when it GROWS — burning it down stays an ordinary PR. + frontend-checks: '["check:manifest", "format", "check:schema-l10n"]' # ── Coverage ratchet ───────────────────────────────────────────────── # `enable-coverage-guard` defaults to FALSE, which is why both diff --git a/.github/workflows/openspec-sync.yml b/.github/workflows/openspec-sync.yml new file mode 100644 index 000000000..cd5de2c3a --- /dev/null +++ b/.github/workflows/openspec-sync.yml @@ -0,0 +1,51 @@ +name: OpenSpec Sync + +# RESTORED, on evidence that the condition which justified dropping it is gone. +# +# This caller was removed from `development` in #42 on 2026-08-04 for a good +# reason: every run in its history was a startup failure reporting `jobs=0`. +# That is the signature of a reusable workflow that never resolved — no log, no +# step, no verdict, just a permanently red branch. As #42 put it, a +# permanently-red workflow is worse than an absent one, because the next +# genuine failure lands on an already-red branch and cannot be told apart from +# the standing noise. That argument was correct. +# +# What has changed since: +# +# 1. The unresolvable ref was FIXED — on `main`, by 9a695ae on 2026-08-03 +# ("fix all 8 shared-workflow callers on main (wrong org)"), one day +# BEFORE #42 dropped this file from development. The fix and the removal +# crossed: `main` got the repair, `development` got the deletion, and +# because main is 345 commits behind development the two never met. +# +# 2. It has now been observed working. Dispatched against main on +# 2026-08-21, this exact caller ran to success and created 12 OpenSpec +# issues — the first non-zero result this workflow has ever produced. +# +# On the PROJECT_TOKEN: #42 and openregister#2111 both attributed the failure +# to an expired project-board PAT. That is not what blocks it here — launchpad +# has no PROJECT_TOKEN secret at all, and the 2026-08-21 run succeeded anyway. +# The token is for the project BOARD; the issues themselves are written with +# the workflow's own github.token. The line below is kept so the board sync +# starts working by itself if a token is ever added, and is harmless while the +# secret is absent. +# +# Why it belongs on `development` and not only on `main`: the push trigger +# below watches `development`, and GitHub reads the workflow file from the +# branch being pushed. With the file only on main, that trigger could never +# fire — which is why the sync had to be kicked by hand and why only main's 16 +# changes were covered while development's 28 went unsynced. + +on: + push: + branches: [development] + paths: ['openspec/**'] + workflow_dispatch: + +jobs: + sync: + uses: ConductionNL/.github/.github/workflows/openspec-sync.yml@main + with: + app-name: launchpad + secrets: + PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }} diff --git a/.gitignore b/.gitignore index 33e4d687c..5f731da4b 100644 --- a/.gitignore +++ b/.gitignore @@ -75,3 +75,9 @@ bom-npm.cdx.json # Local MCP server configuration — carries API keys, must never be committed. # A live n8n API key reached the tip tree of 37 local branches before this was added. .mcp.json + +# Agent/test scratch and tool caches — generated, never source. +# Added by the 2026-08-25 fleet hygiene sweep (ADR-100 Decision 2). +.stale/ +/.e2e-state/ +.phpunit.cache diff --git a/appinfo/info.xml b/appinfo/info.xml index 2b32e4ecc..20b207678 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -52,25 +52,25 @@ Ideaal voor organisaties die consistente, samengestelde dashboards willen voor h Vrij en open source onder de EUPL-1.2-licentie. ]]> - 1.0.13-unstable.20260820060123 + 1.0.13-unstable.20260825094451 EUPL-1.2 Conduction LaunchPad - https://codeberg.org/Conduction/launchpad - https://codeberg.org/Conduction/launchpad - https://codeberg.org/Conduction/launchpad + https://github.com/ConductionNL/launchpad + https://github.com/ConductionNL/launchpad + https://github.com/ConductionNL/launchpad customization organization dashboard - https://codeberg.org/Conduction/launchpad - https://codeberg.org/Conduction/launchpad/issues - https://codeberg.org/Conduction/launchpad + https://github.com/ConductionNL/launchpad + https://github.com/ConductionNL/launchpad/issues + https://github.com/ConductionNL/launchpad - https://codeberg.org/Conduction/launchpad/raw/branch/main/img/screenshot-dashboard.png - https://codeberg.org/Conduction/launchpad/raw/branch/main/img/screenshot-widgets.png - https://codeberg.org/Conduction/launchpad/raw/branch/main/img/screenshot-admin.png + https://raw.githubusercontent.com/ConductionNL/launchpad/main/img/screenshot-dashboard.png + https://raw.githubusercontent.com/ConductionNL/launchpad/main/img/screenshot-widgets.png + https://raw.githubusercontent.com/ConductionNL/launchpad/main/img/screenshot-admin.png