Cinatra — Application Design — Notifications

The unified /notifications surface — one chronological list that holds notifications and pending approvals together, filtered (not clustered), reached from a badge-only bell. It replaces the approvals inbox and the bell flyout. Part of Cinatra Application Design; match it exactly.

Version 0.1.1

I. One surface, one chronological list

Route /notifications · notifications + approvals · one list

One page replaces two. /notifications is the single home for both notifications and pending approvals. It absorbs the former /configuration/approvals inbox and the bell flyout outright — both surfaces are gone (§VII). The page opens with the standard page header (title Notifications, a one-line description, a closing etched rule), then a filter-chip rail (§III), then the list.

Exactly one list, ordered by time. Notifications and pending-approval rows are interleaved into a single chronological list, most-recent first by each item's created time. There are no clusters and no section headers — the Needs your decision / Your requests groupings carried over from the app spec's approvals inbox (app.html §IX) are retired and must not appear here. A view is never split into "your requests" versus "awaiting you"; those are expressed as filters over the one list, never as separate sections or lanes.

Approvals stay read-time federated. Pending-approval rows are read at request time from the existing approval-source registry; they are not materialized as rows in the notifications table. Only the page is unified — the two data models are unchanged behind it. A notification row is a per-user read/unread row; an approval row is an org-shared decide-once item with eligibility (§II).

Example — /notifications, populated (one interleaved list)

Notifications

Everything that needs your attention — updates and pending approvals, newest first.

  • Approve access scope for Outreach agent Awaiting you

    Agent approval · requested by a teammate · 4 minutes ago

  • Prospect list finished — 240 rows

    Prospect Lists · 22 minutes ago

  • Your request to publish “Q3 sequence” Awaiting others

    Promotion approval · you requested this · 1 hour ago

    no action for you
  • Deploy to production succeeded

    Workflows · yesterday

II. Row anatomy — one shell, two species

Uniform slots · read-state ≠ eligibility

One row shell, uniform slots. Every row — notification or approval — is rendered through the same shell, whose four regions comprise six uniform slots: a leading icon / kind glyph (slot 1); a title line carrying the title (slot 2) plus, on approval rows, the status pill (slot 3); a subtitle / meta line carrying the source, actor and context (slot 4) together with the timestamp (slot 5); and a trailing status / action slot (slot 6) that carries EITHER the notification read-dot OR the approval inline-action. Every row fills all six slots — icon/kind, title, status pill, subtitle/meta, timestamp, and the trailing status/action — even when a given slot is empty. Rows are hairline-divided, the last row dropping its divider.

Read-state is a notifications-only concept. The Unread read-dot and the mark-read affordance apply to notification rows only. Approval rows carry no read-state at all — they are org-shared decide-once items, so there is no per-user "unread" to show; their status slot carries a status pill instead, and the Unread filter never counts them.

“Needs action” is eligibility, not raw pendingness. The Needs action state — the chip (§III) and the bell badge (§IV) — reflects rows the viewer can actually decide, not every pending approval. A read-only passthrough row (a legacy inbox item marked non-actionable) does not qualify; an approval the viewer themselves requested and cannot self-approve does not qualify. Eligibility, evaluated per viewer, drives the row's inline action, the Needs action chip, and the badge; a pending row the viewer cannot act on renders its status pill but exposes no decide action.

Example — notification row (carries read-state)
Import finished

Data · 5 min ago

Status slot = read-dot. Timestamp in the meta line. No decide action.

Example — approval row (no read-state; eligibility-gated action)
Approve connector install Awaiting you

Marketplace · 12 min ago

Status pill on the title line; the trailing slot holds the inline decide because the viewer is eligible. Never a read-dot.

III. Filters — All · Needs action · Unread · In progress

Chips FILTER the one list · never tabs

Four chips, one list. A chip rail sits above the list with exactly these chips, in this order: All (the default, selected on load), Needs action, Unread, In progress. These are filters, not tabs or views — selecting a chip narrows the single chronological list in place; it never switches to a separate list, lane, or route, and never changes the interleaving or the time order of what remains.

