-
v2026.8.11
StableAll checks were successfulsecurity-scan / Python SCA (pip-audit) (pull_request) Successful in 38ssecurity-scan / Python SCA (pip-audit) (push) Successful in 36srelease-and-deploy / release-scripts (push) Successful in 6srelease-and-deploy / e2e (push) Successful in 3m27stest-build / build (frontend) (pull_request) Successful in 55sCI / pre-commit (pull_request) Successful in 1m0sCI / frontend-test (pull_request) Successful in 1m0sCI / pre-commit (push) Successful in 58spublish-main / build (frontend) (push) Successful in 59srelease-and-deploy / release (push) Successful in 18stest-build / build (backend) (pull_request) Successful in 59stest-build / build (pull_request) Successful in 0sCI / backend-test (pull_request) Successful in 2m17sCI / frontend-test (push) Successful in 1m2spublish-main / build (backend) (push) Successful in 1m4spublish-main / build (push) Successful in 0srelease-and-deploy / pre-commit (push) Successful in 55sCI / e2e (pull_request) Successful in 3m29sCI / backend-test (push) Successful in 2m19sCI / release-scripts (push) Successful in 6sCI / release-scripts (pull_request) Successful in 6ssecurity-scan / SBOM (trivy) (push) Successful in 10srelease-and-deploy / build (frontend) (push) Successful in 58srelease-and-deploy / frontend-test (push) Successful in 59srelease-and-deploy / deploy (push) Successful in 2m5ssecurity-scan / SBOM (trivy) (pull_request) Successful in 10ssecurity-scan / Filesystem scan (trivy fs) (push) Successful in 14ssecurity-scan / Filesystem scan (trivy fs) (pull_request) Successful in 13ssecurity-scan / JS SCA (npm audit) (push) Successful in 15srelease-and-deploy / build (backend) (push) Successful in 1m3srelease-and-deploy / build (push) Successful in 0ssecurity-scan / JS SCA (npm audit) (pull_request) Successful in 17ssecurity-scan / Python SAST (bandit) (pull_request) Successful in 20ssecurity-scan / Python SAST (bandit) (push) Successful in 17sCI / e2e (push) Successful in 3m30srelease-and-deploy / backend-test (push) Successful in 2m15srelease-and-deploy / ci (push) Successful in 0sreleased this
2026-08-11 21:23:00 +00:00 | 55 commits to main since this releaseChannel 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
gthen i/l/c/u/n/s (Inbox, Leeslijst, Concepten,
Uitgaande post, Notities, Instellingen), and through a card list withj/k
or the arrows. - Act on the focused card:
vcompletes it or marks it read,dsends it to
"Doe nu",lto Backlog,bbubbles it up,wmoves it to the other list,
rreplies andmopens it in Mattermost. - Every status change can be undone for five seconds, with
zor the button on
the notice. - Reorder a card with
Alt/Optionand an arrow key: the keyboard route to
what dragging does. - Inside an open card,
j/kstep to the next and previous one without closing
it, and the status keys move on by themselves. Cmd/Ctrl+Shift+Enteropens 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-squareinstead 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
titleattribute are nownldd-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-AfterandX-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
/apiprefix is written once, inAPI_URL_PREFIX, and applied by one
parent router instead of by each of sixteen. A route mounted outside it fails
a test. /api/healthis 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/configand
GET /api/auth/mepublish 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
metadatais not an object no longer fails the thread
request, a non-string user id no longer fails the response model, and a
non-stringmime_typeno 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:CancellationReasonmissed a
value the backend sends, anduseSyncStatushad 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 onnldd-modal-dialogand 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.ymlgained 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.mdis 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.vuetemplates), 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 autoagain 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
- A saved message now offers "Toon eerdere en latere berichten", directly above