Attachments end-to-end: drafts, replies and scheduled posts (fase 2b) #145

Open
robbertbos wants to merge 6 commits from fase-2b-kabel-v2 into main
Owner

Fase 2b van "nieuw bericht opstellen + plannen". Base: main.

Bijlagen reizen nu end-to-end als geparkeerde refs (owned uuids) door concept, direct antwoord en gepland bericht, en uploaden pas naar Mattermost op het moment van verzenden (dat ontwijkt de 7-daagse vervaltermijn van MM voor niet-gekoppelde bestanden). De oude POST /cards/{id}/files-route (MM file_id-currency) is geschrapt ten gunste van de kaartloze POST /outgoing-files (ref-currency). Een dagelijkse orphan-sweep ruimt geparkeerde bestanden op die na 7 dagen door geen enkele rij meer worden gerefereerd.

Uit de adversariele review-pass (security / correctness / WCAG)

  • Concept-bijlagen rehydrateren bij het laden, zodat een heropend concept ze niet stil loskoppelt.
  • Een bijlage die tijdens uploaden wordt verwijderd lekt niet meer; een lopende concept-save wordt geflusht bij unmount.
  • Upload-fouten worden in een aria-live-regio aangekondigd en met tekst gemarkeerd (niet alleen kleur).
  • De runner verwijdert geparkeerde bestanden pas nadat STATE_SENT is vastgelegd.
  • _delete_orphan_refs is per-eigenaar gescoped; outgoing_files faalt dicht op een eigenaarloze sidecar.

BIO/DPIA/NeRDS-review-fixes (in deze PR verwerkt)

  • Bijlage-inhoud versleuteld at rest (eigen HKDF-domein waggle-parkedfile-v1).
  • Retry plaatst geen dubbele bijlage meer (mm_file_id-memo persistent).
  • Account loskoppelen ruimt de geparkeerde bijlagen op.
  • Toegankelijke upload-status + disabled-reden (WCAG 4.1.3 / 1.4.1).
  • Audit-events op de vertrouwensgrens: verzending naar MM + quota-weigering.
  • Docs: quota-grens, volume-encryptie/DPIA, bewaartermijn.

Poorten

Backend 1398 tests, 100% coverage; frontend tsc + vitest + build + eslint; pre-commit schoon. Postgres-parity + e2e via CI.

Fase 2b van "nieuw bericht opstellen + plannen". Base: main. Bijlagen reizen nu end-to-end als geparkeerde refs (owned uuids) door concept, direct antwoord en gepland bericht, en uploaden pas naar Mattermost op het moment van verzenden (dat ontwijkt de 7-daagse vervaltermijn van MM voor niet-gekoppelde bestanden). De oude `POST /cards/{id}/files`-route (MM file_id-currency) is geschrapt ten gunste van de kaartloze `POST /outgoing-files` (ref-currency). Een dagelijkse orphan-sweep ruimt geparkeerde bestanden op die na 7 dagen door geen enkele rij meer worden gerefereerd. ## Uit de adversariele review-pass (security / correctness / WCAG) - Concept-bijlagen rehydrateren bij het laden, zodat een heropend concept ze niet stil loskoppelt. - Een bijlage die tijdens uploaden wordt verwijderd lekt niet meer; een lopende concept-save wordt geflusht bij unmount. - Upload-fouten worden in een aria-live-regio aangekondigd en met tekst gemarkeerd (niet alleen kleur). - De runner verwijdert geparkeerde bestanden pas nadat STATE_SENT is vastgelegd. - `_delete_orphan_refs` is per-eigenaar gescoped; `outgoing_files` faalt dicht op een eigenaarloze sidecar. ## BIO/DPIA/NeRDS-review-fixes (in deze PR verwerkt) - Bijlage-inhoud versleuteld at rest (eigen HKDF-domein `waggle-parkedfile-v1`). - Retry plaatst geen dubbele bijlage meer (mm_file_id-memo persistent). - Account loskoppelen ruimt de geparkeerde bijlagen op. - Toegankelijke upload-status + disabled-reden (WCAG 4.1.3 / 1.4.1). - Audit-events op de vertrouwensgrens: verzending naar MM + quota-weigering. - Docs: quota-grens, volume-encryptie/DPIA, bewaartermijn. ## Poorten Backend 1398 tests, 100% coverage; frontend tsc + vitest + build + eslint; pre-commit schoon. Postgres-parity + e2e via CI.
Connect the attachment cable end-to-end and harden it
Some checks failed
CI / pre-commit (pull_request) Successful in 52s
CI / frontend-test (pull_request) Successful in 4m44s
CI / release-scripts (pull_request) Successful in 10s
CI / e2e (pull_request) Has been cancelled
CI / backend-test-postgres (pull_request) Has been cancelled
CI / backend-test (pull_request) Has been cancelled
security-scan / Filesystem scan (trivy fs) (pull_request) Failing after 5s
security-scan / Python SCA (pip-audit) (pull_request) Failing after 21s
test-build / build (backend) (pull_request) Failing after 7s
security-scan / Python SAST (bandit) (pull_request) Failing after 22s
security-scan / SBOM (trivy) (pull_request) Successful in 17s
test-build / build (frontend) (pull_request) Failing after 7s
test-build / build (pull_request) Failing after 0s
security-scan / JS SCA (npm audit) (pull_request) Successful in 34s
307aecf189
Attachments now travel as parked refs (owned uuids) through draft, immediate
reply and scheduled send, and upload to Mattermost only at send-time, dodging
MM's 7-day unattached-file expiry. The old POST /cards/{id}/files route is
retired in favour of the card-less POST /outgoing-files; the reply route
validates refs at request-time and again at send-time. A daily orphan sweep
reaps parked files no row references after a 7-day TTL, wired into the
lifespan and reported in get_task_statuses.

