Waggle is een persoonlijk command-centrum voor bewaarde Mattermost-berichten. https://waggle.rijksapp.nl/
  • Python 56%
  • TypeScript 28.2%
  • Vue 14%
  • CSS 0.7%
  • Shell 0.4%
  • Other 0.6%
Find a file
Robbert Bos 16cdbb7e48
All checks were successful
CI / release-scripts (pull_request) Successful in 5s
security-scan / SBOM (trivy) (pull_request) Successful in 9s
security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 14s
security-scan / JS SCA (npm audit) (pull_request) Successful in 15s
security-scan / Python SAST (bandit) (pull_request) Successful in 17s
security-scan / Python SCA (pip-audit) (pull_request) Successful in 36s
test-build / build (frontend) (pull_request) Successful in 57s
CI / pre-commit (pull_request) Successful in 47s
test-build / build (backend) (pull_request) Successful in 58s
test-build / build (pull_request) Successful in 0s
CI / frontend-test (pull_request) Successful in 1m3s
CI / e2e (pull_request) Successful in 2m9s
CI / backend-test (pull_request) Successful in 2m14s
CI / release-scripts (push) Successful in 5s
security-scan / SBOM (trivy) (push) Successful in 11s
security-scan / Filesystem scan (trivy fs) (push) Successful in 14s
security-scan / JS SCA (npm audit) (push) Successful in 16s
security-scan / Python SAST (bandit) (push) Successful in 17s
security-scan / Python SCA (pip-audit) (push) Successful in 34s
CI / pre-commit (push) Successful in 48s
publish-main / build (frontend) (push) Successful in 59s
publish-main / build (backend) (push) Successful in 1m3s
publish-main / build (push) Successful in 0s
CI / frontend-test (push) Successful in 1m4s
CI / e2e (push) Successful in 2m9s
CI / backend-test (push) Successful in 2m17s
Merge remote-tracking branch 'origin/main' into typed-source-data
# Conflicts:
#	CHANGELOG.md
#	docs/wdr/0004-generated-api-types.md
2026-08-10 19:48:39 +02:00
.claude Settings: point a failed save at the field that caused it 2026-08-09 12:52:06 +00:00
.forgejo Update CI images and actions 2026-08-10 14:48:05 +00:00
backend Merge remote-tracking branch 'origin/main' into typed-source-data 2026-08-10 19:48:39 +02:00
containers Update CI images and actions 2026-08-10 14:48:05 +00:00
docs Merge remote-tracking branch 'origin/main' into typed-source-data 2026-08-10 19:48:39 +02:00
frontend Merge remote-tracking branch 'origin/main' into typed-source-data 2026-08-10 19:48:39 +02:00
.gitignore Generate the frontend API types from the OpenAPI document 2026-08-09 12:27:03 +00:00
.gitleaks.toml Initial commit 2026-05-11 17:09:24 +02:00
.pre-commit-config.yaml Generate the frontend API types from the OpenAPI document 2026-08-09 12:27:03 +00:00
CHANGELOG.md Merge remote-tracking branch 'origin/main' into typed-source-data 2026-08-10 19:48:39 +02:00
CODE_OF_CONDUCT.md Documentation in English, interface stays Dutch 2026-08-07 21:36:16 +00:00
compose.dev.yaml Update quay.io/keycloak/keycloak:26.7 Docker digest to f1f1f01 2026-08-06 06:34:49 +00:00
CONTRIBUTING.md Documentation in English, interface stays Dutch 2026-08-07 21:36:16 +00:00
justfile Generate the frontend API types from the OpenAPI document 2026-08-09 12:27:03 +00:00
LICENSE Initial commit 2026-05-11 17:09:24 +02:00
publiccode.yml Documentation in English, interface stays Dutch 2026-08-07 21:36:16 +00:00
README.md Raise the container images to the Python 3.14.7 floor 2026-08-08 22:33:37 +02:00
renovate.json Lift the linkify-it 5.x hold that markdown-it 15 outgrew 2026-08-08 20:26:16 +00:00
SECURITY.md Documentation in English, interface stays Dutch 2026-08-07 21:36:16 +00:00

