345 Commits
Author SHA1 Message Date
才倾梦华V f982147f9f Clarify assistant email pagination cursors and retry errors (#144) 2026-10-15 01:35:04 +07:00
Can K.andCan Kirca 6ffb698dd7 Check MX and the Resend DKIM record in public DNS for manual zones (#132)
A manual zone has no Cloudflare records to read, so the Resend and SES receiving checks reported 'MX record points to Resend' as failed even when the MX was right. Manual zones are now checked against public DNS, and a lookup that cannot run says so instead of failing the step.

A Resend domain with nothing else to name a DKIM selector is checked at resend._domainkey, which is the record Resend asks for.

Co-authored-by: Can Kirca <9063990+cankirca@users.noreply.github.com>
2026-10-15 00:08:14 +07:00
Hieu Nguyen (Jack) 96ce3911af Add account deletion #100 2026-10-15 00:01:06 +07:00
Can K.andCan Kirca c64d2df9c9 Fill the missing strings in every locale catalog (#142)
The catalogs had fallen behind English (Drive, scheduling, seats, password reset
mail and more), which also made the catalog parity test fail on main. Add the
missing keys to all 32 non-English catalogs, with the plural forms each language
needs for the new count strings.

Co-authored-by: Can Kirca <9063990+cankirca@users.noreply.github.com>
2026-10-14 21:30:31 +07:00
Hieu Nguyen (Jack) 92f8eec87b remove blockRemote button 2026-10-14 18:19:56 +07:00
Hieu Nguyen (Jack) 41c13a1e6f improve mobile ui 2026-10-14 18:14:53 +07:00
Hieu Nguyen (Jack) 9af65bfcbe Merge branch 'main' of https://github.com/hieunc229/mailflare 2026-10-14 17:59:13 +07:00
Hieu Nguyen (Jack) baabe856ba Update loading remote content #125 2026-10-14 17:59:10 +07:00
Can K.andCan Kirca 9a3bb6a248 Send the password reset email in the language the user chose (#136)
The reset mail was hardcoded in English. It now uses the language from the
preference cookie of the browser that asked for the reset, with English as the
fallback for any string a catalog does not have yet. English and Turkish are
translated.

Co-authored-by: Can Kirca <9063990+cankirca@users.noreply.github.com>
2026-10-14 16:56:38 +07:00
Can K.andCan Kirca 731167be7f Add the missing Turkish translations (#123)
The Turkish catalog was missing 116 keys added with Drive, scheduled sends, the storage limit, account seats and the self-hosted update note, so those screens showed English text. This adds them.

Co-authored-by: Can Kirca <9063990+cankirca@users.noreply.github.com>
2026-10-14 16:56:21 +07:00
Can K.andCan Kirca 2f8c15471b Fix wrong and awkward Turkish translations (#130)
* Add the missing Turkish translations

The Turkish catalog was missing 116 keys added with Drive, scheduled sends, the storage limit, account seats and the self-hosted update note, so those screens showed English text. This adds them.

* Fix wrong and awkward Turkish translations

A review of the Turkish catalog against the English source and the screens it appears on. The main fixes:
- Route is a routing rule, not a road: "Yol ekle" is now "Kural ekle", and the same word is fixed on the home page.
- The AI agent was "Aracı" (an intermediary); it is now "Ajan".
- A thread is a conversation ("Konuşma"), not a subject ("Konu"). Rule fields call the subject "Konu" instead of "Başlık".
- Booking: "Ev sahibi" (landlord) is now "Organizatör", and meetings are "planlandı" rather than "ayarlandı".
- Account descriptions and the Pro and Team license texts match the English again; some described the wrong plan or feature.
- Several strings now say storage bucket instead of R2, folder instead of section, and so on, where the English does.
- Capitalisation follows the rest of the catalog.

---------

Co-authored-by: Can Kirca <9063990+cankirca@users.noreply.github.com>
2026-10-14 16:55:31 +07:00
Can K.andCan Kirca 0e3fba433b Second round of accessibility fixes (#131)
- The mobile menu drawer closes on Escape and returns focus to the menu button.
- The calendar event editor is a real dialog: focus moves into it, Tab stays inside, Escape closes it and focus returns to where it was.
- The info icons next to routing options are buttons, so keyboard users can reach the hints.
- Table headers declare their column scope, and the wide tables that scroll sideways can be scrolled with the keyboard.
- The Drive file list is exposed as a list.
- Links that open a new tab say so to screen readers.

Co-authored-by: Can Kirca <9063990+cankirca@users.noreply.github.com>
2026-10-14 16:55:05 +07:00
Luca Bertelli c76a810436 Fix insert-link popup submitting the compose form (#141) 2026-10-14 15:58:14 +07:00
Namindu Sanchila Nadeeshan 10c0338a1e Give the assistant the currently open email as context (#140)
The panel already knew which email was open but only used it for the
suggestion buttons, so typed questions like "summarize this email" left the
model guessing. Chat requests now carry the open email's ID, the server uses
it only if it is in the selected mailbox and visible to the user, and the
latest turn tells the model which email "this" means. A chip above the input
shows the email in use and can be dismissed.

Closes #126
2026-10-14 15:55:43 +07:00
Ariel Yu 775f4a74e9 bugfix(.env.docker.example): Add .env.docker.example (#139)
.env.docker.example should be in repo since it is mentioned in README.md

Signed-off-by: Ariel Yu <ariel@ariels.xyz>
2026-10-14 15:53:50 +07:00
Ariel Yu f6d82ffd01 chore(cloudflare-email-relay): Add ignore of node_modules (#138)
node_modules in cloudflare-email-relay should be ignored as well.

Signed-off-by: Ariel Yu <ariel@ariels.xyz>
2026-10-14 15:53:15 +07:00
Can K.andCan Kirca 68fb384c86 Fix conversation unread state and counts, and report skipped permanent deletes (#135)
In conversation view a row's unread state was computed from every message in
the thread, in any folder, while marking the row read only touches the messages
shown in the current folder. A thread with an unread message in another folder
(or Sent rows for an unread thread) looked unread and stayed that way. The
unread count is now taken from the same messages the row stands for.

The sidebar Inbox count moved by one for any row action or bulk action. It now
moves by the number of unread messages the action actually flips, only in the
Inbox, and bulk read/unread updates the conversation rows too.

Permanent delete returned how many messages it skipped but the client ignored
it, so a partly failed delete looked like a success. It now refreshes the list
and shows a message.

Co-authored-by: Can Kirca <9063990+cankirca@users.noreply.github.com>
2026-10-14 15:39:03 +07:00
Can K.andCan Kirca ffc802574d Keep draft attachments, stop duplicate drafts and block past send times (#134)
Attachments added to a draft are now uploaded with the autosave, so closing
the composer no longer drops them. Autosaves run one at a time, so typing
while the first save is still in flight no longer creates a second draft.

The schedule pickers no longer accept a time that has already passed. The
current minute and anything later is allowed. /api/send rejects a time more
than a minute in the past instead of sending immediately.

Co-authored-by: Can Kirca <9063990+cankirca@users.noreply.github.com>
2026-10-14 13:46:30 +07:00
Can K.andCan Kirca 3429e18385 Let users manage their own mailboxes from Settings (#133)
The mailbox pages lived only under the admin layout, so a user with permission to manage mailboxes could use the API but had no page to do it from. Settings now has a Mailboxes entry for those users, opening the same list and editor. The admin account page also links to each managed mailbox's settings.

Co-authored-by: Can Kirca <9063990+cankirca@users.noreply.github.com>
2026-10-14 13:22:00 +07:00
Can K.andCan Kirca 053fd45c2f Fix the type errors that tsc reports on main (#124)
None of these change behaviour. Most give a parsed response a type or guard an optional value. Calendar invitations are now returned as an ArrayBuffer, which is what the attachment type asks for, and the mailbox rename keeps the domain id so the cached entry matches MailboxOption. The icon button size is declared as an empty variant, which renders exactly as before.

Co-authored-by: Can Kirca <9063990+cankirca@users.noreply.github.com>
2026-10-14 12:13:08 +07:00
Can K.andCan Kirca 42e3690752 Name the controls on routing rules (#129)
The edit and delete buttons and the enable switch on each routing rule had no accessible name, and neither did the keep-a-copy switch in the rule dialog. Each is now named after the rule it acts on.

Co-authored-by: Can Kirca <9063990+cankirca@users.noreply.github.com>
2026-10-14 12:09:29 +07:00
Daniel Quah 07c4d56624 Reply to the Reply-To address instead of From (#122)
Inbound messages never stored their Reply-To header, so Reply and Reply all
in the reader always addressed the From address. Mail from a contact form
(From: no-reply@site, Reply-To: visitor) was answered to the no-reply box.

Store the full Reply-To list in a new messages.reply_to column on delivery
and import, use it as the reply target in getReplyRecipients (falling back
to From when it names only the mailbox's own addresses), surface it on JMAP
Email objects, and let the agent's draft_reply read the column before
falling back to parsing the raw MIME for older rows.
2026-10-14 12:07:57 +07:00
rome-xiandrome-xi 6e73e28cae Stop Workers AI streams from repeating text (#120)
Signed-off-by: rome-xi <rome-xi@users.noreply.github.com>
Co-authored-by: rome-xi <rome-xi@users.noreply.github.com>
2026-10-14 12:05:27 +07:00
rome-xiandrome-xi 0c15499889 Keep assistant tool schemas free of regex lookaround (#117)
Signed-off-by: rome-xi <rome-xi@users.noreply.github.com>
Co-authored-by: rome-xi <rome-xi@users.noreply.github.com>
2026-10-14 12:03:47 +07:00
Alexander Lichter bf89234bb1 chore: migrate to Vite+ (#121)
* chore: migrate to Vite+ with lint rule parity

* chore: remove redundant React JS lint rules
2026-10-13 18:10:21 +07:00
Hieu Nguyen (Jack)andNathan Daeila 3bbd1970f9 Atomic scheduled-send cancel and MCP scheduled tools (from #111) (#128)
* Make Cancel send and scheduled delivery claim the job atomically

Cancel read the job, then deleted it; the queue consumer read the job, then
delivered. A cancel that landed while the consumer was loading attachments
could return the message to Drafts and still send it.

The consumer now claims the job (queued -> sending) before delivering, and
Cancel deletes only a job that is still queued, answering 409 otherwise, so
exactly one of them wins. deliverEmail already moves the job on to sent or
failed. 'sending' is added to the outbound_jobs status enum (TypeScript only;
the column is plain text, so no migration).

* Let MCP clients see and cancel scheduled sends

- list_emails and search_emails accept folder: "scheduled".
- get_scheduled_send returns when a scheduled email will go out (mcp:read).
- cancel_scheduled_send returns it to Drafts (mcp:draft), using the same
  helper as the Cancel send route, which now lives in lib/email/scheduled.

Stacked on the atomic cancel change.

---------

Co-authored-by: Nathan Daeila <91697373+ndaeila@users.noreply.github.com>
2026-10-13 18:01:18 +07:00
Hieu Nguyen (Jack) b50101389e improve Composer for mobile #116 2026-10-13 17:54:02 +07:00
Nathan DaeilaandHieu Nguyen f0fddecd2e Use the full width for message bodies on mobile (#112)
* Use the full width for message bodies on mobile

The reader indents the subject, sender row and body by the avatar column
(pl-12/pl-16) at every width, which leaves a 64px gutter on a phone. Below
the sm breakpoint the subject, body, attachments and the scheduled-send
banner's wrapper now start at the page edge; wider screens are unchanged.

* improve mobile layout for email content

---------

Co-authored-by: Hieu Nguyen (Jack) <nc.hieu@live.com>
2026-10-13 14:09:56 +07:00
Hieu Nguyen (Jack) b24535647c ignore auth for new db 2026-10-13 12:38:15 +07:00
sean0407andSean ac5546d53a fix: show self-hosted update guidance (#107)
Co-authored-by: Sean <sean@SeandeMac-mini-2.local>
2026-10-13 09:53:36 +07:00
Hieu Nguyen (Jack) 2e24bfd3b2 Improve read state 2026-10-11 16:23:47 +07:00
Namindu Sanchila Nadeeshan d7795ac632 Add Sinhala interface language (#110) 2026-10-11 16:09:55 +07:00
Hieu Nguyen (Jack) eb9b79e49e pump version 2026-10-11 02:55:07 +07:00
Hieu Nguyen (Jack) b8e2fbe3cc Add migration check 2026-10-11 02:55:02 +07:00
Hieu Nguyen (Jack) e7df653f39 Improve email color in dark/light mode 2026-10-11 02:50:58 +07:00
Namindu Sanchila NadeeshanandClaude Opus 5.5 af266d9533 Serve attachments and Drive files inline only when their type cannot run script (#103)
Message attachments honoured the sender's inline disposition for any type, and
the Drive and preview checks compared raw Content-Type strings, so
image/svg+xml;charset=utf-8 or image/png, text/html passed. The per-route CSP
sandbox was the only guard, and on the Node runtime Next.js drops it in favour of
the global CSP from next.config, which allows inline script.

Parse the MIME essence, allowlist inline types, and send back only the parsed
type.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 02:44:16 +07:00
960dda4656 Raise the contrast of secondary text to WCAG AA (#95)
Secondary text was mostly text-neutral-400 or neutral-500 on the tinted sidebar background, which measured 2.4:1 and 4.45:1 against the 4.5:1 AA minimum. In the dark theme neutral-400 on the card surfaces measured 3.5 to 3.9:1.

Light theme: neutral-500 goes from #737373 to #6b6b6b, which clears 4.5:1 on the sidebar and selected-row backgrounds, and text that used neutral-400 (11 and 12 px captions, timestamps, helper lines, key hints, chart labels, empty-state lines) now uses neutral-500. Placeholders, decorative icons and disabled text are left alone. Dark theme: neutral-400 becomes #9aa0a6 and neutral-500 #a8aeb4. The active command palette item used text-blue-100, which the dark theme remaps to a dark blue (1.75:1 on the blue background); it is white now. The success badge uses green-800 instead of green-600 on its tint.

A few informative icons (close buttons, the recipient chip remove button, the shared-folder marker) go from neutral-400 to neutral-500 as well, for the 3:1 non-text minimum.

Co-authored-by: Can Kirca <9063990+cankirca@users.noreply.github.com>
Co-authored-by: Hieu Nguyen (Jack) <nc.hieu@live.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 02:41:22 +07:00
b50031bd35 Give pages a title, one h1 and unique landmarks (#94)
* Give pages a title, one h1 and unique landmarks

Settings, admin, calendar, booking, drive and compose pages kept the title of whatever page was shown before, because only the mail folders updated document.title. A small RouteTitle component, mounted in each layout, now sets "<page> - <app name>" from a table of the existing navigation labels. The sign-in, forgot-password and reset-password pages get their own titles from generateMetadata.

Headings: the folder list on the desktop layout had no h1 (it was only rendered on narrow screens), and the admin pages hid their h1 with display: none on phones in favour of an h2 outside the main landmark. The list now always has a heading (an h2 when a message with its own h1 is open), the visible title next to the toolbar is decorative, admin pages keep their h1 on phones through sr-only, the mobile top bar title is decorative, and the calendar, account, inbox settings, import, export and rules pages get a screen-reader-only h1. The admin overview cards use h2 so the heading levels no longer skip.

Landmarks: the sidebar nav is named after the app and the settings navs after their sections, so they can be told apart. The admin page chrome (license badge and account menu) is a header instead of a bare span, so it is inside a landmark.

* Test the route title lookup

Covers the longest-prefix match and the routes that keep their own title.

---------

Co-authored-by: Can Kirca <9063990+cankirca@users.noreply.github.com>
Co-authored-by: Hieu Nguyen (Jack) <nc.hieu@live.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 02:37:59 +07:00
Can K.andCan Kirca 08316b5727 Announce errors and status messages and name the controls that had no name (#93)
Error and status messages were mounted as plain paragraphs, so a screen reader said nothing when sign-in failed, a form was saved or a request was rejected. Error lines now have role="alert" and neutral status lines role="status" across the settings, admin, auth, contact and message dialogs. On the sign-in form the error is also tied to the email and password fields with aria-invalid and aria-describedby. The compose toast is an alert for errors and a status otherwise, and new-mail notifications are announced through a persistent status region.

The loading overlay put aria-label on a plain div, which is ignored, and left its icon image readable after loading. It is now a status region that says "Loading" only while it is visible, and the logo is decorative.

Names and labels: the "open booking page" button contained a link and had no name (now a single link with a name), the import file input was not tied to its label, the AWS and Resend key fields only had placeholders, the QR code has a text alternative, the quoted-text toggles in the reader and composer are named (the reader's was hard-coded English), the composer link field has a label, the "Move to" button no longer overrides its visible text with a different name, and the snooze presets report which one is selected. The drive sort buttons no longer carry aria-sort, which is not allowed on a button, and its rows no longer claim role="row" outside a grid.

Identity fields in the profile, forwarding and sign-up forms get autocomplete tokens, and the domain field no longer says it is a URL.

Co-authored-by: Can Kirca <9063990+cankirca@users.noreply.github.com>
2026-10-11 02:35:24 +07:00
65df2554ec Give dialogs, menus and the composer proper focus handling (#92)
The command palette and the shortcuts help were plain divs: no dialog role, no label, focus stayed on the page behind them, Tab walked out, and closing did not return focus. The palette now is a labelled modal dialog with a combobox input, a listbox of options and aria-activedescendant, and keeps the highlighted option in view. The help dialog gets role="dialog", a label from its heading, focus on its close button, Escape and "?" to close, and a Tab trap. Both give focus back to what opened them. The shared logic is in use-dialog-focus.ts.

The "More actions" menus on a message and on a conversation card, and the account menu, closed only on an outside click. Escape now closes them and returns focus to their button, and moving focus out of them closes them too (use-dismissible.ts).

The composer did not take focus when opened and did not give it back. A new message now focuses the recipients field, an opened draft focuses the body, closing returns focus to the button that opened it, and the floating composer is a labelled region.

Tooltips blurred whatever you had activated, so keyboard users lost their place after every icon button. They no longer do, and Escape dismisses them.

Single-key shortcuts no longer fire while focus is inside a dialog, menu or listbox.

Co-authored-by: Can Kirca <9063990+cankirca@users.noreply.github.com>
Co-authored-by: Hieu Nguyen (Jack) <nc.hieu@live.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 02:35:05 +07:00
1504333795 Make message rows reachable by keyboard and add a skip link (#91)
The link that opens a message in the wide list used display: contents, so it had no box and could not take focus. Tab went through the checkbox, star and row buttons of every message but never to the message itself, which left keyboard users unable to open mail. The star button also sat inside that link, which is nested interactive content.

The link now lives on the subject and stretches over the row with a pseudo-element, so clicking anywhere on the row still opens the message. The checkbox, star and hover actions sit above it. The focus ring is drawn around the whole row, because the subject's own outline was clipped by truncate. The rows also get list and listitem roles, the open message is marked aria-current, unread rows say so to screen readers, and the checkbox is named after the sender like in the compact layout. The star keeps one name and reports its state with aria-pressed.

Every layout gets a "Skip to main content" link as its first tab stop, pointing at the main landmark. It stays hidden until it is focused. The two new strings are in all catalogs.

Co-authored-by: Can Kirca <9063990+cankirca@users.noreply.github.com>
Co-authored-by: Hieu Nguyen (Jack) <nc.hieu@live.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 02:30:32 +07:00
Can K.andCan Kirca f10093dcf5 Make "Move to Inbox" work from Snoozed and custom folders (#90)
Moving a snoozed message to the Inbox only set its status to received, which it already was, and left snoozedUntil alone. The Snoozed list keeps showing messages whose snooze has not run out, and the Inbox hides them, so nothing visible happened. Moving to the Inbox now clears the snooze.

The toolbar also left "Inbox" out of the move menu whenever it did not know the folder, which is always the case in a custom folder, so mail could not be moved back to the Inbox from there. It is only hidden in the Inbox itself now.

Co-authored-by: Can Kirca <9063990+cankirca@users.noreply.github.com>
2026-10-11 02:27:31 +07:00
Can K.andCan Kirca c105dedd43 Make the "g" navigation shortcuts match reliably (#89)
useHotkeys pushed every keydown into the sequence buffer and only matched when the whole buffer equalled a shortcut. Two things broke that: the Shift keydown of "g" then "!" put "shift" in the buffer (so "g !", go to Spam, never matched), and any key pressed within 800 ms before a sequence (for example "j" and then "g i") left a stale entry in front of it.

Modifier keys no longer enter the buffer, and a sequence now matches when it ends the buffer. A single key still only matches as the whole buffer, so lone shortcuts behave as before. The matching moved to use-hotkeys-utils.ts with a small test.

Co-authored-by: Can Kirca <9063990+cankirca@users.noreply.github.com>
2026-10-11 02:26:08 +07:00
Can K.andCan Kirca 635ed0ca3b Tell the user when a list action fails (#88)
Archive, trash, spam, move and delete from the message list did nothing visible when the request failed: the toolbar buttons and the row buttons let the error go unhandled, so the message simply stayed where it was. The list now shows a dismissing alert with the existing "Could not update message" text, for both the bulk toolbar and the row actions.

Co-authored-by: Can Kirca <9063990+cankirca@users.noreply.github.com>
2026-10-11 02:25:50 +07:00
Can K.andCan Kirca e938c0f0ec Drop removed messages from the selection and step back from an empty page (#87)
Actions on a single row (the hover buttons and swipe) took the message out of the list but left it in the selection. The toolbar kept saying "1 selected" while nothing was ticked, and a bulk action then ran on a message that was no longer there, for example moving a message you had just trashed to Archive. The selection is now trimmed to the rows that are still in the list.

Archiving or trashing every row on the last page left the list on an offset past the end: it showed the empty-folder text while Previous page was the only way back. When a page other than the first comes back empty, the list now goes to the last page that has messages.

Co-authored-by: Can Kirca <9063990+cankirca@users.noreply.github.com>
2026-10-11 02:25:26 +07:00
dffcc2394e Keep the composer usable when a request fails, and keep what was typed (#86)
* Close the composer after a message is sent

After a successful send the composer kept its window open, emptied, and the "Message sent" toast lived inside it. The popup now closes (the full-page composer goes to Sent) and the confirmation is shown by FloatingComposer through the compose context, so it stays visible once the composer is gone. It is a status region so screen readers announce it.

Sending also cancels the pending autosave and invalidates in-flight ones. Before, a save that fired during the send could recreate the message as a draft, which then sat in Drafts next to the sent copy.

* Keep the composer usable when a request fails, and keep what was typed

Sending while offline, or getting a non-JSON error page back, threw before the busy flag was cleared. The button stayed on "Preparing…" and disabled, with no message. A failed request now shows the send error and frees the form.

Discarding had the same problem: the delete button stayed disabled forever after a network error, and a draft that was already gone (404) could not be discarded from the full-page composer. A 404 now counts as deleted.

The draft is saved 900 ms after you stop typing, and closing the composer or opening another one within that time dropped the pending save, so a message typed and closed quickly was lost. The pending save now runs when the composer unmounts. Send and discard still cancel it.

---------

Co-authored-by: Can Kirca <9063990+cankirca@users.noreply.github.com>
Co-authored-by: Hieu Nguyen (Jack) <nc.hieu@live.com>
2026-10-11 02:24:18 +07:00
Can K.andCan Kirca 4fedfe7388 Fix the star button on conversation cards (#85)
toggleMessageStar returns { starred }, but the card put that whole object into its boolean state. The object is always truthy, so the first click filled the star and every click after that kept it filled until the page was reloaded. Use the starred flag instead. This also clears the type error tsc reported on that line.

Co-authored-by: Can Kirca <9063990+cankirca@users.noreply.github.com>
2026-10-11 02:19:56 +07:00
Can K.andCan Kirca e4c4dd5b97 Stay in the current folder after spam, trash and archive (#84)
Marking a message as spam, moving it to Trash or archiving it from the reading view took you to the Spam, Trash or Archive folder. You now go back to the list you opened the message from, so you can keep working through it. The same goes for blocking a sender, which always jumped to Trash.

getMessageActionRedirect takes the list's href instead of a direction, and the message actions read it from the list visibility context the folder pages already provide, so custom folders work too.

Co-authored-by: Can Kirca <9063990+cankirca@users.noreply.github.com>
2026-10-11 02:18:28 +07:00
Can K.andCan Kirca fed60c1b18 Close the composer after a message is sent (#83)
After a successful send the composer kept its window open, emptied, and the "Message sent" toast lived inside it. The popup now closes (the full-page composer goes to Sent) and the confirmation is shown by FloatingComposer through the compose context, so it stays visible once the composer is gone. It is a status region so screen readers announce it.

Sending also cancels the pending autosave and invalidates in-flight ones. Before, a save that fired during the send could recreate the message as a draft, which then sat in Drafts next to the sent copy.

Co-authored-by: Can Kirca <9063990+cankirca@users.noreply.github.com>
2026-10-11 02:08:08 +07:00
Can K.andCan Kirca 21817cedcb Bring the tests back in line with the code (#82)
* Mark the fixture admin as primary admin in the account tests

Creating accounts needs a primary admin or an admin with user management rights since migration 0050, but the fixture inserted its admin without is_primary_admin, so every create call got a 403.

* Let the agent tool test call mutating tools without chat approval

In chat, mark_email_read, move_email and discard_draft now return pending_approval instead of acting. The test still expected the change to happen right away. It now checks that chat only asks, and runs the direct calls with the mcp origin.

* Test the migrated schema instead of the removed bootstrap SQL

The old test read MIGRATION_NAMES and INITIAL_SCHEMA_SQL out of src/lib/setup/migration.ts, which now just runs the migration files. The replacement applies drizzle/migrations to an empty database, checks that every file is recorded and not re-applied, and keeps the mailbox, license and spam table checks.

* Expect the shortened pt-BR inbox label in the i18n tests

6978c01 changed navigation.inbox from "Caixa de entrada" to "Entrada" in pt-BR, but four tests still expected the old label.

---------

Co-authored-by: Can Kirca <9063990+cankirca@users.noreply.github.com>
2026-10-11 02:06:49 +07:00