Connection lost. Reconnecting… attempt 1 of 8
Paused. Your work is held on the server.
Could not reconnect.
This session has expired on the server.
An unhandled error has occurred.
Sedna.UI
v0.17.0 · main

Common pages

tier 1 — the frame

The pages every app has and nobody designs: access denied, not found, the thing that threw, an expired session, a maintenance window. Each is a shell from Shell with one .empty-state--page block in it.

These are compositions, not new classes Copy the markup and do not restyle it per project — an app that has styled its empty states has already styled these.

Access denied

.auth-layout, because there is no navigation to offer somebody who cannot get in. Name the role and who can grant it, make asking for it the primary action, and put the account line in .auth-foot — a 403 is often the wrong account rather than a missing role.

Dispatch Console
403 Forbidden You do not have access to this workspace

Your account is signed in, but it holds no role in Northwind Retail. An account lead has to grant you one before anything here is visible.

Back to the portal

Signed in as alex.fischer@example.com · Sign in as somebody else

<div class="auth-layout" style="min-height:0; height:560px; padding: var(--space-9)">
    <div class="auth-brand">
        <span class="brand-logo" style="width:32px; height:32px; background:var(--brand)"></span>
        Dispatch Console
    </div>

    <div class="empty-state empty-state--page">
        <svg class="state-art state-art--lg" aria-hidden="true"><use href="#no-access" /></svg>
        <span class="empty-state-code">403 Forbidden</span>
        <span class="empty-state-title">You do not have access to this workspace</span>
        <p>
            Your account is signed in, but it holds no role in <strong>Northwind Retail</strong>.
            An account lead has to grant you one before anything here is visible.
        </p>
        <div class="empty-state-actions">
            <button class="btn btn-sm btn-primary"><i class="ri-mail-send-line"></i> Request the viewer role</button>
            <a class="btn btn-sm" href="#"><i class="ri-arrow-go-back-line"></i> Back to the portal</a>
        </div>
    </div>

    <p class="auth-foot">Signed in as alex.fischer@example.com &middot; <a href="#">Sign in as somebody else</a></p>
</div>

Not found

The one that keeps the frame: the reader is signed in and the nav still works, so .layout stays and the block goes in .page, where it sits at the top rather than centring. Name the thing that was not found — the reference is what the reader will paste into a search or quote to a colleague.

The topbar is the app's ordinary one, unchanged. The trail to the missing record belongs to the page, so the .breadcrumb goes at the top of .page, never in .topbar.

404 Not found There is no order ORD-48812

It may have been merged into another order, or the link may be older than the 90 days orders are kept for. Searching the reference will find it if it was merged.

<div class="layout" style="height:560px; border:1px solid var(--border); border-radius:var(--radius-panel); overflow:hidden">
    <aside class="sidebar">
        <div class="brand">
            <div class="brand-logo" style="background:var(--brand)"></div>
            <div class="brand-text">
                <strong>Dispatch Console</strong>
                <span class="brand-sub">Northwind Retail</span>
            </div>
        </div>
        <nav class="nav">
            <div class="nav-scroll">
                <a class="nav-link" href="#"><i class="ri-inbox-line"></i><span>Queue</span><span class="nav-count">3</span></a>
                <a class="nav-link active" href="#"><i class="ri-chat-3-line"></i><span>Orders</span></a>
                <a class="nav-link" href="#"><i class="ri-check-double-line"></i><span>Decided</span></a>
                <a class="nav-link" href="#"><i class="ri-settings-3-line"></i><span>Settings</span></a>
            </div>
        </nav>
    </aside>

    <div class="content">
        <header class="topbar">
            <button class="topbar-btn topbar-btn--start" aria-label="Collapse sidebar" data-tip="Collapse the sidebar to an icon rail."><i class="ri-side-bar-line"></i></button>
            <div class="topbar-spacer"></div>
            <div class="user-widget">
                <button class="user-trigger">
                    <span class="user-avatar"><i class="ri-user-line"></i></span>
                    <span class="user-info">
                        <span class="user-name">Alex Fischer</span>
                        <span class="user-email">alex.fischer@example.com</span>
                    </span>
                </button>
                <a class="user-signout" href="#" aria-label="Sign out" data-tip="Sign out of the console."><i class="ri-logout-box-r-line"></i></a>
            </div>
        </header>

        <div class="page">
            <nav class="breadcrumb" aria-label="Breadcrumb">
                <a class="breadcrumb-item" href="#">Orders</a>
                <span class="breadcrumb-item" aria-current="page">ORD-48812</span>
            </nav>

            <div class="empty-state empty-state--page">
                <svg class="state-art state-art--lg" aria-hidden="true"><use href="#not-found" /></svg>
                <span class="empty-state-code">404 Not found</span>
                <span class="empty-state-title">There is no order ORD-48812</span>
                <p>
                    It may have been merged into another order, or the link may be older than the
                    90 days orders are kept for. Searching the reference will find it if it was
                    merged.
                </p>
                <div class="empty-state-actions">
                    <a class="btn btn-sm btn-primary" href="#"><i class="ri-search-line"></i> Search for the reference</a>
                    <a class="btn btn-sm" href="#"><i class="ri-inbox-line"></i> Back to the queue</a>
                </div>
            </div>
        </div>
    </div>
</div>

Something went wrong

.empty-state--failed colours the drawing and nothing else. Say whether retrying is safe, because the reader cannot know whether the write went through, and give them the reference in .empty-state-detail with a data-copy button beside it.

