• v2026.8.17 78347065d9

    v2026.8.17
    All checks were successful
    CI / release-scripts (pull_request) Successful in 8s
    security-scan / SBOM (trivy) (pull_request) Successful in 9s
    security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 16s
    security-scan / JS SCA (npm audit) (pull_request) Successful in 17s
    security-scan / Python SAST (bandit) (pull_request) Successful in 17s
    security-scan / Python SCA (pip-audit) (pull_request) Successful in 40s
    test-build / build (frontend) (pull_request) Successful in 58s
    CI / pre-commit (pull_request) Successful in 59s
    test-build / build (backend) (pull_request) Successful in 1m5s
    CI / frontend-test (pull_request) Successful in 1m6s
    test-build / build (pull_request) Successful in 0s
    CI / backend-test (pull_request) Successful in 2m34s
    CI / e2e (pull_request) Successful in 3m46s
    CI / release-scripts (push) Successful in 6s
    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 17s
    security-scan / Python SAST (bandit) (push) Successful in 19s
    security-scan / Python SCA (pip-audit) (push) Successful in 39s
    CI / pre-commit (push) Successful in 1m1s
    publish-main / build (frontend) (push) Successful in 1m0s
    CI / frontend-test (push) Successful in 1m5s
    publish-main / build (backend) (push) Successful in 1m14s
    publish-main / build (push) Successful in 0s
    CI / backend-test (push) Successful in 2m34s
    CI / e2e (push) Successful in 3m49s
    release-and-deploy / release-scripts (push) Successful in 5s
    release-and-deploy / release (push) Successful in 20s
    release-and-deploy / pre-commit (push) Successful in 57s
    release-and-deploy / build (frontend) (push) Successful in 1m0s
    release-and-deploy / frontend-test (push) Successful in 1m0s
    release-and-deploy / build (backend) (push) Successful in 1m10s
    release-and-deploy / build (push) Successful in 0s
    release-and-deploy / backend-test (push) Successful in 2m31s
    release-and-deploy / e2e (push) Successful in 3m42s
    release-and-deploy / ci (push) Successful in 0s
    release-and-deploy / deploy (push) Successful in 1m56s
    Stable

    robbertbos released this 2026-08-17 19:38:04 +00:00 | 1 commits to main since this release

    Replying in the thread or in the channel

    • The chevron next to "Verzend" opens one panel: the destination (thread or
      channel) above the scheduling options. The button then reads "Verzend in
      kanaal", and the choice returns to the thread every time.
    • "Markeer kaart als voltooid na verzenden" now applies to "Verzend" too, not
      only to a scheduled send. The card completes once the send has gone out, so a
      failure or an undo leaves it open.
    • A scheduled message keeps its destination, and Uitgaande post names it.
    • A channel is named in words rather than with a sigil: "In kanaal Town
      Square", "Aan @noor". The card breadcrumb drops its "#" too, so a
      screenreader hears "kanaal X" instead of "#x".
    • Fixed: Escape in the scheduling popover closed the whole card.

    Preview for text attachments

    • A markdown attachment opens in a preview instead of downloading, with a
      "Weergave"/"Broncode" switch.
    • Other text files (.txt, .csv, .json, code, logs) open straight in the source
      view, syntax-highlighted where a grammar exists. Classified on the extension
      too, because Mattermost reports many of them as octet-stream.
    • Above 1 MB the preview asks first and names the size; past 5 MB it fetches
      only the beginning, so a big file is never transferred whole.
    • HTML, PDF and text previews now share one window instead of three
      hand-rolled ones.

    Cards load faster, and show a skeleton

    • A loading card shows placeholder bars instead of a half-filled card, and its
      quick actions and status tags wait for the message.
    • Takes it longer than a second, an activity indicator covers and dims the
      list. Both follow NLDD's loading guidelines, which rule out a shimmer.
    • A message that fails to load says "Bericht kan niet worden geladen" instead
      of waiting on skeleton bars forever; the card's actions stay usable.
    • Channel and team labels come from two collection endpoints instead of one
      call per channel: four round trips per linked server, whatever the number of
      cards. A few hundred cards took over ten seconds to paint before.
    • Those labels are now cached for an hour across requests; the old per-client
      cache never hit, because the client is built fresh per request.
    • Fixed: a Mattermost token expiring during team lookup read as a server error
      rather than a token problem.

    Reading the messages around a card

    • Opening the panel no longer hides the message you were reading.
    • One centred "Berichten laden..." indicator instead of a spinner and a label
      scattered across the width, and the panel settles once instead of twice.
    • A load-more button names its direction while it loads instead of shrinking to
      "Laden...".

    Under the hood

    • An unreachable Mattermost no longer costs one hanging avatar request per
      author: the first failure is remembered for a minute, the timeout drops from
      15 to 3 seconds, and an upstream failure reads as 502 rather than 500.
    • An avatar reply that is not an image is refused rather than replaced by a
      transparent pixel, which showed as an empty circle where initials belong.
    • The dev Mattermost mock is installed at startup instead of being reached for
      per request: waggle no longer names devtools in the request path.
    • The mock can fake a per-call latency (WAGGLE_DEV_MM_MOCK_LATENCY_MS),
      answers posts/ids for ids it never created, serves /users/me/channels and
      honours Range.
    • The dev seed writes refs-only cards like production, instead of a snapshot
      nothing reads that showed as one repeated message on every card.
    • Renovate 44.31.0; fresh nginx-unprivileged digest for the frontend image.

    Container images

  • v2026.8.16 9472d8d78b

    v2026.8.16
    All checks were successful
    CI / release-scripts (pull_request) Successful in 7s
    security-scan / SBOM (trivy) (pull_request) Successful in 10s
    security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 15s
    security-scan / JS SCA (npm audit) (pull_request) Successful in 16s
    security-scan / Python SAST (bandit) (pull_request) Successful in 18s
    security-scan / Python SCA (pip-audit) (pull_request) Successful in 37s
    test-build / build (frontend) (pull_request) Successful in 56s
    CI / pre-commit (pull_request) Successful in 56s
    CI / frontend-test (pull_request) Successful in 1m1s
    test-build / build (backend) (pull_request) Successful in 1m28s
    test-build / build (pull_request) Successful in 0s
    CI / backend-test (pull_request) Successful in 2m20s
    CI / e2e (pull_request) Successful in 3m28s
    CI / release-scripts (push) Successful in 6s
    security-scan / SBOM (trivy) (push) Successful in 10s
    security-scan / Filesystem scan (trivy fs) (push) Successful in 14s
    security-scan / JS SCA (npm audit) (push) Successful in 15s
    security-scan / Python SAST (bandit) (push) Successful in 16s
    security-scan / Python SCA (pip-audit) (push) Successful in 34s
    CI / pre-commit (push) Successful in 56s
    publish-main / build (frontend) (push) Successful in 56s
    CI / frontend-test (push) Successful in 1m6s
    publish-main / build (backend) (push) Successful in 1m10s
    publish-main / build (push) Successful in 0s
    CI / backend-test (push) Successful in 2m19s
    CI / e2e (push) Successful in 3m26s
    release-and-deploy / release-scripts (push) Successful in 6s
    release-and-deploy / release (push) Successful in 18s
    release-and-deploy / pre-commit (push) Successful in 57s
    release-and-deploy / frontend-test (push) Successful in 58s
    release-and-deploy / build (frontend) (push) Successful in 1m9s
    release-and-deploy / build (backend) (push) Successful in 1m13s
    release-and-deploy / build (push) Successful in 0s
    release-and-deploy / backend-test (push) Successful in 2m19s
    release-and-deploy / e2e (push) Successful in 3m25s
    release-and-deploy / ci (push) Successful in 0s
    release-and-deploy / deploy (push) Successful in 1m49s
    Stable

    robbertbos released this 2026-08-16 21:02:14 +00:00 | 14 commits to main since this release

    Note when rolling out

    • The migration converts cards.source_data, cards.notes and
      outgoing_posts.content to encrypted bytea in place. Not rolling-safe:
      stop the old pod before the new one boots, and take a database backup
      first - reading the converted columns afterwards requires the
      WAGGLE_SECRET_KEY the migration ran with.
    • A second migration then strips the message, author and attachments out of
      cards.source_data. One-way by design: the downgrade cannot put them back.
    • Typing ~ offers your channels, most recently linked first, and inserts a
      reference to the one you pick.
    • Channels from your other teams are in the list too, marked with their team.
      They get a full link, because Mattermost only resolves a ~name inside the
      team of the message.
    • @ in a new message without a recipient now says "Kies eerst een kanaal bij
      Aan" rather than inviting you to keep typing.

    Release notes in the app

    • Returning users get a "Nieuw in Waggle" banner above every page, folded shut,
      listing the releases they missed by date. Dismissing it is permanent and
      follows the account, not the browser.
    • Every missed release is in that one expand. A button underneath fetches the
      releases before them, one at a time.
    • Notes are Dutch markdown per release in frontend/src/content/releases/; a
      release without a file shows no banner, and a new account sees nothing. The
      release job fails while unreleased.md has not been renamed to its version.

    Under the hood

    • Message snapshots, notes and outgoing drafts are encrypted at rest:
      Fernet under two new HKDF domains, covered by waggle rotate-keys.
    • A card stores references only - post, channel and permalink. The message,
      author and attachments come from Mattermost per request, so an edited post
      no longer needs a resync and nothing of it sits in the database.
    • A card whose post Mattermost no longer has reads "Bericht is niet meer
      beschikbaar in Mattermost" and keeps its status, planning and notes. If the
      fetch itself fails, the list says so once, with "Opnieuw proberen".
    • Cache and log hardening: API responses default to no-store, attachments cache
      only as inline images for an hour, typed search text moved from URLs into POST
      bodies, and the production access log is off.
    • Error paths stopped echoing content: database errors hide bind parameters,
      422 responses drop the submitted input, the rate limiter logs a hashed key.
    • Logout clears the browser's cache and storage (Clear-Site-Data) plus
      localStorage traces; sync_logs age out after 90 days.
    • The pod writes its own scrubbed request log (status, duration, pseudonymous
      user/session ids; no IPs or query strings) and sessions record
      last_seen_at for incident reconstruction.
    • NLDD design system 0.8.82. Enter submits the account forms again, the
      retention dropdowns are now named by the label above them instead of by a
      summary that shared no words with it, and the import success notice is an
      nldd-banner rather than a hand-rolled one.
    • Postgres 17 digest bumped to e384114.

    Container images

  • v2026.8.15 63cb8fffce

    v2026.8.15
    All checks were successful
    CI / release-scripts (pull_request) Successful in 6s
    security-scan / SBOM (trivy) (pull_request) Successful in 10s
    security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 14s
    security-scan / JS SCA (npm audit) (pull_request) Successful in 14s
    security-scan / Python SAST (bandit) (pull_request) Successful in 19s
    security-scan / Python SCA (pip-audit) (pull_request) Successful in 36s
    CI / pre-commit (pull_request) Successful in 56s
    test-build / build (frontend) (pull_request) Successful in 59s
    CI / frontend-test (pull_request) Successful in 59s
    test-build / build (backend) (pull_request) Successful in 1m5s
    test-build / build (pull_request) Successful in 0s
    CI / backend-test (pull_request) Successful in 2m17s
    CI / e2e (pull_request) Successful in 3m24s
    CI / release-scripts (push) Successful in 10s
    security-scan / Filesystem scan (trivy fs) (push) Successful in 23s
    security-scan / Python SAST (bandit) (push) Successful in 37s
    security-scan / SBOM (trivy) (push) Successful in 13s
    security-scan / JS SCA (npm audit) (push) Successful in 29s
    security-scan / Python SCA (pip-audit) (push) Successful in 53s
    release-and-deploy / release (push) Successful in 35s
    release-and-deploy / release-scripts (push) Successful in 11s
    publish-main / build (frontend) (push) Successful in 2m13s
    CI / pre-commit (push) Successful in 2m42s
    release-and-deploy / frontend-test (push) Successful in 2m22s
    publish-main / build (backend) (push) Successful in 3m26s
    publish-main / build (push) Successful in 0s
    release-and-deploy / build (frontend) (push) Successful in 2m57s
    release-and-deploy / pre-commit (push) Successful in 3m21s
    release-and-deploy / build (backend) (push) Successful in 3m47s
    release-and-deploy / build (push) Successful in 0s
    CI / frontend-test (push) Successful in 2m17s
    CI / backend-test (push) Successful in 9m15s
    CI / e2e (push) Successful in 9m42s
    release-and-deploy / backend-test (push) Successful in 8m57s
    release-and-deploy / e2e (push) Successful in 8m48s
    release-and-deploy / ci (push) Successful in 0s
    release-and-deploy / deploy (push) Successful in 1m49s
    Stable

    robbertbos released this 2026-08-15 20:19:50 +00:00 | 23 commits to main since this release

    The composer edits markdown as text

    • Reply, new message and notes run on the design system's editor instead of
      Tiptap. You edit the markdown itself: the syntax shows on the line you are on
      and is hidden elsewhere, the ``` of a code block included.
    • Tab nests a list item and indents inside a code block, Shift+Tab reverses it,
      and Escape leaves the field before a second Escape closes the window.
    • A long message keeps the toolbar and the send button in view: the typing
      surface grows to twenty lines, then scrolls.
    • The link bar lost its "Openen" button - the editor shows one beside the link
      the cursor is in.
    • Fixed along the way, most visibly: the reply box had no accessible name,
      pasting a screenshot over selected text deleted the text, the emoji list and
      picker opened off screen on a scrolled page, and Escape from the link bar's
      buttons closed the whole card. The rest is in git log.
    • The toolbar's line markers (list, task, quote, heading, code block) now work
      on an empty line, switch to another type, and put the cursor after the
      marker instead of in front of it.

    Menu and page titles

    • The menu now sits behind a labeled "Menu" button: fixed top right on a wide
      screen, beside the wordmark on a phone. Its items are real links
      (cmd/ctrl-click opens a new tab), with the overview pages grouped under
      "Overzichten".
    • The wordmark is a plain link to the Inbox above the page, and it scrolls
      away with the content.
    • Inbox, Leeslijst and Instellingen have a visible page title, and the browser
      tab follows the page ("Inbox - Waggle").
    • Every page shares one left edge: titles, search, cards and the settings
      sections all follow the design system's page gutter.
    • The inbox tour lost its menu step, and no longer jumps while the banners
      load, ends on a click beside it, races through steps, or hides its card
      steps under the modal.

    Channel window

    • Every root in the channel window now shows its reply count and unfolds in
      place - until now only the saved message's own thread did.

    Fixed

    • Moving a card within its own list no longer drifts a slot off after half a
      dozen moves into the same spot: positions are now also redistributed on a
      reorder, not only when a card changes list.
    • A card moved with the keyboard now scrolls fully back into view instead of
      stopping half under the fold.
    • Text in a dialog is the content colour again instead of pure black or white.
      A native <dialog> carries the UA's color: CanvasText, which cut the
      document colour off above every overlay.

    Under the hood

    • The composer has 21 props instead of 27, and the formatting toolbar gets its
      own tests.
    • The app loads 39% less JavaScript (919 kB gzip to 556 kB), because the design
      system is imported per component instead of as one chunk; see WDR-0005.
    • The Mattermost client now handles an unexpected response body the same way in
      get_thread, get_post and get_channel_posts. The first two coerced before
      they checked, which made the empty-thread branch unreachable.
    • The pinned-trivy-binary variables are scoped to the SBOM job that uses them,
      so a TRIVY_-prefixed name no longer reaches the containerised filesystem
      scan, where it lands in trivy's own config namespace.
    • CI downloads the pinned uv and trivy binaries with --fail and three retries.
      Without --fail an HTTP error was written to the tarball and reported as a
      checksum mismatch, so a rate-limited download looked like a tampered binary.
    • The test schema is now keyed on the checkout as well as the xdist worker, so
      two worktrees running the suite against the same database no longer drop each
      other's tables mid-run.
    • The synthetic dev users, the Mattermost mock, the passwordless-login routes
      and the synthetic-user bootstrap moved to backend/devtools/, which the
      container image does not copy; no dev-only code ships any more.
    • The container build now asserts it: import waggle succeeds and
      import devtools fails in the built image.
    • Dependency bumps: uvicorn 0.52.3, sqlalchemy 2.0.52, ruff 0.16.3, ty 0.0.72,
      the CodeMirror and Lezer packages, vue-tsc 3.3.10, typescript-eslint 8.67 and
      nanoid 3.3.18 (CVE-2026-67213), plus the CI images (uv 0.12.5, trivy 0.74,
      renovate 44.30) and the Postgres 17 digest. Tiptap is gone as a dependency.

    Container images

  • v2026.8.11 7d7c8f76fa

    v2026.8.11
    All checks were successful
    security-scan / Python SCA (pip-audit) (pull_request) Successful in 38s
    security-scan / Python SCA (pip-audit) (push) Successful in 36s
    release-and-deploy / release-scripts (push) Successful in 6s
    release-and-deploy / e2e (push) Successful in 3m27s
    test-build / build (frontend) (pull_request) Successful in 55s
    CI / pre-commit (pull_request) Successful in 1m0s
    CI / frontend-test (pull_request) Successful in 1m0s
    CI / pre-commit (push) Successful in 58s
    publish-main / build (frontend) (push) Successful in 59s
    release-and-deploy / release (push) Successful in 18s
    test-build / build (backend) (pull_request) Successful in 59s
    test-build / build (pull_request) Successful in 0s
    CI / backend-test (pull_request) Successful in 2m17s
    CI / frontend-test (push) Successful in 1m2s
    publish-main / build (backend) (push) Successful in 1m4s
    publish-main / build (push) Successful in 0s
    release-and-deploy / pre-commit (push) Successful in 55s
    CI / e2e (pull_request) Successful in 3m29s
    CI / backend-test (push) Successful in 2m19s
    CI / release-scripts (push) Successful in 6s
    CI / release-scripts (pull_request) Successful in 6s
    security-scan / SBOM (trivy) (push) Successful in 10s
    release-and-deploy / build (frontend) (push) Successful in 58s
    release-and-deploy / frontend-test (push) Successful in 59s
    release-and-deploy / deploy (push) Successful in 2m5s
    security-scan / SBOM (trivy) (pull_request) Successful in 10s
    security-scan / Filesystem scan (trivy fs) (push) Successful in 14s
    security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 13s
    security-scan / JS SCA (npm audit) (push) Successful in 15s
    release-and-deploy / build (backend) (push) Successful in 1m3s
    release-and-deploy / build (push) Successful in 0s
    security-scan / JS SCA (npm audit) (pull_request) Successful in 17s
    security-scan / Python SAST (bandit) (pull_request) Successful in 20s
    security-scan / Python SAST (bandit) (push) Successful in 17s
    CI / e2e (push) Successful in 3m30s
    release-and-deploy / backend-test (push) Successful in 2m15s
    release-and-deploy / ci (push) Successful in 0s
    Stable

    robbertbos released this 2026-08-11 21:23:00 +00:00 | 55 commits to main since this release

    Channel context around a saved message

    • A saved message now offers "Toon eerdere en latere berichten", directly above
      "Antwoord in thread": the messages around it in the channel, five per click in
      either direction. On request you can load more.
    • A thread stays folded there: its root, the saved message under it when that is
      a reply, and one row that opens the rest in place - "Toon 12 antwoorden", or
      "Toon 11 andere antwoorden" when the saved reply is already on screen.
    • A saved reply now appears under its root instead of loose in the timeline.
    • Beside it sits "Toon de thread": the same thread without the channel around
      it. One of the two panels is open at a time.
    • The saved message is marked by its outline alone; the extra "bewaard" tag is
      gone.
    • Reactions work on any message in a card's channel.

    Keyboard shortcuts

    • Move between screens with g then i/l/c/u/n/s (Inbox, Leeslijst, Concepten,
      Uitgaande post, Notities, Instellingen), and through a card list with j/k
      or the arrows.
    • Act on the focused card: v completes it or marks it read, d sends it to
      "Doe nu", l to Backlog, b bubbles it up, w moves it to the other list,
      r replies and m opens it in Mattermost.
    • Every status change can be undone for five seconds, with z or the button on
      the notice.
    • Reorder a card with Alt/Option and an arrow key: the keyboard route to
      what dragging does.
    • Inside an open card, j/k step to the next and previous one without closing
      it, and the status keys move on by themselves.
    • Cmd/Ctrl+Shift+Enter opens the schedule popover from a composer,
      beside send.
    • ? lists every key. "Sneltoetsen met lettertoetsen" in Instellingen switches
      the letter keys off; arrows, Tab, Enter and Esc keep working.

    Instellingen

    • A failed server save now puts the message on the field that caused it, instead
      of in one banner under all three.
    • A base URL that is refused or unreachable used to report "Token is ongeldig"
      on a token that was fine. Those three failures shared one message and are now
      told apart.
    • The recovery code in the import wizard is a normal field, and a code that does
      not decrypt says "De herstelcode klopt niet." at the field itself.
    • In "Account verwijderen", the e-mail field says why the button is disabled.
    • "Thema" and "Sync-interval" are radio buttons instead of dropdowns, so every
      option is visible without opening anything. "Sync-interval" is down to three
      steps: "1 minuut", "5 minuten" and "1 uur".
    • A failed save in Voorkeuren or Automatisch opruimen can be retried with
      "Opnieuw proberen".
    • "Opslaan..." sits beside the setting you changed, and only appears when a save
      takes longer than a second. Without a connection it says "Wachten op
      verbinding...".

    Smaller improvements

    • A channel reference now reaches Mattermost as ~town-square instead of
      \~town-square, so it autolinks again. A doubled ~~ stays escaped.
    • A task list no longer gains a blank line between its items, so Mattermost
      renders it tight.
    • Sending, scheduling, saving a draft or uploading an attachment used to surface
      the raw server message ("card not found"). Those are Dutch sentences now.
    • The "Wacht op verzending" section is gone from Inbox and Leeslijst: scheduled
      messages live on Uitgaande post, so they were listed twice. The red notice for
      a failed send stays on both pages.
    • Hints that sat on the native title attribute are now nldd-tooltip: they
      appear on keyboard focus as well as hover, and a screen reader announces them.
      Where a hint only repeated the control's own label it is gone.
    • The send hint names the key your own system uses ("⌘ + Enter" on a Mac,
      "Ctrl + Enter" elsewhere) and says nothing on a phone, where you cannot press
      it. The "…" in a source path and the add-reaction button say what they do.

    Fixed

    • The sync-status endpoint showed every user the last sync time, status and
      error message of whoever synced most recently, instead of their own: it
      depended on the logged-in user but never scoped the query to them.
    • The scheduled-post routes accepted a draft id and acted on it: editing one
      through that route replaced the draft's text and stamped a send time onto a
      row nothing ever sends. Those routes now answer "niet gevonden", as they
      already did for someone else's message.
    • A thread message whose Mattermost timestamp is missing or unreadable no longer
      jumps to the top of the thread, and no longer fails the whole request.
    • Someone who set only a nickname in Mattermost is now shown by that nickname
      everywhere; the thread view used their bare username. Cards already saved keep
      the name they were stored with until the next sync.

    Under the hood

    • A rate-limited response now carries Retry-After and X-RateLimit-*, so a
      client can wait the right number of seconds and slow down before it is refused
      at all.
    • All 22 rate-limited operations declare their 429 and name their budget; a test
      reads the limits back out of the limiter, so the next one cannot arrive
      undocumented.
    • The API reference now lives at /api/docs, where a deployment can reach it,
      renders without a CDN, and describes all 60 operations in eleven groups.
    • The /api prefix is written once, in API_URL_PREFIX, and applied by one
      parent router instead of by each of sixteen. A route mounted outside it fails
      a test.
    • /api/health is no longer in the OpenAPI document: it belongs to the load
      balancer, not to a client.
    • The thread and channel-context endpoints, /api/me/import, /api/config and
      GET /api/auth/me publish response models, so their shapes are generated
      instead of hand-written on the frontend.
    • A card's Mattermost payload (source_data) has a model too, with every field
      optional: older cards keep loading and a missing value is filled in on read.
      An import bundle is checked against it as well, so a wrong-typed payload is
      refused at import instead of breaking the card list afterwards.
    • A Mattermost post whose metadata is not an object no longer fails the thread
      request, a non-string user id no longer fails the response model, and a
      non-string mime_type no longer fails a sync.
    • Verifying a Mattermost token answers 502 when the server cannot be reached. It
      was 422, the same status as an invalid token, which is why one message covered
      both.
    • The frontend's API types are generated from the OpenAPI document
      (just gen-types); CI fails when the committed file and the Pydantic schemas
      disagree. Two hand-written types were wrong: CancellationReason missed a
      value the backend sends, and useSyncStatus had no callers.
    • The twelve closed value lists (card track, cleanup status, outgoing-post
      state, theme, retention period and so on) each live in one place now, read by
      the column, the API schema and a database CHECK alike. Ten value lists that
      were declared and never read are gone.
    • Dialogs are now real browser dialogs. The shared modal shell is built on
      nldd-window, so the backdrop, the focus trap and focus restore come from the
      browser. Everything that has to float above a dialog moved with it, because
      the browser top layer cannot be reached by any z-index.
    • The delete confirm in Instellingen moved onto that shell; the disconnect
      confirm stays on nldd-modal-dialog and now registers with the overlay stack,
      so the single-key shortcuts no longer stay live behind it.
    • Three single-key shortcuts (c, /, ?) each carried their own "is a modal
      open" check that missed a component's shadow root; it is one shared check now.
    • Documentation is English: README, CHANGELOG, docs/**, CONTRIBUTING, SECURITY
      and the code of conduct. The interface stays Dutch. publiccode.yml gained an
      English description.
    • Decisions that are expensive to reverse now live in docs/wdr/ as numbered
      Waggle Decision Records, starting with the NL GOV OAuth deviation, the
      path-based ingress and the documentation language.
    • docs/deployment.md is only a runbook again; the ZAD research moved to
      docs/zad-platform.md, its two go-live checklists became one, and the
      instructions for one release moved to that release's CHANGELOG section.
    • Four documented instructions that no longer worked are corrected: npx tsc
      (which skips .vue templates), per-IP rate limits in SECURITY, the pre-Alembic
      schema flow in the PR checklist, and a HKDF domain table missing one of five.
    • The frontend build was broken: markdown-it 15 imports a named LinkifyIt
      export that only linkify-it 6 has, while an npm override still forced 5.x. The
      hold was meant to last only until markdown-it moved, so it is lifted.
    • The e2e suite drops its throwaway databases again. Each worker created one per
      run and never removed it; 76 had piled up, 641MB.
    • The backend test job runs pytest -n auto again instead of a fixed six
      workers, so the worker count follows the machine it lands on.
    • The backend runs on Python 3.14.7, with the container base image, uv and the
      security-scan image raised to match. The runtime image no longer ships pip.
    • Dependency bumps: cryptography 50, pydantic-settings 2.15, alembic 1.19.1,
      ruff 0.16.2, ty 0.0.69, markdown-it 15, jsdom 30 and NLDD 0.8.79.

    Container images

  • v2026.8.7 397a615d56

    v2026.8.7
    All checks were successful
    CI / release-scripts (pull_request) Successful in 6s
    security-scan / SBOM (trivy) (pull_request) Successful in 9s
    security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 15s
    security-scan / JS SCA (npm audit) (pull_request) Successful in 16s
    security-scan / Python SAST (bandit) (pull_request) Successful in 21s
    CI / pre-commit (pull_request) Successful in 28s
    security-scan / Python SCA (pip-audit) (pull_request) Successful in 37s
    test-build / build (frontend) (pull_request) Successful in 55s
    test-build / build (backend) (pull_request) Successful in 58s
    test-build / build (pull_request) Successful in 0s
    CI / frontend-test (pull_request) Successful in 1m5s
    CI / backend-test (pull_request) Successful in 2m0s
    CI / e2e (pull_request) Successful in 2m10s
    CI / release-scripts (push) Successful in 6s
    security-scan / Python SAST (bandit) (push) Successful in 20s
    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 15s
    CI / pre-commit (push) Successful in 27s
    security-scan / Python SCA (pip-audit) (push) Successful in 38s
    publish-main / build (frontend) (push) Successful in 58s
    publish-main / build (backend) (push) Successful in 1m2s
    publish-main / build (push) Successful in 0s
    CI / frontend-test (push) Successful in 1m4s
    CI / backend-test (push) Successful in 1m58s
    CI / e2e (push) Successful in 2m9s
    release-and-deploy / release-scripts (push) Successful in 5s
    release-and-deploy / pre-commit (push) Successful in 22s
    release-and-deploy / release (push) Successful in 28s
    release-and-deploy / build (frontend) (push) Successful in 56s
    release-and-deploy / frontend-test (push) Successful in 59s
    release-and-deploy / build (backend) (push) Successful in 1m2s
    release-and-deploy / build (push) Successful in 0s
    release-and-deploy / backend-test (push) Successful in 1m55s
    release-and-deploy / e2e (push) Successful in 2m5s
    release-and-deploy / ci (push) Successful in 0s
    release-and-deploy / deploy (push) Successful in 1m54s
    Stable

    robbertbos released this 2026-08-07 19:27:37 +00:00 | 129 commits to main since this release

    Let op bij het uitrollen

    • Deze release vraagt een stop-then-start-deploy, geen rolling update. De
      vorige en de nieuwe versie kunnen niet tegelijk op dezelfde database: zolang
      ze naast elkaar draaien, krijgt elke gebruiker foutmeldingen van de oude
      versie.
    • Draai de twee controlequery's uit docs/deployment.md vooraf (identifiers en
      cleanup_undo_state). Vindt een van beide iets, ruim dat op vóór de deploy -
      de migratie stopt er anders op.
    • Een export uit een oudere versie kan geweigerd worden bij het
      terugzetten.
      Een bundel die op een SQLite-installatie is gemaakt bevat
      tijdstippen zonder tijdzone; de import weigert die nu met
      Input should have timezone info (HTTP 400). docs/deployment.md beschrijft
      hoe je zo'n bundel repareert: de tijdstippen in de JSON een Z geven.

    Nieuw bericht opstellen

    • Je kunt nu nieuwe berichten opstellen en laten afleveren in een kanaal of als
      direct bericht bij een persoon: kies een doel, schrijf je bericht met opmaak,
      @-mentions en bijlagen, en verstuur het direct of plan het in. Openen via
      "Nieuw bericht" in het menu of met de sneltoets c.
    • Een bericht dat je nog niet wilt versturen bewaar je als concept. Je kunt aan
      meerdere concepten tegelijk werken; ze staan onder Concepten, waar je ze weer
      opent. Ook een mislukt gepland bericht pak je daar op om het opnieuw te plannen.
    • Een concept gooi je weg op de Concepten-pagina, of door het helemaal leeg te
      maken - dat laatste geldt ook voor een antwoord in een thread. Je hebt tien
      seconden om dat ongedaan te maken; daarna verdwijnt het concept met zijn
      bijlagen.
    • Je kunt nu ook een bijlage meesturen met een concept of gepland bericht;
      voorheen kon dat alleen bij een direct antwoord.
    • In alle composers toont het kopjesmenu per niveau een icoon, zodat je Kop 1
      tot en met Kop 6 sneller uit elkaar houdt.

    Overzichtspagina's

    • Nieuwe pagina's Concepten, Uitgaande post en Notities (via het menu)
      verzamelen je concepten, geplande en verzonden berichten, en notities op één
      plek.

    Automatisch opruimen

    • Afgehandelde berichten en voltooide of gelezen kaarten worden nu desgewenst
      vanzelf opgeruimd (standaard na 90 dagen; instelbaar op uit, 30, 90, 180 of
      365). Concepten en kaarten met een notitie blijven altijd staan.
    • Bij bestaande accounts staat deze optie standaard uit. Deze accounts krijgen
      een eenmalig bericht dat aanbiedt om het op 90 dagen te zetten.
    • Bijlagen van geannuleerde en mislukte berichten bleven onbeperkt op het volume
      staan. Dat lossen we ook direct op.

    Plannen en uitstellen

    • Bij "Zelf kiezen…" kies je datum en tijd nu met een echte kalender (met
      weeknummers) en een keuzewiel ernaast, in plaats van met twee kale
      invoervelden.
    • De plan- en snooze-popover zijn nu met het toetsenbord te bereiken en te
      bedienen; het waren muis-only dialogen die buiten de focus-trap vielen. De
      focus gaat er bij openen in en keert bij sluiten terug naar de knop, je loopt
      met de pijltjestoetsen door de opties, en Escape sluit alleen de popover, niet
      de kaart eronder (WCAG 2.1.1).
    • De API weigert nu een tijdstip zonder tijdzone, in plaats van er UTC van te
      maken. Een gepland bericht dat om 09:00 werd ingestuurd zonder offset
      vertrok daardoor een tot twee uur te laat, afhankelijk van zomer- of
      wintertijd. De app zelf stuurde altijd al een tijdzone mee; dit raakt eigen
      scripts, handmatig bewerkte import-bestanden en exports uit een oudere versie
      (zie "Let op bij het uitrollen").

    Voorwaarden en welkom

    • Je ziet nu op de voorwaardenpagina wanneer je de voorwaarden accepteerde, en
      kunt ze altijd teruglezen via het menu-item Voorwaarden.
    • De welkomstpagina stopt na het accepteren van de voorwaarden; Mattermost
      koppelen doe je in Instellingen (een banner wijst je daarheen als er nog geen
      server gekoppeld is).

    Toegankelijkheid

    • Nieuwe link "Direct naar de inhoud" bovenaan elke pagina: druk op Tab en je
      slaat het menu over. Ook een schermlezer springt nu direct naar de inhoud.
    • Focus blijft binnen een geopende modal: de focus-trap zag knoppen in
      web-components en de tekst-editor (contenteditable) eerder over het hoofd,
      waardoor Tab naar de achtergrond kon weglopen (WCAG 2.4.3).
    • De sneltoets / opent de zoekbalk niet meer terwijl je typt in een invoerveld
      binnen een web-component (zoals het Mattermost-token-veld), en ook niet meer
      achter een geopende kaart (WCAG 2.1.4).
    • Een reactie op een bericht vertelt nu ook aan een schermlezer of jij hem
      zelf hebt gegeven; dat was alleen aan de kleur te zien.
    • De kaartdetail-modal markeert het paneel zelf als dialoog in plaats van de
      achtergrondlaag, zodat een schermlezer de juiste grens aanhoudt.
    • Instellingen gebruikt nu overal dezelfde opbouw voor een veld: label, hint
      en foutmelding komen uit NLDD. Hints worden daardoor ook echt voorgelezen;
      ze waren eerder alleen zichtbaar.
    • Een mislukte opslag bij Voorkeuren was volledig stil en is nu zichtbaar.
      Een thema dat niet opgeslagen kon worden draait ook weer terug.
    • De vinkjes in de import-tabel heten nu hetzelfde als hun zichtbare label
      ("Toepassen: Thema" in plaats van "Toepassen: theme"), zodat je ze met
      spraakbediening kunt aanwijzen.
    • Bij "Account verwijderen" springt de focus nu echt naar het eerste vinkje;
      dat was de bedoeling maar gebeurde niet. Het e-mailveld doet geen
      spellingscontrole meer.

    Tekst en vormgeving

    • Tekst is groter, gelijkmatiger en staat ruimer op de regel. Berichten,
      uitlegteksten, formulierlabels en knoppen stonden verspreid op 10 tot 16px
      terwijl de pagina op 18px staat; ze volgen nu de basis, met een regelafstand
      van 1.5 in plaats van 1.125. Compacte menu's houden bewust 16px aan, het
      formaat dat NLDD zelf voor dat soort oppervlakken gebruikt.
    • Waggle heeft veel minder eigen ontwerp-tokens: lettergroottes, spacing,
      hoekafronding, letterdiktes, schaduwen, focus-ring en overgangen komen nu uit
      NLDD in plaats van uit een eigen reeks ernaast. Vetgedrukte tekst wordt
      daardoor iets minder zwaar (600 in plaats van 700) en de kleinste iconen
      worden 16px (waren 12-14px).
    • Hoeken en kaders zijn overal gelijk: kaarten, vensters, menu's en zwevende
      panelen (plannen, bron-pad, "Aan") zijn even rond en krijgen alleen schaduw
      en hoekstraal, geen extra haarlijn. Sommige menu's en popovers waren nog
      vierkant.
    • Knoppen, lege toestanden, labels en initialen-cirkels komen nu uit NLDD in
      plaats van uit eigen CSS. De knopjes op kaarten, in het kaartdetail en op de
      rijen van uitgaande post zijn daardoor overal even groot, reageren hetzelfde
      op de muis en zijn met Tab allemaal bereikbaar; een kapotte profielfoto valt
      vanzelf terug op de initialen. Daarmee staat er nergens in de app nog een
      hardgecodeerde lettergrootte.
    • Een paar iconen dragen nu het begrip dat ze bedoelen: een video-bijlage toonde
      een leeg vakje en krijgt een afspeel-driehoek, "Verzend nu" een pijl naar het
      eind in plaats van een terugdraaiende klok, en Backlog een eigen icoon zodat
      het niet meer hetzelfde oogt als Inbox.
    • NLDD-design-system naar 0.8.78: kaarten hebben nu een rand in plaats van een
      schaduw, en foutmeldingen en scheidingslijnen in menu's krijgen meer ruimte.

    Opgelost

    • Waggle gebruikt RijksSans niet meer. Dat font is volgens de voorwaarden van de
      Rijksoverheid voorbehouden aan publicaties van de Rijksoverheid en aan werk in
      haar opdracht; Waggle valt daarbuiten. De app gebruikt nu het systeemlettertype
      en levert de fontbestanden ook niet meer mee. JetBrains Mono (vrij
      gelicentieerd) blijft voor code.
    • Foutmeldingen zijn weer rood. Ze verwezen naar een kleur-token dat niet
      bestaat, waardoor 19 meldingen (banners, toasts, formulierfouten, mislukte
      bijlagen) in gewone tekstkleur stonden en de rode banner-achtergrond helemaal
      wegviel.
    • De opmaak van Mattermost-berichten staat nu op één plek. Zes componenten
      hadden er elk een eigen kopie van, die onderling uit elkaar waren gelopen:
      koppen ontbraken op de inbox-kaart, en de spatiëring verschilde per scherm.
    • De muiscursor volgt nu de NLDD-conventie: knoppen tonen de standaardpijl; het
      handje is voor links en klikbare kaarten/rijen. Voorheen was dit door elkaar
      (de ene knop toonde wel een handje, de andere niet).

    Onder de motorkap

    • Waggle draait nu alleen nog op Postgres; de SQLite-optie is vervallen. Dit
      maakt tijdzone-bewuste datums en database-validatie mogelijk. Een andere
      database-URL dan postgresql+asyncpg:// wordt bij het starten geweigerd, en
      een URL uit backend/.env werkt weer.
    • Identifiers zijn nu echte UUID's in plaats van tekst die er zo uitziet: de
      database controleert ze, vergelijkt ze sneller en slaat ze half zo groot op.
      De ongedaan-maken-momentopname van een kaart was tekst met JSON erin en is nu
      een echte JSON-kolom.
    • Negen ongebruikte Python-pakketten uit het backend-image gehaald (50 -> 41).
      Ze telden wel mee in de kwetsbaarhedenscan, die een uitrol kan blokkeren op
      code die nooit draait. De beheercommando's waggle generate-secrets en
      waggle rotate-keys werken ongewijzigd.
    • De backend-container start sneller: de Python-bytecode zit nu in het image in
      plaats van dat elke start hem opnieuw wegschrijft onder /app (1,60 s -> 0,68 s
      voor het inladen van de app; het image wordt er 34 MB groter van).
    • Een bijlage die je uploadt staat nu ook onderweg op het gegevensvolume in
      plaats van op de schijf van de pod. Die schijf kon vollopen buiten het zicht
      van de ruimtecontrole om, met een pod-herstart als gevolg; een volle schijf
      geeft nu de nette melding in plaats van een serverfout.
    • Geparkeerde bijlagen worden nu versleuteld op schijf (eigen sleutel-domein),
      zodat een volume-snapshot of backup de inhoud niet prijsgeeft.
    • Nieuwe dagelijkse retention_sweep-loop past de bewaartermijnen per gebruiker toe
      en legt alleen aantallen per categorie vast in het audit-log (retention_purged).
    • Logging is samengevoegd tot één centrale pijplijn (structlog
      ProcessorFormatter op de root-handler): structlog-, stdlib- én
      uvicorn-logs gaan door dezelfde redactie en dezelfde rendering (JSON in
      productie). Voorheen omzeilden structlog en uvicorn de redactie allebei.
    • Redactiepatronen verbreed: ook geciteerde/JSON-vormen ("token": "..."),
      alle cookie-pairs, OAuth-queryparameters (?code=/&state=),
      secret-veldnamen en traceback-tekst worden geredact.
    • Logniveau instelbaar via WAGGLE_LOG_LEVEL (default INFO); stdlib-INFO
      (o.a. CSRF-blocks, audit-events) verscheen voorheen helemaal niet.
      Elke request krijgt een request_id in alle bijbehorende logregels.
    • Alembic zet de logging-config niet meer terug bij het opstarten: de
      in-process migratie sloopte de redactie-handler en dempte de
      uvicorn-loggers op elke boot (fileConfig draait nu alleen nog voor de
      losse Alembic-CLI).
    • De build en de tests slaan nu alarm bij fouten die eerder geruisloos
      doorgingen: een verdwenen ontwerp-token, een meegeleverd RijksSans-font, een
      icoonnaam die niet meer bestaat, een ongeldige waarde op een NLDD-component
      (variant, kleur, formaat) en een NLDD-component die zijn padding weer op de
      buitenkant zet.
    • CI is sneller: de e2e-job bouwde de frontend twee keer en typecheckte opnieuw
      wat de frontend-job al deed, en draait nu op drie parallelle workers met een
      eigen backend en database per worker (lokaal 76s -> 25s). De backend-suite
      draait op een vast aantal workers in plaats van -n auto, dat de cores van de
      hele host claimde en naast een tweede CI-run tot tienvoudige looptijden leidde.
    • Renovate bumpt npm- en Python-dependencies nu ook binnen een bestaande range
      (rangeStrategy: bump). De default replace liet >=-floors en
      caret-ranges op hun oude versie staan, waardoor routine-updates (inclusief
      in-range security-patches) stil bleven liggen.
    • Dependencies bijgewerkt: fastapi 0.141.1, uvicorn 0.52.1, sqlalchemy 2.0.51,
      alembic 1.19.0, pydantic 2.13.4, structlog 26.1, tiptap 3.29.2, dompurify
      3.4.13, vite 8.2.0, typescript-eslint 8.66 en cryptography 50.0.0
      (security-advisory).
    • CI-toolchain bijgewerkt: uv 0.12.1, trivy 0.73.0, node 24.19.0-alpine en een
      nieuw nginx-basisimage; de fail-closed sha256-pins voor uv en trivy zijn
      tegen de upstream-checksums gecontroleerd.
    • Drie ongebruikte toegankelijkheidspakketten verwijderd; dat scheelt 134
      pakketten en vier van de zes high-severity meldingen in npm audit.
    • Alle vensters (opstellen, kaartdetail, herstelcode, sneltoetsen) delen nu één
      Modal-component: overlay, focus-trap, scroll-lock, Escape, header en breedte
      zitten op één plek in plaats van in vier eigen kopieën.

    Container images

  • v2026.7.24 ed061e466c

    v2026.7.24
    All checks were successful
    CI / pre-commit (pull_request) Successful in 1m1s
    CI / frontend-test (pull_request) Successful in 5m4s
    CI / release-scripts (pull_request) Successful in 10s
    security-scan / Python SCA (pip-audit) (pull_request) Successful in 1m0s
    security-scan / Python SAST (bandit) (pull_request) Successful in 38s
    security-scan / JS SCA (npm audit) (pull_request) Successful in 44s
    security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 23s
    security-scan / SBOM (trivy) (pull_request) Successful in 15s
    CI / backend-test (pull_request) Successful in 8m56s
    CI / e2e (pull_request) Successful in 8m41s
    CI / backend-test-postgres (pull_request) Successful in 9m47s
    test-build / build (backend) (pull_request) Successful in 1m52s
    CI / pre-commit (push) Successful in 53s
    test-build / build (frontend) (pull_request) Successful in 2m17s
    test-build / build (pull_request) Successful in 0s
    CI / frontend-test (push) Successful in 5m8s
    CI / release-scripts (push) Successful in 10s
    publish-main / build (backend) (push) Successful in 2m22s
    CI / backend-test (push) Successful in 9m9s
    security-scan / Python SCA (pip-audit) (push) Successful in 56s
    CI / e2e (push) Successful in 8m53s
    CI / backend-test-postgres (push) Successful in 9m53s
    publish-main / build (frontend) (push) Successful in 2m10s
    publish-main / build (push) Successful in 0s
    security-scan / Python SAST (bandit) (push) Successful in 41s
    security-scan / Filesystem scan (trivy fs) (push) Successful in 25s
    security-scan / SBOM (trivy) (push) Successful in 16s
    security-scan / JS SCA (npm audit) (push) Successful in 37s
    release-and-deploy / release (push) Successful in 33s
    release-and-deploy / pre-commit (push) Successful in 49s
    release-and-deploy / build (backend) (push) Successful in 2m16s
    release-and-deploy / build (frontend) (push) Successful in 2m28s
    release-and-deploy / build (push) Successful in 0s
    release-and-deploy / frontend-test (push) Successful in 5m17s
    release-and-deploy / release-scripts (push) Successful in 10s
    release-and-deploy / backend-test (push) Successful in 9m14s
    release-and-deploy / backend-test-postgres (push) Successful in 10m6s
    release-and-deploy / e2e (push) Successful in 9m17s
    release-and-deploy / ci (push) Successful in 0s
    release-and-deploy / deploy (push) Successful in 2m0s
    Stable

    robbertbos released this 2026-07-24 05:55:31 +00:00 | 232 commits to main since this release

    Verbeterd

    • Zoeken doorzoekt nu ook je notities bij een kaart (naast berichttekst, auteur
      en kanaal).
    • De "Voltooid"-teller toont nu het echte totaal aantal voltooide kaarten in
      plaats van maximaal het geladen aantal (dat op 50 bleef hangen).
    • Het tijdelijke "wordt opgeruimd"-label op net-voltooide kaarten is weg: het
      vroeg geen actie (de opruiming loopt vanzelf) en flikkerde. Mislukt de
      opruiming toch, dan blijft de aparte foutmelding wel staan.

    Opgelost

    • De teller bij "Wacht op verzending" had een afwijkend omkaderd boxje; hij is
      nu opgemaakt als de tellers van de andere secties.

    Onder de motorkap

    • Renovate ververst voortaan zelf de UV_SHA256/TRIVY_BIN_SHA256-pin bij een
      uv- of trivy-bump (postUpgradeTask). Voorheen bumpte renovate alleen de versie
      en faalde CI closed tot de sha handmatig werd bijgewerkt.
    • Afhankelijkheden bijgewerkt: @nldd/design-system 0.8.70,
      @tanstack/vue-query 5.101.4 en de Keycloak- en Renovate-images.

    Container images

  • v2026.7.21 acaf98d5cf

    v2026.7.21
    All checks were successful
    CI / pre-commit (pull_request) Successful in 57s
    CI / frontend-test (pull_request) Successful in 4m50s
    CI / release-scripts (pull_request) Successful in 10s
    security-scan / Python SCA (pip-audit) (pull_request) Successful in 58s
    security-scan / Python SAST (bandit) (pull_request) Successful in 38s
    security-scan / JS SCA (npm audit) (pull_request) Successful in 46s
    security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 24s
    security-scan / SBOM (trivy) (pull_request) Successful in 16s
    CI / backend-test (pull_request) Successful in 8m47s
    CI / e2e (pull_request) Successful in 8m41s
    CI / backend-test-postgres (pull_request) Successful in 9m39s
    test-build / build (backend) (pull_request) Successful in 1m54s
    CI / pre-commit (push) Successful in 57s
    test-build / build (frontend) (pull_request) Successful in 2m18s
    test-build / build (pull_request) Successful in 0s
    CI / frontend-test (push) Successful in 5m1s
    CI / release-scripts (push) Successful in 10s
    publish-main / build (backend) (push) Successful in 2m15s
    CI / backend-test (push) Successful in 8m39s
    security-scan / Python SCA (pip-audit) (push) Successful in 56s
    CI / backend-test-postgres (push) Successful in 9m29s
    CI / e2e (push) Successful in 8m36s
    security-scan / Python SAST (bandit) (push) Successful in 38s
    security-scan / Filesystem scan (trivy fs) (push) Successful in 25s
    publish-main / build (frontend) (push) Successful in 2m6s
    publish-main / build (push) Successful in 0s
    security-scan / SBOM (trivy) (push) Successful in 16s
    security-scan / JS SCA (npm audit) (push) Successful in 43s
    release-and-deploy / release (push) Successful in 32s
    release-and-deploy / pre-commit (push) Successful in 51s
    release-and-deploy / build (backend) (push) Successful in 2m17s
    release-and-deploy / build (frontend) (push) Successful in 2m36s
    release-and-deploy / build (push) Successful in 0s
    release-and-deploy / frontend-test (push) Successful in 5m13s
    release-and-deploy / release-scripts (push) Successful in 10s
    release-and-deploy / backend-test (push) Successful in 8m50s
    release-and-deploy / e2e (push) Successful in 7m40s
    release-and-deploy / backend-test-postgres (push) Successful in 9m55s
    release-and-deploy / ci (push) Successful in 0s
    release-and-deploy / deploy (push) Successful in 2m8s
    Stable

    robbertbos released this 2026-07-21 20:28:29 +00:00 | 243 commits to main since this release

    Opgelost

    • HTML-bijlage-preview werkte niet in productie: de SPA-CSP (frame-src 'none') blokkeerde de sandbox-iframe. Nu frame-src 'self', zodat de
      afgeschermde preview laadt.
    • "Nieuw tabblad" toonde de HTML-broncode i.p.v. de pagina en waarschuwde
      onterecht. Het opent nu de gesandboxede ?preview=html (rendert correct en
      kan niet bij je sessie), zonder waarschuwing.

    Onder de motorkap

    • Frontend-CSP: ${OIDC_ISSUER} uit connect-src/form-action gehaald - de
      SPA praat alleen met de eigen backend, dus de Keycloak-origin was overbodig.
      De nginx-config is nu statisch (geen template/entrypoint-substitutie meer).
    • Geparkeerde bijlagen hebben nu een eigenaar. Elke lees-, schrijf- en verwijderactie
      noemt nu de eigenaar, en er geldt een plafond van 1 GB per gebruiker.
    • De maximale bestandsgrootte wordt niet meer gegokt, maar aan de Mattermost-server
      gevraagd (die geeft hem aan elke ingelogde client). Er stonden drie constanten in de
      code die elkaar tegenspraken: Mattermost verhoogde zijn eigen standaard van 50 naar
      100 MB en één comment liep achter.
    • De SQLite-databaselocatie volgt nu WAGGLE_DATA_DIR, zodat db en geparkeerde
      bijlagen altijd op hetzelfde volume staan. Enkele ongebruikte config-variabelen
      verwijderd. (In productie draait Postgres; dit raakt alleen de SQLite-fallback.)
    • CI release-scripts-job was flaky op de runner: de changelog-check las
      intermitterend een lege sectie. De teststap heeft nu een retry met diagnostiek
      bij een gefaalde poging.
    • Afhankelijkheden en CI-tooling bijgewerkt: npm-(dev-)dependencies, diverse
      GitHub-actions en de CI/container-images (o.a. setup-node en jest-dom naar
      v7, uv 0.11.30, actions/checkout 7.0.1 en herbouwde base-images).

    Container images

  • v2026.7.20 0ff03ce8df

    v2026.7.20
    All checks were successful
    CI / frontend-test (push) Successful in 4m50s
    publish-main / build (backend) (push) Successful in 2m18s
    CI / backend-test (push) Successful in 8m49s
    release-and-deploy / deploy (push) Successful in 2m9s
    CI / backend-test-postgres (push) Successful in 9m34s
    CI / e2e (push) Successful in 8m47s
    publish-main / build (frontend) (push) Successful in 2m28s
    security-scan / Python SCA (pip-audit) (push) Successful in 57s
    publish-main / build (push) Successful in 0s
    security-scan / SBOM (trivy) (push) Successful in 18s
    release-and-deploy / pre-commit (push) Successful in 51s
    security-scan / Python SAST (bandit) (push) Successful in 39s
    security-scan / Filesystem scan (trivy fs) (push) Successful in 25s
    security-scan / JS SCA (npm audit) (push) Successful in 38s
    release-and-deploy / release (push) Successful in 33s
    release-and-deploy / build (backend) (push) Successful in 2m16s
    release-and-deploy / build (frontend) (push) Successful in 2m28s
    release-and-deploy / build (push) Successful in 0s
    release-and-deploy / frontend-test (push) Successful in 4m56s
    release-and-deploy / release-scripts (push) Successful in 11s
    release-and-deploy / backend-test (push) Successful in 8m53s
    CI / pre-commit (pull_request) Successful in 53s
    release-and-deploy / backend-test-postgres (push) Successful in 9m43s
    CI / frontend-test (pull_request) Successful in 5m23s
    release-and-deploy / e2e (push) Successful in 8m34s
    release-and-deploy / ci (push) Successful in 0s
    CI / release-scripts (pull_request) Successful in 11s
    security-scan / Python SCA (pip-audit) (pull_request) Successful in 58s
    security-scan / Python SAST (bandit) (pull_request) Successful in 34s
    CI / backend-test (pull_request) Successful in 9m5s
    security-scan / JS SCA (npm audit) (pull_request) Successful in 41s
    security-scan / SBOM (trivy) (pull_request) Successful in 17s
    security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 23s
    CI / backend-test-postgres (pull_request) Successful in 10m1s
    CI / e2e (pull_request) Successful in 8m38s
    test-build / build (backend) (pull_request) Successful in 1m46s
    test-build / build (frontend) (pull_request) Successful in 2m1s
    test-build / build (pull_request) Successful in 0s
    CI / pre-commit (push) Successful in 54s
    CI / release-scripts (push) Successful in 11s
    Stable

    robbertbos released this 2026-07-20 07:37:36 +00:00 | 262 commits to main since this release

    Bijlagen: previews van bestanden en grotere kaartjes

    • Niet-afbeelding-bijlagen zijn nu grotere kaartjes in een grid (met type-icoon,
      naam op leesbare grootte, bestandsgrootte en een aparte download-knop) in
      plaats van kleine chips.
    • Video's speel je direct op de pagina af in een modal-speler (met download);
      de file-proxy ondersteunt nu HTTP Range zodat je vlot kunt scrubben.
    • PDF's open je in-page in een preview-modal (met download + open-in-tabblad).
    • HTML-bijlagen (bv. rapporten) open je standaard in een veilige, afgeschermde
      sandbox-preview (scripts/grafieken werken, maar het bestand kan niet bij je
      sessie). Los te downloaden, of - met waarschuwing - buiten de sandbox in een
      nieuw tabblad te openen.

    Onboarding

    • Onboarding-hints: token-hint op het token-veld en een 8-staps
      inbox-rondleiding (menu, secties, snelle acties, kaart openen, thread,
      antwoorden, status en acties, verzenden).
    • De rondleiding komt met een voorbeeldbericht zodat er altijd iets is om
      naar te wijzen - ook met een lege "Doe nu". Het verdwijnt zodra je
      de rondleiding afrondt of overslaat.
    • Bestaande gebruikers met een gekoppelde server krijgen de rondleiding niet
      alsnog te zien (datamigratie).

    Verbeterd

    • Sectie-iconen zeggen nu wat ze bedoelen: "Doe nu" is een vlag (was een oog,
      dat in NLDD "tonen" betekent), "Voltooid" en "Gelezen" zijn een archiefdoos
      en de leeslijst een boek - overal hetzelfde icoon voor hetzelfde begrip.
    • De status- en actie-knoppen in de kaart-modal hebben dezelfde iconen als de
      secties waar ze de kaart heen sturen, en de statusrij staat nu in dezelfde
      volgorde als die secties: Doe nu, Nieuw, Backlog, Bubble up, Voltooid.
    • De toggle "Toon/verberg (concept) antwoord" is weg; de antwoord-composer
      staat altijd open. Scheelt een klik en een knop.
    • De notities-editor kreeg links (met Cmd/Ctrl+K) en emoji, plus een
      takenlijst-knop. De takenlijst is nu ook in de thread-composer beschikbaar
      (Mattermost rendert - [ ] als checklist).
    • Opmaak-werkbalk: de lijst-soorten (opsomming, genummerd, takenlijst) staan
      nu gegroepeerd, gescheiden van citaat en codeblok.
    • Het snooze-icoon (bubble-up) is nu een timer in plaats van een maan; past
      logischer bij "laat deze kaart later terugkomen".
    • Doorhalen, markeren en codeblok in de opmaak-werkbalk gebruiken nu native
      NLDD-iconen (strikethrough / highlighter / code-block) in plaats van
      eigen inline-SVG's, met uniforme icoongrootte.

    Opgelost

    • De beeld-lightbox lag onder de bovenbalk: linksboven klikken raakte de
      navigatie in plaats van de lightbox te sluiten. Hij wordt nu naar body
      geteleporteerd, net als de andere modals.
    • In een codeblok sprong het eerste teken in (inline <code>-padding); nu
      staat elke regel vlak, in zowel de composer als de notities-editor.
    • De tekst-inspringing van de notities-editor is gelijkgetrokken met de
      thread-composer.
    • Een link met een dubbele punt in de URL (bv. een LinkedIn-permalink) werd als
      platte tekst getoond; emoji-shortcodes binnen URLs worden nu met rust gelaten.
    • In de reageerders-hover staat de naam weer netjes uitgelijnd met de avatar.

    Onder de motorkap

    • De uitgaande wachtrij kan geen bericht meer versturen met het Mattermost-token
      van een andere gebruiker. outgoing_posts heeft nu composite foreign keys naar
      de kaart en het account van de eigenaar, en de runner leest de afzender van de
      rij in plaats van van de kaart. Niet exploiteerbaar, maar de garantie zat in een
      gewoonte in plaats van in het schema.
    • Uitloggen beëindigt nu ook de centrale Keycloak-sessie: de app navigeert
      top-level naar het end-session-endpoint (RP-initiated logout) i.p.v. via een
      fetch-redirect, zodat de volgende inlog niet stil via SSO doorgaat.
    • Backchannel-logout heeft nu een rate-limit (60/min in productie).
    • OIDC-inloggen blijft werken tijdens Keycloak-sleutelrotatie: de JWKS wordt
      bij een onbekende sleutel-id direct herophaald i.p.v. tot een uur te falen.
    • OIDC-tokenvalidatie gehard: asymmetrische-algoritme-allowlist, sub en exp
      verplicht, en malformed tokens worden netjes geweigerd i.p.v. een
      onafgehandelde 500.
    • Productie weigert te starten zonder WAGGLE_PUBLIC_HOST als OIDC aan staat:
      de redirect- en post-logout-URL mogen niet van de (spoofbare) Host-header
      afhangen. Op ZAD staat deze variabele al automatisch goed.
    • De dev-only endpoints (wachtwoordloze login, gebruiker wisselen, onboarding
      resetten) bestaan in productie niet meer: ze worden alleen geregistreerd als
      de dev-vlag aanstaat.
    • Alembic-migraties worden voortaan getest: upgrade, downgrade en opnieuw
      upgrade op zowel SQLite als Postgres, met een controle dat het gemigreerde
      schema overeenkomt met de modellen. De testsuite bouwde haar schema rechtstreeks
      uit de modellen, dus een kapotte of ontbrekende revisie bleef groen.
    • click naar 8.4.2: 8.3.2 had een advisory (PYSEC-2026-2132), waardoor de
      pip-audit-gate op elke PR afging. Transitief via typer.
    • Renovate biedt linkify-it 6 niet meer aan: die major laat de default-export
      vallen die markdown-it importeert, waardoor de build en alle composer-tests
      breken. Geblokkeerd tot markdown-it meegaat.
    • Renovate biedt TypeScript 7 niet meer aan: vue-tsc crasht erop
      (typescript/lib/tsc bestaat niet meer) en typescript-eslint eist <6.1.0.
      Geblokkeerd tot beide tools 7 ondersteunen.
    • De frontend-container-build faalde op npm ci (Missing: emojibase@17.0.0 from lock file): de Containerfile kopieerde frontend/.npmrc niet mee, waardoor npm
      daar zonder legacy-peer-deps draaide en de lockfile afkeurde die juist mét die
      vlag is gegenereerd.
    • De file-proxy serveert HTML-previews met een strikte CSP (sandbox allow-scripts + geen netwerk) en X-Frame-Options: SAMEORIGIN, zodat een
      rapport rendert maar niet bij je sessie kan of gegevens kan exfiltreren.
    • NLDD design-system naar 0.8.69: popovers en menu's flitsen niet meer kort op
      een default-positie voordat ze geplaatst worden, en de pagina blijft verborgen
      tot de web-components geladen zijn (geen flash of unstyled content).
    • npm >= 11 vastgelegd (engines in package.json + Renovate constraints)
      zodat de lockfile niet opnieuw scheef raakt: npm < 11 gooit emojibase uit
      package-lock.json, wat Renovate's lockfile-onderhoud liet afbreken
      (lockfile-error) en zo de dependency-PR's blokkeerde.
    • De e2e-CI-job wacht niet meer op frontend-test; hij bouwt zijn eigen
      frontend en deelt er niets mee. De jobs draaien nu parallel, wat de langste
      CI-stap eerder laat starten.
    • Renovate-config opgeschoond: forgejo-platform, Python-groep op pep621 (de
      uv-manager bestaat niet) en de dubbele regex-manager voor zad-actions weg.
    • Afhankelijkheids- en digest-bumps: TipTap 3.27.1 -> 3.28.0, linkify-it 5.0.2 +
      markdown-it 14.3.0 (npm-overrides), uv -> 0.11.29, renovate-image -> 43.259.2,
      dev-Keycloak 26.6 -> 26.7, nginx-unprivileged 1.31.2 -> 1.31.3 en verse
      digest-pins voor Postgres 17 en Python 3.14.6.

    Container images

  • v2026.6.30 dcc5a9f635

    v2026.6.30
    All checks were successful
    CI / pre-commit (pull_request) Successful in 1m5s
    CI / release-scripts (pull_request) Successful in 10s
    security-scan / Python SCA (pip-audit) (pull_request) Successful in 1m0s
    security-scan / Python SAST (bandit) (pull_request) Successful in 42s
    security-scan / JS SCA (npm audit) (pull_request) Successful in 47s
    CI / frontend-test (pull_request) Successful in 4m46s
    security-scan / SBOM (trivy) (pull_request) Successful in 18s
    security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 28s
    CI / backend-test (pull_request) Successful in 7m54s
    test-build / build (backend) (pull_request) Successful in 1m58s
    test-build / build (frontend) (pull_request) Successful in 2m17s
    test-build / build (pull_request) Successful in 0s
    CI / pre-commit (push) Successful in 1m10s
    CI / backend-test-postgres (pull_request) Successful in 9m6s
    CI / release-scripts (push) Successful in 11s
    publish-main / build (backend) (push) Successful in 2m21s
    CI / frontend-test (push) Successful in 4m47s
    publish-main / build (frontend) (push) Successful in 2m45s
    publish-main / build (push) Successful in 0s
    security-scan / Python SCA (pip-audit) (push) Successful in 1m3s
    security-scan / Python SAST (bandit) (push) Successful in 41s
    security-scan / JS SCA (npm audit) (push) Successful in 44s
    security-scan / Filesystem scan (trivy fs) (push) Successful in 23s
    security-scan / SBOM (trivy) (push) Successful in 16s
    CI / backend-test (push) Successful in 7m44s
    release-and-deploy / release (push) Successful in 33s
    CI / backend-test-postgres (push) Successful in 8m54s
    release-and-deploy / pre-commit (push) Successful in 1m8s
    release-and-deploy / build (backend) (push) Successful in 2m10s
    release-and-deploy / build (frontend) (push) Successful in 2m24s
    release-and-deploy / build (push) Successful in 0s
    release-and-deploy / release-scripts (push) Successful in 10s
    release-and-deploy / frontend-test (push) Successful in 4m42s
    release-and-deploy / backend-test (push) Successful in 8m0s
    CI / e2e (pull_request) Successful in 7m47s
    release-and-deploy / backend-test-postgres (push) Successful in 8m56s
    CI / e2e (push) Successful in 5m50s
    release-and-deploy / e2e (push) Successful in 4m45s
    release-and-deploy / ci (push) Successful in 0s
    release-and-deploy / deploy (push) Successful in 2m9s
    Stable

    robbertbos released this 2026-06-30 16:48:20 +00:00 | 325 commits to main since this release

    Verbeterd

    • Een kaart op Voltooid (of Gelezen) zetten in de modal sluit de modal nu
      meteen, in plaats van markeren-en-dan-zelf-sluiten.
    • Scheduled-send biedt 's ochtends vóór 08:00 nu ook "Vandaag 08:00" als
      snelkeuze, naast "Morgen 08:00".
    • De scheduled-send-popover klapt naar boven (of scrollt intern) wanneer er
      onderaan het scherm te weinig ruimte is, zodat hij niet meer buiten beeld
      valt.

    Opgelost

    • De profielkaart van een @-mention toont nu de avatar (de proxy-URL miste het
      vereiste account_id); zonder avatar blijven de initialen staan. Dezelfde
      fix geldt voor de @-mention-autocomplete tijdens het opstellen van een
      antwoord.
    • De sluit-knop op die profielkaart sluit alleen de kaart zelf, niet ook de
      onderliggende kaart-modal.
    • De thread-toon/verberg-knop in de kaart-modal toont weer een icoon (de oude
      icoonnaam bestond niet meer in de NLDD-set).
    • Een >-citaat (en losse </&) in een antwoord kwam als HTML-entiteit
      (&gt;) in Mattermost aan, waardoor de blockquote-opmaak wegviel; de
      markdown wordt nu letterlijk verstuurd.

    Container images

  • v2026.6.28 3a1de067aa

    v2026.6.28
    All checks were successful
    CI / pre-commit (pull_request) Successful in 1m19s
    CI / release-scripts (pull_request) Successful in 11s
    security-scan / Python SCA (pip-audit) (pull_request) Successful in 1m2s
    security-scan / Python SAST (bandit) (pull_request) Successful in 42s
    security-scan / JS SCA (npm audit) (pull_request) Successful in 48s
    security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 25s
    CI / frontend-test (pull_request) Successful in 4m39s
    security-scan / SBOM (trivy) (pull_request) Successful in 20s
    test-build / build (backend) (pull_request) Successful in 2m12s
    test-build / build (pull_request) Successful in 0s
    test-build / build (frontend) (pull_request) Successful in 2m36s
    CI / backend-test (pull_request) Successful in 8m14s
    CI / backend-test-postgres (pull_request) Successful in 9m17s
    CI / pre-commit (push) Successful in 1m17s
    CI / e2e (pull_request) Successful in 5m55s
    CI / release-scripts (push) Successful in 11s
    publish-main / build (backend) (push) Successful in 2m24s
    CI / frontend-test (push) Successful in 4m52s
    security-scan / Python SCA (pip-audit) (push) Successful in 1m3s
    security-scan / SBOM (trivy) (push) Successful in 17s
    CI / backend-test-postgres (push) Successful in 9m27s
    publish-main / build (frontend) (push) Successful in 2m45s
    publish-main / build (push) Successful in 0s
    security-scan / Python SAST (bandit) (push) Successful in 42s
    security-scan / Filesystem scan (trivy fs) (push) Successful in 25s
    security-scan / JS SCA (npm audit) (push) Successful in 43s
    CI / backend-test (push) Successful in 8m31s
    release-and-deploy / release (push) Successful in 35s
    release-and-deploy / pre-commit (push) Successful in 1m19s
    release-and-deploy / build (backend) (push) Successful in 2m28s
    release-and-deploy / build (frontend) (push) Successful in 2m40s
    release-and-deploy / build (push) Successful in 0s
    release-and-deploy / release-scripts (push) Successful in 11s
    release-and-deploy / frontend-test (push) Successful in 5m23s
    release-and-deploy / backend-test (push) Successful in 9m32s
    CI / e2e (push) Successful in 8m28s
    release-and-deploy / backend-test-postgres (push) Successful in 9m54s
    release-and-deploy / e2e (push) Successful in 5m42s
    release-and-deploy / ci (push) Successful in 0s
    release-and-deploy / deploy (push) Successful in 2m4s
    Stable

    robbertbos released this 2026-06-28 21:39:44 +00:00 | 336 commits to main since this release

    Verbeteringen aan de editor

    • De antwoord-composer en notities-editor heeft nu een opmaak-werkbalk. De basis
      bestaat uit een Paragraaf/Kop 1-6-dropdown, vet, cursief, doorhalen, code,
      opsomming, genummerde lijst, citaat en codeblok en links.
      Voor de antwoord-composer aanvullend nog een bijlage- en emoji-knop. Voor de
      notities-editor een onderstrepen, takenlijst en highlight opties.
    • Ctrl/Cmd+K opent een inline link-balk: typ of plak een URL voor de selectie,
      of pas een bestaande link aan / verwijder 'm. Geen window.prompt meer; de
      sneltoets-hint toont platform-bewust (Cmd op Mac, Ctrl elders).
    • Strakkere horizontale marges zodat de editor de beschikbare breedte beter
      benut.

    Reacties en custom emojis

    • Hover of focus op een emoji-reactie toont nu wie er reageerden (avatar +
      naam), met "Jij" bovenaan voor je eigen reactie.
    • Eigen Mattermost-emoji (custom emoticons) worden nu als afbeelding getoond, zowel
      in reacties als in berichttekst; onbekende codes vallen terug op :naam:.

    Verbeterd

    • Importeren: het bestand kies je nu via een gestylede sleepzone (slepen of
      klikken) die de gekozen bestandsnaam toont en een ongeldig bestand duidelijk
      als fout markeert, in plaats van de kale native bestand-knop.
    • Home/End springen nu naar de eerste/laatste kaart binnen een sectie (Focus en
      leeslijst), als aanvulling op de pijltjestoetsen.

    Opgelost

    • Een geopende kaart-modal (en de afbeelding-lightbox) vergrendelt nu de
      achtergrond-scroll, zodat de pagina eronder niet meer wegscrolt.
    • Verzonden/getoonde berichten gebruiken nu dezelfde rustige koppen-schaal als de
      editor (Kop 1 bescheiden, Kop 5/6 niet kleiner dan bodytekst) i.p.v. de grote
      browser-default.
    • Een getypte markdown-link [tekst](url) werd ontsnapt verstuurd
      (\[tekst\](url)) en kwam in Mattermost als platte tekst aan; nu wordt het
      bij het typen een echte link en als schone markdown verstuurd.
    • Afbeelding-previews in de Inbox- en leeslijst-stream worden weer getoond.
    • De bubble-up-popover opende buiten beeld bij een gescrolde pagina (o.a. een
      lange kaart in de modal); staat nu correct boven de knop.

    Design-system opfrissing

    • De reageerders- en mention-profiel-popovers zijn herbouwd op het
      NLDD-popover-component (consistente styling, top-layer-positionering en
      light-dismiss); de reageerders-lijst opent links onder de badge.
    • Statuslabels (concept/ingepland, de "Wacht op verzending"-telling, "Bubbled
      up", "Verschijnt over ...") gebruiken nu de design-system-tag: beter leesbaar,
      AA-contrast in licht en donker, geen HOOFDLETTERS meer.
    • Im-/export tonen een laad-indicator over het paneel bij acties langer dan een
      seconde; de invoer is dan vergrendeld. Korte acties tonen niets.
    • Een thread laden toont nu een laad-animatie naast de tekst.
    • De "/"-zoeksneltoets staat als toetsenbord-hint in het menu (verborgen op
      apparaten zonder toetsenbord).
    • Op smalle schermen klapt de bron-breadcrumb op een kaart in tot … > kanaal;
      de opent server + team in een dropdown. Lange kanaalnamen kappen af met
      een ellipsis, zodat de kaart-acties niet meer buiten de kaart vallen.
    • De geplande/mislukte-berichten-lijst stapelt op smalle schermen in één kolom
      (tijd, bericht, acties onder elkaar) i.p.v. een vaste 3-koloms-grid die de
      inhoud buiten de kaart duwde.

    Onder de motorkap

    • ZAD-productie wordt automatisch (her)deployed bij een CalVer-tag via
      release-and-deploy.yaml (build -> release -> deploy; deploy draait alleen na
      een geslaagde release). PR's draaien test-build (build + scan, geen push),
      main pusht :latest via publish-main; de container-build zelf is een
      herbruikbare workflow (_container-build). De ZAD-deploymentnaam is
      configureerbaar via ZAD_DEPLOYMENT_NAME i.p.v. vast in de workflow, en de
      ZAD-deploy-action staat op v4.0.6.
    • package-lock.json opnieuw gegenereerd met npm 11 (de versie in de
      CI-container) zodat verplichte peer-packages - emojibase en
      vue-eslint-parser - weer in de lockfile staan. Een eerdere herberekening
      onder oudere npm liet ze vallen, waardoor npm ci in de frontend-image en
      de ESLint-stap braken. vue-eslint-parser is bovendien expliciet als
      devDependency vastgelegd.
    • NLDD design-system 0.8.56 -> 0.8.63. Statuslabels en laad-indicatoren
      gebruiken nu nldd-tag / nldd-activity-indicator en de button-variant
      neutral-base; de dark-mode tag-fix komt mee.
    • Afhankelijkheids- en digest-bumps: renovate-image -> 43.245.0, Node
      24.16.0 -> 24.18.0, TipTap 3.26.0 -> 3.27.1, @axe-core/cli 4.11.3 ->
      4.12.1, uv -> 0.11.25, actions/cache v5 -> v6 plus digest-bumps voor
      dev-Keycloak 26.6 en Postgres 17.
    • Release-notes bevatten nu per service een klikbare link naar de
      container-image (image-ref als linktekst naar de package-pagina).
    • Renovate bundelt updates in logische lanes: naast npm / npm-dev / Python /
      nldd nu ook een container images-lane (postgres, keycloak, node, nginx) en
      trivy/uv terug bij de CI-lane. prHourlyLimit 2 -> 5. Security, digest-
      rebuilds en majors blijven losse PR's.
    • Markdown-sanitisatietests draaien onder jsdom i.p.v. happy-dom; happy-dom
      past DOMPurify's allow-lists vanaf 3.4.11 verkeerd toe (blok-tags ontwrapt,
      class= lekt door). Test-only, geen browser-regressie; deblokkeert de
      dompurify security-bump.
    • Renovate bumpt Postgres niet meer voorbij major 17 (allowedVersions <18):
      ZAD draait op 17, dev-compose blijft gelijk tot ZAD meegaat.
    • Security-bumps tegen gepubliceerde advisories, zodat de fail-closed
      pip-/npm-audit-stappen groen blijven. Backend: msgpack 1.1.2 -> 1.2.1
      (GHSA-6v7p-g79w-8964), pydantic-settings 2.13.1 -> 2.14.2
      (GHSA-4xgf-cpjx-pc3j) en joserfc 1.6.5 -> 1.7.1 (CVE-2026-48990).
      Frontend: dompurify 3.4.1 -> 3.4.11 (GHSA-cmwh-pvxp-8882), vite 8.0.10 ->
      8.0.16 (CVE-2026-53571) en via npm-overrides linkify-it 5.0.0 -> 5.0.1
      (GHSA-22p9-wv53-3rq4) + markdown-it 14.1.1 -> 14.2.0 (GHSA-6v5v-wf23-fmfq).

    Container images