Fixes from the adversarial review pass (security / correctness / WCAG):

- Draft attachments now rehydrate on load. The save side was wired but not the
  load side, so reopening a draft showed no chips and the first keystroke PUT an
  empty file_refs list, detaching and reaping the bytes. useFileUpload.seedDone
  restores parked files as done entries and useServerDraftSync seeds
  lastSavedRefs from the server so no empty list is ever sent.
- A file removed mid-upload no longer leaks: the finished upload's ref is
  deleted instead of lingering to the sweep and eating the user's quota.
- A pending draft-save is flushed on unmount, so closing the card modal inside
  the 500ms debounce no longer drops an edit (notably an attachment removal).
- An immediate reply now drops the server draft, so its content and now-sent
  refs do not linger and reload later.
- Upload failures are announced in an aria-live region and marked with text, not
  colour alone; a failed attachment gives an actionable reason instead of the
  misleading "wait until uploads finish".
- An upload response without a usable ref is treated as a failure.
- scheduled_runner._settle_sent deletes parked files only after STATE_SENT has
  committed; a crash in the old window turned a real send into a FAILED row
  whose bytes were already gone.
- _delete_orphan_refs is scoped to the owner: refs are per-user-unique, so the
  tenant filter is now an explicit invariant, not a bet on uuid uniqueness.
- outgoing_files stat_ref/delete_ref fail closed on an ownerless sidecar
  (no `None != None` fall-through).
- The new sweep symbols are exported from cleanup_runner.__all__.

