New-message composer: overlay, shared editor, mentions and queue #192
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fase-5-composer"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Compose a new Mattermost message to a channel or person, independent of a saved
card, and send it now (with a pre-send undo window) or schedule it - consuming
the existing outgoing-message backend.
Composer
components/Modal.vue: focus-trapa send-now / schedule split-button. Content and target autosave to a concept; you can work on several concepts
at once. During the undo window the pending send locks its concept (hidden
from Concepten, not reopenable); the concept is deleted only after Mattermost
accepted the message, so a failed send or a crash never loses composed text
or attachments.
a debounced people search to start a DM. The search matches every typed token
against the channel name and its team name ("off topic groep x").
cshortcut, behind adevice-local single-key-shortcuts preference (WCAG 2.1.4).
Shared editor + mentions
emoji picker + optional @-mention autocomplete, used by both the reply
composer and the new-message composer so they cannot drift.
target channel for a new message (new
GET /api/mm/mention-candidates, sharingthe reply endpoint's member/autocomplete logic).
Backend
POST /api/messages,/api/scheduled-posts) and per-usernew-message drafts (multiple concepts per user). The composer-facing API is renamed from "cardless"
to "new message"; the data-layer term (a row with
card_id IS NULL)deliberately stays "card-less".
MAX_FILE_REFSfor the attachment cap. A dev Mattermost mock behind adedicated
dev_mm_mockflag makes the composer testable locally without a realserver, committed with full coverage.
Housekeeping
AttachmentChipList(image thumbnails everywhere) and auseMessageComposerattachment core remove duplication between the twocomposers.
Undo is a pre-send delay toast (
lib/delayedSend), not a post-send delete.Review round
A deep multi-agent review of everything since v2026.7.24 (with adversarial
verification per finding) confirmed 28 issues; all are fixed in the six
commits from
c512a6ato77a8839. Highlights:destroyed the concept (draft delete reaped the parked files before the
delayed send fired). The concept now lives until the send succeeded.
post (dedupe keyed on an idempotency key that exports never carry) - the
message fired twice. Dedupe now uses content-shaped keys.
instead of retrying outside Mattermost's 30s dedupe cache, the runner claim
is an atomic state-guarded UPDATE, replies carry a pending_post_id, and the
composers reuse one dedupe key across retries.
restored as a concept, including server, target and attachments.
deterministic migration downgrade, silent-autosave feedback, a11y fixes
(Server select name, error-text contrast) and authorization tests for
mention-candidates.
a4db9cb9181a0e760f87ComposeOverlay used a native <dialog>, which rendered top-aligned: the global `* { margin: 0 }` reset overrides the UA `dialog:modal { margin: auto }` that would have centered it. Rather than patch that one dialog, extract a Modal shell (components/Modal.vue) mirroring the app's other modals - a centered backdrop panel with useFocusTrap, useScrollLock, Escape-to-close and backdrop-click - and render ComposeOverlay through it. Focus now traps inside the panel and restores to the invoker on close.The combobox's magnifier sat over the start of the placeholder: a global `input[type="text"] { padding }` rule (specificity 0,1,1) beat the bare `.input` class (0,1,0), so the left inset meant to clear the icon never applied. Scope the rule to `.comboWrap .input` (0,2,0) to win the cascade, and set the left padding to clear the icon plus a gap. The combobox stays custom - moving it to nldd-search-field would jeopardise its WCAG combobox ARIA contract.Main retired Waggle's spacing/radius/type/weight/shadow tokens in favour of NLDD's primitives. Resolve the conflicts to main's versions (numeric nldd-icon sizes, the schedule-checkbox layout), keep the composer refactor (shared MessageEditor, mentions, AttachmentChipList), and migrate every retired token in the six composer files to its NLDD equivalent: --space-N -> --primitives-space-4N, --radius-{sm,md,lg} -> --primitives-corner-radius-{xs,sm,md}, --text-* -> --primitives-font-size-*, --shadow-lg -> --semantics-overlays-box-shadow, --transition-fast -> --primitives-transition-duration-fast, and --semantics-content-error-color -> --semantics-content-critical-color.Card-less composer: new-message overlay + queue integrationto New-message composer: overlay, shared editor, mentions and queueThe mock's channel/user ids ("c-town", "u-sam") were not the 26-char lowercase- alphanumeric shape real Mattermost uses, and mm_targets_service.resolve_target enforces that shape at submit-time - so scheduling or sending a new message in the preview failed with "ongeldig kanaal-id", while the read paths (listing channels, mentions) worked. Derive valid ids from a short seed via _mmid().--semantics-input-fields-border is a full `border` shorthand ("2px solid <color>"), not a color. Using it as `border: 1px solid var(--semantics-input-fields-border, ...)` expands to `1px solid 2px solid <color>`, invalid at computed-value time, so the whole border drops (border-style resets to none). The "Aan" combobox input and the selected-target chip therefore rendered with no visible frame. Use --semantics-dividers-color (a real color), matching the Bericht box and the reply composer.Card-less new-message drafts were a per-user singleton: a partial-unique index (ux_outgoing_one_new_message_draft) plus an upsert meant starting a new message overwrote the previous concept, so you could never keep two in flight. Make them id-based so a user can work on several messages at once: - Drop the singleton index (migration 595995509a58); the per-card one-draft index stays. - Service: replace the upsert with create/update/get/delete-by-id. - API: POST creates (201); GET/PUT/DELETE target /{draft_id} and 404 on an unknown id. The schedule endpoint no longer deletes "the" draft; the composer deletes its own source draft by id after scheduling. - Schemas expose the draft id. - Frontend: the composer tracks the current draft id, autosave creates on first content then updates by id, "Nieuw bericht" opens blank while earlier concepts persist under Concepten, and reopening a concept re-seeds its target and content.- A failed or cancelled card-less scheduled message can be restored as a concept ("Zet terug naar concept"): content, server, target and attachments come back editable; ScheduledPostRead now serializes account_id so the server choice survives. The concept is created while the terminal row still holds its bytes, so the refs stay protected. - Autosave failures in the new-message composer now show the SavedIndicator (like replies) instead of failing silently. - Overview pages show a critical banner when the query fails instead of the empty-state text; drafts list rows key on the draft id. - The Server select has an accessible name; failed rows keep full-contrast error text (tinted background instead of whole-row opacity). - The unlink-account dialog names the real consequences (scheduled messages, concepts, attachments for that server are deleted). - Query-cache hygiene: schedule creators invalidate the whole outgoing prefix; draft save/delete also invalidate the drafts list.The heading-clear step (Ctrl+A + Backspace) could race the next keystrokes, leaving the heading text prepended to the link ('## Een ko[Klik hier]...'). Assert the editor is empty before typing so Playwright waits out the clear.814b79ce9c0fd75a9814