What each chip shows. All is every row (default). Needs action is the eligibility set from §II — only rows the viewer can actually decide (never mere pendingness). Unread is unread notifications only — approval rows, which carry no read-state, are never in it. In progress is the in-flight items (runs and jobs still working), collapsed by job. Selecting a chip applies its filter over the one list; clearing back to All restores the full list.

Example — filter chips (All selected on load)

Selecting a chip filters the list below in place. There is no second list and no route change — the chronological order of the surviving rows is unchanged.

IV. The bell — badge + link, no flyout

Badge count · navigates to /notifications

The bell is a badge and a link — nothing more. The top-bar bell shows a count badge and links to /notifications. Clicking it navigates to the page; it does not open an in-place flyout, dropdown, or panel. There is no hover preview and no inline list under the bell — the flyout is retired (§VII). The whole entry into notifications is the page itself.

The badge counts what needs the viewer. The badge is driven by the same viewer-eligibility signal as the Needs action chip (§III) — it counts what the viewer can actually act on, not raw pendingness. It is not a raw pending-approval count.

Example — bell (badge + link; click navigates, no panel)
2

The badge is the eligibility (Needs action) count. Activating the bell routes to /notifications. No dropdown opens under it.

No flyout

The bell has no attached panel. It is a badge and a link — the only in-place notification UI is the /notifications page. The former flyout list under the bell is removed, not relocated.

V. Empty state & vendor gating

One empty state · vendor rows gated

One universal empty state. When there is nothing to show, the page renders a single empty state whose text is exactly “No notifications”. There is never a per-type or per-source empty state — no "no approvals", no "no marketplace items", no per-kind empties. The one message covers an empty list regardless of which underlying sources are quiet.

Vendor content is gated on registration. Vendor rows and vendor copy render only when the instance is a registered vendor. On any instance that is not a registered vendor, zero vendor rows and zero vendor copy appear — the vendor surface is absent, not merely empty. Sources that are not connected or not configured contribute zero rows and zero UI to this surface (connectivity is owned by configuration, not this page).

Example — empty (the single universal state)

No notifications

Example — non-vendor instance (zero vendor UI)

On a non-vendor instance the list shows only its notifications and approvals. No vendor rows and no vendor copy render at all — the vendor surface does not appear until the strict registered-vendor predicate holds.

VI. Degraded sources — one inline line

One line · never per-source sections

A failed source degrades to one line. Approvals are federated across several sources; when one or more of them fail or time out, the page shows a single inline line reading exactly “some approvals are currently unavailable”, above the list. It never renders a per-source error section, a per-source card, or a stack of per-source failure notices. This explicitly supersedes and retires the app spec's approvals-inbox per-source-card / per-source-error layout (app.html §IX) for this surface — a global chronological sort forbids streamed per-source sections anyway.

Example — degraded source (single inline line)
some approvals are currently unavailable

One line for any number of failed sources. The rest of the list renders normally beneath it; no source gets its own error block.

VII. Entry points & retirement

Sidebar Approvals gone · flyout + /configuration/approvals removed

The left-sidebar “Approvals” entry is gone. The sidebar no longer carries an Approvals navigation item. The only entry points into approvals are the bell (§IV) and the /notifications page itself. There is no separate approvals destination in the navigation.

The old surfaces are removed. The /configuration/approvals inbox and the bell flyout are deleted, not relocated. The ?direction=inbox|mine URL machinery is gone from the surface; inbox/mine survives only as an internal domain concept (the approval tools and per-row eligibility still need it), never as a tab, a route parameter, or a visible view here.

Retired here, not patched

The approvals inbox and the bell flyout are superseded by this one surface. Their clustering (Needs your decision / Your requests), their per-source error cards, and the sidebar Approvals item do not carry forward. The retirement of those sections in the app spec is a separate, owner-gated publish; this page is the pinned contract they retire toward.