The demo user becomes Nora Lindqvist, engineering lead at Meridian Labs,
in the week of a product launch. The fixtures tell one story across mail,
contacts, calendar, tasks and files, and exercise the features the
screenshots and the public demo should show:
- a second, mail-only login (her personal domain) and the shared Support
mailbox delegated to the work account, so the account switcher, the
shared section and the merged inbox have real content
- raw headers the way Stalwart writes them: the sender check fails on
the phishing mail, newsletters carry a one-click unsubscribe, one mail
asks for a read receipt, and spam scores show in the details
- an iMIP invitation with a served .ics, an event waiting for the answer
on the calendar, and RSVP patches applied in the demo client
- @mentions as mailto links in a received mail, a verification code
twelve minutes old, nested folders, named tags on the mail, contact
groups, a scheduled message and tasks switched on
- every attachment and file backed by real bytes under public/demo,
generated by `scripts/demo-assets/generate.mjs`; contact photos are
bundled portraits instead of requests to randomuser.me
- folder counts computed from the mail instead of hand-kept numbers
Demo login seeds the tags, templates and merged-inbox settings once per
device and switches the cross-account gate on for the session; "Reset"
restores them. `bulwark-demo-simulate=off` in localStorage stops the
simulated incoming mail for screenshot runs.
With the navigation collapsed, Mountain View gives the sidebar a 72px
column, but the sidebar inside kept the default layout's 48px width. The
round compose button was cut in half, the selected folder's highlight
ran off the edge, and an empty strip showed before the message list.
The toolbar's run() had only try/finally, so a failed archive or tag
write rejected silently. Delete, move and archive now go through
runBatchEmailAction with the same messages as the default list, and
tag, read and spam actions show their success and error toasts.
The top bar also starts update polling. It replaces the rail, which
used to start it, so the update dot on the admin icon never showed.
Under the message the reply buttons sat on the tinted strip the default
layout uses for its quick reply, a different colour from the message
above them in the light theme. Mountain View now reads the whole
conversation on the page colour.
The account avatar in the top bar got a square highlight while every
other button there is round.
The top bar replaces the navigation rail on desktop, and with it went
the shield that takes a Stalwart admin to the console, so an admin under
Mountain View had no way in. The bar now shows it, with the same update
dot. The check that decides it moves out of the rail into
useStalwartAdmin so both use the same one.
- The refresh button was labelled "Loading" and the overflow menu
"Mark folder as read"; they now say Refresh and More actions.
- The Starred row stayed highlighted while a tag was open, and its icon
ignored the colourful-icons setting.
- Reply, Reply all and Forward use the standard small button size.
- With a single login the account popover offers one Sign out pill
instead of a pill-less footer row.
- The compose backdrop matches the other dialogs.
- Layout settings hide the mail layout and toolbar label options, which
do nothing under Mountain View; their values still apply to the
default layout.
Calendar, contacts, files and settings still drew the left navigation
rail under Mountain View, so moving away from mail switched to the
default frame. They now show the Mountain View top bar too, without the
mail search and the menu button, which only make sense in mail.
Add an "Interface layout" setting at the top of Settings > Layout with two
options, Default and Mountain View. Default is the existing layout and stays
the default, so nothing changes unless a user opts in.
Mountain View rearranges the chrome while keeping the theme's colours, icons
and type:
- desktop: a top bar with a wide search field (filters drop from the field),
an apps grid and the account avatar instead of the left app rail; flat
navigation with Starred under Inbox and the rest behind "More"; a Compose
pill; one list toolbar with select-all, refresh, overflow menu, a 1-N of
total range and the batch actions; rows led by a checkbox and a star; an
icon-only message toolbar with Reply / Reply all / Forward under the
message; compose in a docked window; an account popover with per-account
unread counts; j/k cursor, x to select and g-sequences
- phone: rounded search header, compact rows with the star on the preview
line, and Reply / Reply all / Forward at the end of a message instead of
the bottom action bar
It is one setting (`interfaceLayout`), written to a data attribute on <html>
that keys a block of CSS tokens in globals.css, plus a few components gated
on the flag. The mail layout in force is derived (effectiveMailLayout), so
the user's saved mail layout is never rewritten. The setting is listed in
the admin policy tab, and the new strings are translated in all locales.
Includes unit tests and a Playwright smoke test against a running instance
(scripts/mountain-view-smoke/).
An aria-label on a plain span is not reliably read out, so the count
now comes with a visually hidden label and the digits themselves are
hidden from screen readers. Also reuses the sidebar's existing accounts
selector instead of subscribing to the same list twice.
After a load the logins other than the one on screen reconnect in the
background, and until they are all back the unified counts cover only
some of them. With many logins that lasts seconds, and a partial total
looks exactly like the whole one.
While that restore runs, the "All accounts" header shows how many logins
are already in the scope, e.g. "4/10", with a translated label for screen
readers. It goes away when the scope is complete or the restore is over -
a login that failed to reconnect does not keep it up.
`restoringAccounts` in the auth store marks the background restore; the
count itself is a pure function with its own tests.
Once a search runs the header field shows the active query, but the only
way back to the full list was to open the panel and clear it there.
Put a clear button at the trailing end of the field, shown only while a
query is applied. The field is now a container rather than one button,
since nesting a button inside a button is invalid markup — the label
area and the clear control are siblings.
Phones stacked two 56px bars: the mailbox title, then a row holding
select-all, the search box, the filter toggle and refresh. That is 112px
of chrome before the first message, and the title merely repeats what
the drawer already shows.
The header now carries the account avatar and a tappable search field,
and the second row is presented full-screen when that field is tapped —
so the same SearchBox, suggestions, history and filter chips appear,
none of it rebuilt. Select-all and refresh remain in that panel, and the
list already supports long-press selection and pull-to-refresh.
Tablet and desktop are unchanged; the row is only relocated on phones.
The rail variant flies its popover out sideways, which is right for a
narrow vertical sidebar but wrong at the top of the screen: anchored to
a trigger at the inline-end edge it runs straight off the viewport. In
RTL that edge is the left one, so the menu opened past the left border
and was unreachable.
Add a "header" variant that drops the menu below the trigger and pins it
by its trailing edge, with a max-width so it can never exceed the
viewport. rail and expanded are untouched.