Waggle

A personal command centre for saved Mattermost messages.

EXPERIMENTAL - NOT PRODUCTION READY

Waggle is an individual experiment by an employee of the Dutch central government, published as open source for learning, sharing and discussion.

It is known not to meet (yet) the requirements and standards that the Dutch government sets for production software, including but not limited to:

  • Information security: no BIO/ISO 27001 assessment (BIO is the Dutch government baseline for information security), no penetration test, no formal threat-model review.
  • Privacy: no DPIA carried out; not assessed for GDPR suitability. Not suitable for processing special-category or otherwise sensitive personal data.
  • Accessibility: not tested against WCAG 2.2 / EN 301 549.
  • Standards: not assessed against the NeRDS guidelines or the "comply or explain" standards of Forum Standaardisatie.
  • Operations: no formal maintenance, no SLA, no support channel, no continuity guarantee. It may break or stop without notice.
  • Architecture: single-user, local deploy. No scalability, no high availability, no audit logging, no role-based access control.

Use at your own risk and for exploratory purposes only. Contributions, feedback and issues are very welcome - this is a learning environment.

What does Waggle do?

  • Imports your saved (flagged) Mattermost posts into a Focus view with five sections (Doe nu / Nieuw / Backlog / Bubble up in de toekomst / Voltooid) plus a separate reading list.
  • Completing a card automatically unsaves the message in Mattermost (with retry/backoff).
  • An external unsave in Mattermost automatically completes the card: one mental model, one source of truth.
  • Rich preview per card: markdown body, attachments (images in a lightbox, files as chips), author avatar, thread context, notes.
  • Bubble-up: defer a card to a moment in the future.
  • Blocked flag: mark that you are waiting on someone else.
  • A "Nieuw" badge on cards you have not looked at yet, dark/light theme, full keyboard navigation.

Note that the user interface is in Dutch; the section names above are the literal labels in the app.

Architecture

  • Backend: Python 3.14 + FastAPI + SQLAlchemy async + Postgres (dev and prod). Tokens encrypted at rest with Fernet (HKDF domain-separated key). Auth via a signed session cookie. Rate limiting + Origin check as CSRF protection.
  • Frontend: Vue 3.5 (Composition API + <script setup>) + Vite + TypeScript + TanStack Vue Query + Vue Router. NLDD design system (Lit web components + design tokens, OKLCH, dark/light).
  • Sync: an asyncio loop inside the FastAPI lifespan, concurrency guarded by an asyncio.Lock. Default interval 5 min (configurable 1-60 min).

Dev quickstart

Requires: Postgres running (see "Local dev stack" below) and WAGGLE_DATABASE_URL set in .env. The backend has no fallback database and will not start without it.

# Backend
cd backend
uv sync
uv run waggle generate-secrets      # → WAGGLE_SECRET_KEY=...
# Put the value in a local .env file next to pyproject.toml.
# Login goes through OIDC (start Keycloak via compose.dev.yaml) or through the
# dev-only synthetic-auth fallback (WAGGLE_DEV_SYNTHETIC_AUTH=1).
uv run uvicorn waggle.main:app --reload --port 8000
# Frontend (separate terminal)
cd frontend
npm install
npm run dev     # → http://localhost:5173

Or everything through the justfile:

just dev-backend     # backend with reload on :8000
just dev-frontend    # vite dev server on :5173
just preview         # builds the frontend + starts the backend serving static on :8090
just urls

Local dev stack: Keycloak + Postgres

compose.dev.yaml starts two services for local development:

  • Keycloak (port 8082) - OIDC provider with the waggle-dev realm preloaded. Replaces the external ZAD Keycloak so you can work offline.
  • Postgres 17 (port 5432) - the same engine that runs on ZAD. Required: the backend has no fallback database and will not start without WAGGLE_DATABASE_URL.

We use podman compose (same compose spec as docker, no daemon required). docker compose works too.

# Start the whole stack (Keycloak + Postgres)
podman compose -f compose.dev.yaml up -d

# Or just Postgres (needed to start the backend or run the tests)
podman compose -f compose.dev.yaml up -d postgres