This is the one drawing on the page that does not change when you switch theme in the header, and that is the modifier working: it re-points --state-accent at --danger-fg, and the danger ramp is deliberately outside the brand's — a failure must not turn green because the brand did. The other four pages are not failures, so they keep the brand accent and move with it.

Dispatch Console
500 Server error Something went wrong loading the queue

The request failed before it reached the workflow API. Nothing was decided and nothing was lost — trying again is safe.

Reference 8f3c2a71-4d90-4f0e-9c2b-1a77e5b0c344 · 09:41 CEST

Service desk

Quote the reference and the service desk can find the request in the log.

<div class="auth-layout" style="min-height:0; height:600px; padding: var(--space-9)">
    <div class="auth-brand">
        <span class="brand-logo" style="width:32px; height:32px; background:var(--brand)"></span>
        Dispatch Console
    </div>

    <div class="empty-state empty-state--page empty-state--failed">
        <svg class="state-art state-art--lg" aria-hidden="true"><use href="#server-error" /></svg>
        <span class="empty-state-code">500 Server error</span>
        <span class="empty-state-title">Something went wrong loading the queue</span>
        <p>
            The request failed before it reached the workflow API. Nothing was decided and nothing
            was lost — trying again is safe.
        </p>
        <p class="empty-state-detail">Reference 8f3c2a71-4d90-4f0e-9c2b-1a77e5b0c344 &middot; 09:41 CEST</p>
        <div class="empty-state-actions">
            <button class="btn btn-sm btn-primary"><i class="ri-refresh-line"></i> Try again</button>
            <button class="btn btn-sm" data-copy="8f3c2a71-4d90-4f0e-9c2b-1a77e5b0c344">
                <i class="ri-file-copy-line" data-copied-hide></i><span data-copied-hide>Copy reference</span>
                <i class="ri-check-line" data-copied-show="ok"></i><span data-copied-show="ok">Copied</span>
            </button>
            <a class="btn btn-sm btn-ghost" href="#"><i class="ri-customer-service-2-line"></i> Service desk</a>
        </div>
    </div>

    <p class="auth-foot">Quote the reference and the service desk can find the request in the log.</p>
</div>

Session expired

For the hard redirect. Where the app is still running and only the circuit dropped, that is the .status-bar's job instead; say what happened to the work in progress, or the reader redoes something they did not need to.

Dispatch Console
401 Session expired Sign in again to carry on

You were signed out after 12 hours, which is the workspace policy. The order you were reviewing has not been decided and will still be in the queue.

You will come back to the page you were on.

<div class="auth-layout" style="min-height:0; height:560px; padding: var(--space-9)">
    <div class="auth-brand">
        <span class="brand-logo" style="width:32px; height:32px; background:var(--brand)"></span>
        Dispatch Console
    </div>

    <div class="empty-state empty-state--page">
        <svg class="state-art state-art--lg" aria-hidden="true"><use href="#session-expired" /></svg>
        <span class="empty-state-code">401 Session expired</span>
        <span class="empty-state-title">Sign in again to carry on</span>
        <p>
            You were signed out after 12 hours, which is the workspace policy. The order you were
            reviewing has not been decided and will still be in the queue.
        </p>
        <div class="empty-state-actions">
            <a class="btn btn-sm btn-primary" href="#"><i class="ri-login-circle-line"></i> Sign in</a>
        </div>
    </div>

    <p class="auth-foot">You will come back to the page you were on.</p>
</div>

Maintenance

The title is the time it comes back, not the word “maintenance”. Give a window and a way to check, and keep the same page for the planned and unplanned cases.

Dispatch Console
503 Maintenance Back at 21:00 CEST

The workflow database is being upgraded. Approvals already submitted are queued and will be decided as soon as the window closes.

Planned window: 20:00–21:00 CEST, Wednesday 9 September.

<div class="auth-layout" style="min-height:0; height:570px; padding: var(--space-9)">
    <div class="auth-brand">
        <span class="brand-logo" style="width:32px; height:32px; background:var(--brand)"></span>
        Dispatch Console
    </div>

    <div class="empty-state empty-state--page">
        <svg class="state-art state-art--lg" aria-hidden="true"><use href="#maintenance" /></svg>
        <span class="empty-state-code">503 Maintenance</span>
        <span class="empty-state-title">Back at 21:00 CEST</span>
        <p>
            The workflow database is being upgraded. Approvals already submitted are queued and
            will be decided as soon as the window closes.
        </p>
        <div class="empty-state-actions">
            <a class="btn btn-sm btn-primary" href="#"><i class="ri-refresh-line"></i> Check again</a>
            <a class="btn btn-sm" href="#"><i class="ri-external-link-line"></i> Status page</a>
        </div>
    </div>

    <p class="auth-foot">Planned window: 20:00&ndash;21:00 CEST, Wednesday 9 September.</p>
</div>

Which shell, which drawing

Each page has its own drawing, chosen by what the page is. Reusing one across two of them makes them look like the same page to somebody scanning a screenshot in a ticket. The full set is on State art.

PageShellDrawingPrimary action
Access denied.auth-layout#no-accessRequest the role
Not found.layout.page#not-foundSearch for the reference
Server error.auth-layout#server-errorTry again
Session expired.auth-layout#session-expiredSign in
Maintenance.auth-layout#maintenanceCheck again

Sign-in itself is not on this list because it is not a state — it is a form, and it belongs in an .auth-card.

Writing one

Three lines, in this order: what would normally be here, why it is not, and the one action that changes that. .empty-state-code carries the status code above the title, small and quotable, and the drawing is .state-art--lg here.

One primary action, and at most two beside it. .empty-state-actions wraps and centres them and takes the top margin off the buttons, so a second action does not stagger against the first on a narrow screen.