Backend stays at 100% coverage; parity verified on Postgres.
robbertbos closed this pull request 2026-07-21 06:19:28 +00:00
robbertbos reopened this pull request 2026-07-21 06:19:31 +00:00
robbertbos closed this pull request 2026-07-21 18:46:51 +00:00
robbertbos reopened this pull request 2026-07-21 18:46:53 +00:00
robbertbos force-pushed fase-2b-kabel-v2 from 37d283d720
Some checks failed
CI / pre-commit (pull_request) Successful in 55s
CI / frontend-test (pull_request) Successful in 5m20s
CI / release-scripts (pull_request) Successful in 10s
security-scan / Python SCA (pip-audit) (pull_request) Failing after 22s
security-scan / Python SAST (bandit) (pull_request) Failing after 22s
security-scan / JS SCA (npm audit) (pull_request) Successful in 44s
security-scan / Filesystem scan (trivy fs) (pull_request) Failing after 6s
security-scan / SBOM (trivy) (pull_request) Successful in 17s
test-build / build (backend) (pull_request) Failing after 7s
test-build / build (frontend) (pull_request) Failing after 7s
test-build / build (pull_request) Failing after 0s
CI / backend-test (pull_request) Successful in 8m49s
CI / e2e (pull_request) Successful in 8m55s
CI / backend-test-postgres (pull_request) Successful in 10m2s
to 5b59b3ad04
Some checks failed
CI / pre-commit (pull_request) Has been cancelled
CI / backend-test (pull_request) Has been cancelled
CI / backend-test-postgres (pull_request) Has been cancelled
CI / frontend-test (pull_request) Has been cancelled
CI / e2e (pull_request) Has been cancelled
CI / release-scripts (pull_request) Has been cancelled
security-scan / Python SAST (bandit) (pull_request) Successful in 37s
security-scan / Python SCA (pip-audit) (pull_request) Successful in 56s
security-scan / JS SCA (npm audit) (pull_request) Successful in 38s
security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 24s
security-scan / SBOM (trivy) (pull_request) Successful in 18s
test-build / build (backend) (pull_request) Successful in 2m1s
test-build / build (frontend) (pull_request) Successful in 2m21s
test-build / build (pull_request) Successful in 0s
2026-07-21 20:53:21 +00:00
Compare
robbertbos changed title from Connect the attachment cable end-to-end and harden it (fase 2b) to Attachments end-to-end: drafts, replies and scheduled posts (fase 2b) 2026-07-21 21:02:39 +00:00
Composer drag-and-drop: dropzone overlay + attachment thumbnails
Some checks failed
CI / pre-commit (pull_request) Successful in 53s
CI / frontend-test (pull_request) Successful in 5m17s
CI / release-scripts (pull_request) Successful in 10s
security-scan / Python SCA (pip-audit) (pull_request) Successful in 1m0s
CI / backend-test (pull_request) Successful in 8m38s
security-scan / Python SAST (bandit) (pull_request) Successful in 36s
security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 25s
CI / e2e (pull_request) Successful in 8m11s
security-scan / JS SCA (npm audit) (pull_request) Successful in 39s
security-scan / SBOM (trivy) (pull_request) Successful in 19s
CI / backend-test-postgres (pull_request) Failing after 9m33s
test-build / build (backend) (pull_request) Successful in 2m6s
test-build / build (frontend) (pull_request) Successful in 2m36s
test-build / build (pull_request) Successful in 0s
017715c474
- A dragged file is always attached, never inserted at a caret, so the
  ProseMirror drop-cursor (which promised placement) is turned off, and a
  dragover overlay ('Sleep bestanden hier...') makes the drop target explicit.
- Attachment chips show an image thumbnail (client-side object URL, revoked on
  removal/unmount) or a paperclip icon; there is no read-back route, so a
  rehydrated image falls back to the icon.
Some checks failed
CI / pre-commit (pull_request) Successful in 53s
Required
Details
CI / frontend-test (pull_request) Successful in 5m17s
Required
Details
CI / release-scripts (pull_request) Successful in 10s
Required
Details
security-scan / Python SCA (pip-audit) (pull_request) Successful in 1m0s
Required
Details
CI / backend-test (pull_request) Successful in 8m38s
Required
Details
security-scan / Python SAST (bandit) (pull_request) Successful in 36s
Required
Details
security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 25s
Required
Details
CI / e2e (pull_request) Successful in 8m11s
Required
Details
security-scan / JS SCA (npm audit) (pull_request) Successful in 39s
Required
Details
security-scan / SBOM (trivy) (pull_request) Successful in 19s
Required
Details
CI / backend-test-postgres (pull_request) Failing after 9m33s
Required
Details
test-build / build (backend) (pull_request) Successful in 2m6s
test-build / build (frontend) (pull_request) Successful in 2m36s
test-build / build (pull_request) Successful in 0s
This pull request has changes conflicting with the target branch.
  • CHANGELOG.md
View command line instructions

Manual merge helper

Use this merge commit message when completing the merge manually.

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin fase-2b-kabel-v2:fase-2b-kabel-v2
git switch fase-2b-kabel-v2
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
robbertbos/waggle!145
No description provided.