# By default Keycloak binds to localhost:8082 and Postgres to localhost:5432.
# Different host or port? Set KC_HOSTNAME, KC_PORT or PG_PORT as shell env vars.
# The dev realm uses wildcard redirect URIs, so extra hosts work without
# changing the realm; KC_HOSTNAME only steers the issuer claim in tokens.
KC_PORT=8085 PG_PORT=15432 podman compose -f compose.dev.yaml up -d

Running the backend against Postgres:

# In backend/.env
WAGGLE_DATABASE_URL=postgresql+asyncpg://waggle:waggle@localhost:5432/waggle

Alembic applies the schema automatically on first startup (see backend/alembic/).

Keycloak is available once its healthcheck passes (~30-60 sec):

URL (default) Description
http://localhost:8082/admin/master/console/ Realm admin (admin / admin)
http://localhost:8082/realms/waggle-dev/.well-known/openid-configuration OIDC discovery endpoint

Test users (both with password welkom123):

  • sam@example.com
  • noor@example.com

Backend .env setup (see also backend/.env.example):

WAGGLE_OIDC_ISSUER=http://localhost:8082/realms/waggle-dev
WAGGLE_OIDC_CLIENT_ID=waggle
WAGGLE_OIDC_CLIENT_SECRET=dev-not-secret
WAGGLE_PUBLIC_HOST=http://localhost:5173
WAGGLE_DEV_SYNTHETIC_AUTH=0

Verification once Keycloak has started:

curl -sf http://localhost:8082/realms/waggle-dev/.well-known/openid-configuration \
  | python3 -c "import json,sys; d=json.load(sys.stdin); print(d['issuer']); print(d['token_endpoint'])"
# Expected:
# http://localhost:8082/realms/waggle-dev
# http://localhost:8082/realms/waggle-dev/protocol/openid-connect/token

Stopping:

podman compose -f compose.dev.yaml down

Connecting a Mattermost token

Waggle needs a Mattermost token. There are two routes, depending on how your Mattermost instance is configured.

Route A - Personal access token (preferred)

Works when the Mattermost administrator has enabled personal access tokens.

  1. Log in to your Mattermost environment in the browser.
  2. Click your profile picture in the top left and choose Profile.
  3. Open the Security tab (in older versions: Account Settings → Security).
  4. Scroll to Personal Access Tokens and click Create.
  5. Give the token a name (e.g. Waggle) and click Create.
  6. Copy the Access Token that is shown (not the Token ID).
  7. Paste the token into Waggle (Instellingen → Mattermost) and save.

Works on instances where personal access tokens are disabled (among them the Digilab instance).

  1. Log in to Mattermost in your browser.
  2. Open the developer tools (F12 or Ctrl/⌘+Shift+I).
  3. Go to Application (Chrome/Edge) or Storage (Firefox) → Cookies and select your Mattermost domain.
  4. Find the MMAUTHTOKEN cookie and copy its value.
  5. Paste that value into the Token field in Waggle.

The session token expires when you log out of Mattermost or when the session ends; you have to fetch it again after that.

Tokens are stored encrypted (Fernet, HKDF-derived key).

Tests

cd backend && uv run pytest -x -q     # backend pytest (needs Postgres)
cd frontend && npm run typecheck      # frontend typecheck
cd frontend && npm run build          # frontend build (runs typecheck first)

npm run typecheck is vue-tsc. Do not substitute npx tsc --noEmit: plain tsc does not look inside .vue templates, so it reports success on an invalid component prop.

Through the justfile:

just test       # backend pytest + frontend build

Building a preview

just preview     # builds the frontend + starts the backend serving static on :8090

Requires WAGGLE_SECRET_KEY in .env or the environment.

Deploy

For the ZAD deployment (waggle.rijksapp.nl), see docs/deployment.md. Platform findings and open questions are in docs/zad-platform.md; decisions that are expensive to reverse are recorded in docs/wdr/.

Licence

Open source under the EUPL-1.2. See publiccode.yml for machine-readable metadata.

Developed and maintained by Robbert Bos (robbert.bos@rijksoverheid.nl) - see the disclaimer at the top of this file for context.