Connect the attachment cable end-to-end and harden it (fase 2b) #145

Open
robbertbos wants to merge 1 commit from fase-2b-kabel-v2 into main
Owner

Fase 2b van "nieuw bericht opstellen + plannen". Base: main (fase 2a / #140 is gemerged).

Sluit de bijlage-kabel end-to-end aan. Bijlagen reizen nu als geparkeerde refs (owned uuids) door concept, direct antwoord en gepland bericht, en uploaden pas naar Mattermost op verzendmoment (ontwijkt MMs 7-daagse vervaltermijn 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, gewired in de lifespan en gerapporteerd in get_task_statuses.

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

  • Concept-bijlagen rehydrateren nu bij het laden: de save-kant was gewired maar de load-kant niet, waardoor een heropend concept geen chips toonde en de eerste toetsaanslag een lege file_refs-lijst PUTte (bijlage losgekoppeld + bytes opgeruimd). useFileUpload.seedDone + useServerDraftSync seeden de opgeslagen refs, zodat er nooit een lege lijst wordt verstuurd.
  • Een bijlage die tijdens het uploaden wordt verwijderd lekt niet meer: de ref van de afgeronde upload wordt gewist i.p.v. te blijven staan tot de sweep.
  • Een lopende concept-save wordt geflusht bij unmount, zodat de modal sluiten binnen de 500ms-debounce geen edit meer laat vallen.
  • Een direct antwoord dropt daarna het server-concept, zodat inhoud en verzonden refs niet blijven hangen.
  • Upload-fouten worden in een aria-live-regio aangekondigd en met tekst gemarkeerd (niet alleen kleur); een mislukte bijlage geeft een actiegerichte reden.
  • scheduled_runner._settle_sent verwijdert geparkeerde bestanden pas nadat STATE_SENT is vastgelegd; een crash in het oude venster maakte van een echte verzending een FAILED-rij waarvan de bytes al weg waren.
  • _delete_orphan_refs is per-eigenaar gescoped (expliciete tenant-invariant i.p.v. een gok op uuid-uniciteit).
  • outgoing_files stat_ref/delete_ref falen dicht op een eigenaarloze sidecar.

Poorten

Backend 1392 tests, 100% coverage; frontend tsc + 513 vitest + build + eslint (0 errors); pre-commit schoon. Postgres-parity via CI.

Herbouw-context

Herbouwd uit de oude gestapelde branch (#113) op de nieuwe main-basis. Conflicten opgelost: cards.py (oude upload-route + de size-limit-helper daar verdwijnen met de route), CHANGELOG (kabel-entry in Unreleased, release-drift vermeden), ReplyComposer.vue (de nieuwe Hint-wrapper behouden + de errored/uploading schedule-disabled-logica overgenomen). De per-ongeluk-gecommitte spec-docs zitten niet in deze keten.

Fase 2b van "nieuw bericht opstellen + plannen". Base: main (fase 2a / #140 is gemerged). Sluit de bijlage-kabel end-to-end aan. Bijlagen reizen nu als geparkeerde refs (owned uuids) door concept, direct antwoord en gepland bericht, en uploaden pas naar Mattermost op verzendmoment (ontwijkt MMs 7-daagse vervaltermijn 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, gewired in de lifespan en gerapporteerd in get_task_statuses. ## Uit de adversariele review-pass (security / correctness / WCAG) - Concept-bijlagen rehydrateren nu bij het laden: de save-kant was gewired maar de load-kant niet, waardoor een heropend concept geen chips toonde en de eerste toetsaanslag een lege file_refs-lijst PUTte (bijlage losgekoppeld + bytes opgeruimd). `useFileUpload.seedDone` + `useServerDraftSync` seeden de opgeslagen refs, zodat er nooit een lege lijst wordt verstuurd. - Een bijlage die tijdens het uploaden wordt verwijderd lekt niet meer: de ref van de afgeronde upload wordt gewist i.p.v. te blijven staan tot de sweep. - Een lopende concept-save wordt geflusht bij unmount, zodat de modal sluiten binnen de 500ms-debounce geen edit meer laat vallen. - Een direct antwoord dropt daarna het server-concept, zodat inhoud en verzonden refs niet blijven hangen. - Upload-fouten worden in een aria-live-regio aangekondigd en met tekst gemarkeerd (niet alleen kleur); een mislukte bijlage geeft een actiegerichte reden. - `scheduled_runner._settle_sent` verwijdert geparkeerde bestanden pas nadat STATE_SENT is vastgelegd; een crash in het oude venster maakte van een echte verzending een FAILED-rij waarvan de bytes al weg waren. - `_delete_orphan_refs` is per-eigenaar gescoped (expliciete tenant-invariant i.p.v. een gok op uuid-uniciteit). - `outgoing_files` stat_ref/delete_ref falen dicht op een eigenaarloze sidecar. ## Poorten Backend 1392 tests, 100% coverage; frontend tsc + 513 vitest + build + eslint (0 errors); pre-commit schoon. Postgres-parity via CI. ## Herbouw-context Herbouwd uit de oude gestapelde branch (#113) op de nieuwe main-basis. Conflicten opgelost: `cards.py` (oude upload-route + de size-limit-helper daar verdwijnen met de route), CHANGELOG (kabel-entry in Unreleased, release-drift vermeden), `ReplyComposer.vue` (de nieuwe Hint-wrapper behouden + de errored/uploading schedule-disabled-logica overgenomen). De per-ongeluk-gecommitte spec-docs zitten niet in deze keten.
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
Some checks failed
CI / pre-commit (pull_request) Successful in 52s
Required
Details
CI / frontend-test (pull_request) Successful in 4m44s
Required
Details
CI / release-scripts (pull_request) Successful in 10s
Required
Details
CI / e2e (pull_request) Has been cancelled
Required
Details
CI / backend-test-postgres (pull_request) Has been cancelled
Required
Details
CI / backend-test (pull_request) Has been cancelled
Required
Details
security-scan / Filesystem scan (trivy fs) (pull_request) Failing after 5s
Required
Details
security-scan / Python SCA (pip-audit) (pull_request) Failing after 21s
Required
Details
test-build / build (backend) (pull_request) Failing after 7s
security-scan / Python SAST (bandit) (pull_request) Failing after 22s
Required
Details
security-scan / SBOM (trivy) (pull_request) Successful in 17s
Required
Details
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
Required
Details
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.