Cinatra — Application Design — Artifacts

/artifacts is the library — everything the acting user's agents and uploads have produced, each row opened by its type's renderer. The library also ingests: an upload affordance types a file by its MIME base, a type picker lets the user assert its meaning among installed types, and an inline marketplace adds a type when none fits (admins install; others request). Dashboards are artifacts too — they list as rows and open the dashboard itself at its canonical surface (never rendered inline); a team, project or organization lists the dashboards in its scope on its Dashboards tab, where a scope manager can add an existing dashboard as a secondary listing (§IX). /configuration/artifacts is the Artifacts console — a tablist page whose Type definitions tab lists every type every artifact extension defines, whose Stored objects tab inventories every stored object across every artifact extension, and whose Restore objects tab browses and undoes object change sets. Artifact change review lives on the shared artifact-review surface. Part of Cinatra Application Design; match it exactly.

Version 0.8.0

I. Two surfaces — the library and the artifacts console

/artifacts library · /configuration/artifacts console

/artifacts is the library, and nothing else. It lists exactly the acting user's artifacts, for every user — there is no mode toggle, because the library is the lone view. The page opens with the standard page header (title Artifacts, a one-line description, a closing etched rule), then a toolbar over the list. The sidebar entry points here.

/configuration/artifacts is the Artifacts console. It opens with the standard page header (title Artifacts) closed by a canonical underline tablistType definitions first, Stored objects second, Restore objects third (Application Design — Components · Tabs). The Type definitions tab lists every type every artifact extension defines; the Stored objects tab is a workspace-wide inventory of every stored object across every artifact extension; the Restore objects tab browses past object change sets (time-keyed, across every extension) and undoes them (§IV). Artifact change review lives on the shared artifact-review surface (§V).

A targeted restore has its own route. Reversing a single change set the acting user is authorized for is authorized per object, so it also has a dedicated route, /configuration/artifacts/restore/[changeSetId], nested under the Artifacts console and reachable by any authorized user (§IV).

Example — /artifacts, the library (every user; no mode toggle)

Artifacts

Everything your agents and uploads have produced.

Example — /configuration/artifacts, the Artifacts console (tablist: Type definitions · Stored objects · Restore objects)

Artifacts

Every type your artifact extensions define, every stored object across them, and the change sets you can undo.

II. Library — the actor's artifacts

Artifact rows · the acting user's set

Exactly the acting user's set. The library lists precisely the artifacts the acting user may see, resolved per actor against each defining extension's access grants, so two users on the same workspace can see different libraries. It never shows non-artifact internals. Rows are artifacts: each is an object of a defined type, presented by its display name — never a raw type id.

Row anatomy. Each row is a title line over a muted meta line, hairline-divided, the last row dropping its divider. The title line carries a leading renderer glyph, the artifact name (the artifact's display name), and a muted extension label naming the type's defining extension — the extension that defines that type, listed on the console's Type definitions tab (§IV). The row opens through that type's renderer, or, when the extension ships no renderer for the representation, through the generic fallback (§III). A file-form artifact (an upload) shows its MIME type in a mono label and opens through the MIME viewer. A dashboard row is the one exception: it renders nothing inline and instead navigates to the dashboard's canonical surface (§VIII). The meta line carries the owner / visibility and the relative updated time; the row's right edge holds the Open affordance.

Faceting & search. The Type facet filters the list by defining extension or type; the search field filters by name. An unfiltered library is grouped only by recency.

Example — Library, populated
  • Q3 re-engagement email Outreach

    Team: Growth · Draft · updated 8 minutes ago

    Open
  • Enterprise ICP · 240 prospects Prospect Lists

    Organization: Acme Corp · updated 2 hours ago

    Open
  • 2026 pricing sheet.pdf application/pdf

    Private · uploaded yesterday

    Open
Example — empty

No artifacts yet

Artifacts appear here as your agents produce work and as you upload files.

Example — error

Couldn't load your artifacts

The library source failed. Retry

III. Renderer dispatch — how a row opens

Type renderer · MIME · fallback

Dispatch by type. Opening a row resolves its renderer from the artifact's type — the type its defining extension registered a renderer for. Three dispatch cases cover every row:

Read authorization. A row the viewer may list but not read opens to a not-authorized panel, never to its bytes — the surface never serves a representation the viewer isn't cleared to read.

Example — typed renderer (email draft)
Outreachtype renderer
To: prospects@acme.example
Re-connecting on Q3 priorities

Hi there — following up on the pilot we scoped last quarter. Are you open to a short call next week?

Example — MIME handler (PDF)
application/pdfMIME viewer
Example — generic fallback (no renderer)
@acme/support:casestructured data
{
  "subject": "Login loop on SSO",
  "priority": "high",
  "open": true
}

IV. The Artifacts console — type definitions, stored objects, restore

/configuration/artifacts · Type definitions · Stored objects · Restore objects · /configuration/artifacts/restore/[changeSetId]

One page, three tabs. /configuration/artifacts is the Artifacts console. Its page header carries a canonical underline tablistType definitions first, Stored objects second, Restore objects third (see Application Design — Components · Tabs; underline only, no pill tabs). The tablist selects between a global type registry, a global stored-object inventory, and the change-set restore console; the etched paired-line rule begins to the right of the last tab and runs to the page edge, and the tabs never stack a second rule.

Type definitions — the global type registry. The Type definitions tab lists every type every artifact extension defines, alphabetically across all extensions — one workspace-wide registry, never scoped to a single extension. A type exists only because an installed artifact extension defines it, so exactly one extension defines each type; any extension that needs it declares the defining extension as a dependency, which guarantees the type is present wherever it is used. Each row therefore names the type (its display name over its type id), the one Defined by extension (its manifest display name), and the Used by extensions that declared that dependency. It is a read-only inventory: record inspection only, taking no actions and making no decisions. The empty state reads that no artifact extension defines a type yet.

Example — Type definitions tab, the global type registry (every artifact extension, alphabetical)
Type Defined by Used by
Case
@acme/support:case
Support Desk Escalations
Email draft
@cinatra-ai/email:draft
Email Sequences, Inbox Zero Agent
Email thread
@cinatra-ai/email:thread
Email
Prospect list
@cinatra-ai/prospects:list
Prospect Lists
Every stored object has a defining extension

There is no untyped floor and no default catch-all extension: every object stored is an object of a type some installed artifact extension defines, and dependents guarantee that type is present at install time (an extension that needs a type declares its definer as a dependency). Record inspection therefore always resolves an object to the extension that defines its type — the Defined by extension listed here, and the type named against every row of the global Stored objects inventory below.

Stored objects — the global inventory. The Stored objects tab lists every stored object of every artifact extension — one workspace-wide inventory for record inspection, never scoped to a single extension. Each row names the object by its display name over a mono meta line (its type id, object id, version and updated time), and the row's right edge carries the object's scope as an entity-named labelTeam: Growth, Organization: Acme Corp. It is a read-only inventory: record inspection only, taking no actions and making no decisions. The empty state reads that no objects are stored yet.

Example — Stored objects tab, the global inventory (every artifact extension)
  • Q3 re-engagement email

    @cinatra-ai/email:draft · obj_2a4e… · v1 · updated 8 minutes ago

    Team: Growth
  • Renewal nudge — cohort B

    @cinatra-ai/email:draft · obj_9c71… · v3 · updated 2 hours ago

    Organization: Acme Corp
  • Enterprise ICP · 240 prospects

    @cinatra-ai/prospects:list · obj_4f10… · v2 · updated yesterday

    Organization: Acme Corp

Restore objects — undoing object change sets. The Restore objects tab browses object change sets and undoes them. Every save, edit or delete an agent run — or a user action — makes over objects is captured as a reversible change set. The tab lists them time-keyed, across every extension, and each entry says exactly what it would restore: the operation (what changed), the affected type, how many objects, and when. The row's Undo action reverses that change set.

Authorized per object. A change set is restorable when the acting user holds restore authorization for every object it touched — the same per-object check the restore action enforces on confirm. There is no administrator bypass: an actor authorized for every affected object may restore it whatever their role, and an actor not authorized for some affected object is refused. The tab lists the change sets the actor may act on; each row's right edge holds an Undo action that opens the restore confirmation in place (an inline modal), and the empty state reads that there is nothing to undo.

Entry points outside the console. Two affordances deep-link to a single change set's restore surface, /configuration/artifacts/restore/[changeSetId]: the in-chat Undo last action chip beneath an agent-run card, and the Saved … · Undo toast on a successful mutation. Each renders only when the acting user is eligible to restore that change set — authorized for every affected object, and the change set still restorable. For a non-eligible user no affordance appears: no chip, no toast action — suppression over a disabled control, because a transient safety affordance that cannot act is noise, and a rendered control must never dead-end on the not-authorized panel. Opened directly, the /configuration/artifacts/restore/[changeSetId] route holds to the same eligibility: it serves the addressed change set's restore surface for an authorized actor of any role, and shows the standard not-authorized state to anyone else.

Example — Restore objects tab, the change-set list
  • Deleted 12 objects · @cinatra-ai/email:draft

    by an agent run · 6 minutes ago

  • Enriched 8 objects · @acme/support:case

    by an agent run · 41 minutes ago

  • Bulk visibility change · 40 objects

    by you · 1 hour ago

Example — targeted-restore entry points, by eligibility
Eligible actor — affordance shown
Saved draft
agent run · deleted 3 objects
Not eligible — no affordance
Saved draft no Undo
agent run · deleted 3 objects

No chip and no toast action — suppressed, not disabled, and never a link that dead-ends on the not-authorized panel.

Example — /configuration/artifacts/restore/[changeSetId], authorized actor (any role)
Restore this change?

Deleted 3 objects · @cinatra-ai/email:draft

change-set cs_5e21… · by an agent run · 2 minutes ago. You are authorized to restore every affected object — no administrator role required.

V. Change review — on the artifact-review surface

Shared review surface · cinatra-ai/cinatra#1795

One shared review surface. Agent-proposed changes to artifacts — enrichment field merges and the like — are reviewed on the shared, generic artifact-review surface. That surface (immutable review targets, gate-provenance authorization, and the host decision chrome) is specified separately in cinatra-ai/cinatra#1795; this spec references it. A notification that a change awaits review deep-links to that surface — decide-once review lands in /notifications (see Application Design — Notifications), which federates pending approvals; a field-level artifact review opens on the artifact-review surface itself.

One place for every decision

A change proposal is a diff to review over an immutable target, exactly the shape the generic artifact-review surface already handles — so every human decision over an artifact, whatever produced the proposal, is reviewed in one place. This spec owns the library, the renderer, and the Artifacts console (type definitions, stored objects, restore); the review surface is cinatra-ai/cinatra#1795. An applied change is an ordinary reversible change set, undoable through the Artifacts console's Restore objects tab (§IV).

VI. Upload & typing — the library ingest path

/artifacts library · MIME-base type · user-meaning picker · cinatra#1883 D3 · rulings 1–2

The library ingests, not just lists. The library toolbar (§I) carries a primary Upload action at its right edge, and the whole list region is a drop target. An upload takes two conceptual steps: a file is typed by its MIME base deterministically, then the user may assert its meaning in a type picker. The base step is exactly-one-or-refuse and never asks the user; the meaning step is optional and always the user's. This section is design only — the pipeline, the required-base extensions and the classifier machinery are cinatra-ai/cinatra#1883 (D3, slices A1–A3).

MIME-base typing — one base or a visible refusal. A file's detected MIME resolves to exactly one installed required base type — the roster is image, pdf, audio, video, chart, and (ruling 1) zip, text, document (office formats) and json. The bases are MIME-disjoint: a detected MIME either lands on one base or is refused (§VI.3). The base is the artifact's format axis and is set at write; the picker never changes it (§VI.1). A base-typed artifact with no asserted meaning is a complete, valid file artifact — meaning is optional, not a precondition.

Placement & states. The Upload control sits in the toolbar; the drop target is the list. The affordance reads across these states — idle (the button, plus a faint hint that files can be dropped), drag-over (the list region highlights and reads Drop to upload), uploading (a transient progress row at the top of the list), typed (the new row appears, or the type picker opens when the user chooses to assert meaning), and refused (the recourse panel — §VI.3). These interaction states are shown as example frames below; the machine-checkable state axis (§X) stays the closed loading · error · empty · kind:artifact set.

Example — the Upload affordance in the library toolbar (idle)
or drop a file
Example — drag-over

Drop to upload

Typed by its file kind on drop.

Example — uploading
2026 pricing sheet.pdf
62%
Example — typed (base resolved, no meaning yet)
2026 pricing sheet.pdfapplication/pdf

Base pdf · Set meaning

VI.1 · The installed-type picker — a user meaning assertion, never a re-type

Meaning, not format. After the base resolves, the user may open the type picker to assert what the file means. The picker admits only installed, file-accepting types whose accepts include the detected MIME — it is a user-sourced meaning assertion over those types, never a re-type: the format base (§VI) is fixed, and a PDF stays a PDF. Confirming the picker writes a user-sourced eligible meaning assertion, which outranks any matcher or agent assertion (see Renderer dispatch §III; the ranked model is cinatra#1883 D1). If no installed type accepts the detected MIME, the picker offers the inline marketplace (§VII).

Derived meaning & suggestion chips (ruling 2). A classifier may derive a meaning for the file. When its confidence is at or above the pack's threshold, that meaning auto-surfaces as the row's presented identity — presentation only, with an org-level toggle as escape hatch — and the picker opens pre-selected on it. When confidence is below threshold, the candidate never auto-applies; it appears as a suggestion chip the user may confirm. Confirming a suggestion records the user's choice as a user-sourced meaning — which outranks the machine's — and the machine's suggestion is never silently promoted into it; dismissing it leaves the base-only row. The assertion lifecycle and the matcher service are cinatra#1883 D1/D2 (slice A3); this spec fixes only the visible provenance and ranking.

Example — type picker (installed types accepting application/pdf; a below-threshold suggestion offered)
What is this?

Assert what 2026 pricing sheet.pdf means. This sets its meaning, not its file kind (application/pdf).

Suggested
Installed types that accept PDF
  • Contract @acme/legal:contractLegal
  • Pricing sheet @acme/sales:pricing-sheetSales
None of these — find a type…
Meaning ranks above the machine; the base never moves

The picker only ever asserts a user-sourced meaning — the highest-ranked identity in Renderer dispatch (§III). It cannot change the file's base (its format axis, set at upload): the choices are the installed types whose accepts admit the detected MIME, never a different file kind. Confirming a below-threshold suggestion chip yields a fresh user-sourced meaning; the machine's draft is never promoted into it (lifecycle: cinatra#1883 D1).

VI.3 · Refusal with recourse

A refused upload is visible, and offers a way forward. Refusal is the base case only: when a file's detected MIME lands on no installed base the file cannot be stored, so the upload is refused — but never silently (this supersedes the chat silent-drop; cinatra#1883 D6). The refusal names the file kind and gives recourse: for a user who can install, a deep link to the marketplace filtered to a base that accepts the detected MIME; for everyone else, a one-click Request install (ruling 4, §VII). The panel never dead-ends. This is distinct from missing meaning: when the base did resolve but no installed meaning type fits, nothing is refused — the artifact is kept as a base-only file row (§VI), and the picker's None of these path (§VI.1) offers a meaning type from the same marketplace.

Example — refused upload (no installed base accepts the detected MIME) with recourse

Can't type schematic.dwg

No installed base type accepts image/vnd.dwg. Install a base that handles it, then re-upload.

VII. Installing a type — the inline marketplace tab

Inline · kind:artifact catalog · admin install · non-admin request · cinatra#1883 D4 · ruling 4

Install without leaving the upload — and installing is admin-only. When no installed type fits, the picker (§VI.1) and the refusal panel (§VI.3) switch to an inline Marketplace tab, beside an Installed tab, inside the same picker panel — so the upload context and the return path are never lost. The tab is the public catalog filtered to artifact-kind packs (cinatra#1883 D4; the storefront itself is Application Design — Marketplace), narrowed to a base when reached from a refusal (§VI.3) or a meaning type when reached from the picker. Whether the tab installs or only requests depends on the actor: a platform administrator installs; everyone else requests (ruling 4).

Administrator — install inline. A platform administrator browses the artifact-kind packs, picks one, confirms an install-scope dialog, and the pack installs without a redirect. The card shows an installing state; once the install settles the new type becomes selectable and the picker (§VI.1) refreshes to include it. The install mechanics — scope, dependency resolution and registry readiness — are Application Design — Extensions / cinatra#1883 D4; this spec fixes only the visible flow and its states.

Non-administrator — one-click Request install (ruling 4). Marketplace installs from the picker stay platform-admin-only. A non-admin gets a functional one-click Request install on each pack: it notifies the platform admins — carrying the pack, the requester and a marketplace deep link — and coalesces repeat clicks onto one request, then flips the card to a muted Request sent state. The admin completes the install from that notification (Application Design — Notifications; the notification's dedup and payload are cinatra#1883 D4). No dead-end, no disabled button with no path.

Example — inline marketplace tab, administrator (artifact-kind packs; one installing)
kind: artifact · accepts PDF
Back to picker typing 2026 pricing sheet.pdf
  • Contract

    Accepts PDF, DOCX · defines @acme/legal:contract

  • Pricing sheet

    Installing · selectable once it settles…

    Installing
Example — non-administrator: one-click Request install (before / after)
Contract

Needs a platform admin to install.

Contract

Requested · admins notified.

Request sent
Install stays admin-only; the request is real

A non-admin never installs from the picker (ruling 4). Request install is a functional action, not a placeholder: it notifies the platform admins — the pack, the requester and a marketplace deep link — reflects a Request sent state, and coalesces repeated clicks onto one request; the admin installs from that notification. The notification's dedup and payload are cinatra#1883 D4.

VIII. Dashboards as artifacts

Library rows · pointer detail · scope chips · cinatra#1883 D7/D9 · ruling 5

A dashboard is an artifact. Every dashboard has a paired artifact row of the type @cinatra-ai/dashboard-artifact:dashboard, so it lists in the library and opens like any other artifact (cinatra#1883 D7/B1–B2). It reads as a normal library row — a dashboard glyph, its name, the muted defining-extension label (Dashboards, or a meaning pack), scope and updated time — with one difference from a file row: no Download. A dashboard is a live view, not a file, so the row offers only Open, which navigates straight to the dashboard itself at its canonical surface — a dashboard is never previewed or rendered inline in the library (ruling, cinatra#1883).

Example — a dashboard row in the library (Open only; no Download)
  • Pipeline health — Q3Dashboards

    Team: Growth · updated 20 minutes ago

    Open

The artifact detail page — a pointer, not a viewer. A dashboard-typed artifact's detail route /artifacts/[id] is a pointer surface: it never previews or renders the dashboard inline. It shows the artifact's metadata — the dashboard glyph, its name, the defining-extension label, the scope chips and the updated time — over a single primary Open dashboard affordance that navigates to the dashboard's canonical surface (cinatra#1883 D7). A dashboard is a live view with its own home, so it is always opened there; the library row and this detail page only point to it. There is no read-only grid, no embedded portlet render, and no in-page rendering here — the canonical surface owns both viewing and editing.

Example — /artifacts/[id], the dashboard pointer detail (metadata + Open dashboard + scope chips; no inline render)
Pipeline health — Q3 Dashboards
In scope

A dashboard is a live view — this page points to it, it does not render here.

@cinatra-ai/dashboard-artifact:dashboard · updated 20 minutes ago

Open dashboard

Scope chips & routing (D9). A dashboard has exactly one canonical home — its single entityRef, one canonical entity identity that never changes (cinatra#700; cinatra#1883 D9). Beyond that home, a dashboard can be listed in other scopes as secondary reference listings (a separate listing relation, not a second home; cinatra#1883 D9), and the pointer detail page renders one scope chip per listing. A chip opens the dashboard in that scope's context; the library row and the Open dashboard affordance both open the dashboard at its canonical surface; none of these re-anchor the dashboard — the singular entityRef is never moved.

Dashboards are always visible in their scope (ruling 5)

A dashboard is always visible to everyone in its scope — project, team or organization — with no admin-specific access anywhere. The scope chips name exactly the scopes in which every member can already see it; there is no admin-gated dashboard state and no admin-only access. This differs from the workspace-wide object console (§IV), which is record inspection, not a per-scope visibility grant.

IX. The Dashboards tab — every entity page's first tablist entry

Entity-page tablist (Dashboards · Settings) · secondary listings · add / remove · cinatra#1897 B4 · #1886 C2 · #1883 D9/D11 · #1904 · ruling 5

Every entity page opens on a Dashboards tab. A personal, project, team or organization page carries a canonical underline tablist (Application Design — Components · Tabs) whose first entry is Dashboards. On a project, team or organization the second entry is Settings — that entity's management pane, where rename, visibility and the members / access section live folded together. A personal scope has no Settings pane, so its tablist is Dashboards alone. Dashboards is a plain tablist entry like any other — no pill, no bespoke chrome — and the etched paired-line rule begins to the right of the last tab and runs to the page edge. This section fixes the Dashboards tab; the Settings pane is its own surface.

What the Dashboards tab lists. On a team, project or organization the tab lists the dashboards in that scope — a dashboard whose canonical home is this scope (the singular entity identity of §VIII), and any secondary reference listing added here (a separate listing relation, never a second home; §VIII). Both open the dashboard at its canonical surface, never rendered inline (§VIII), and a scope manager can add an existing dashboard as a listing (§IX.1). On a personal scope the tab shows the acting user's own dashboards; a personal user scope and the whole-workspace scope are not add-to-scope targets — they carry no Add, so add-to-scope is the three shared scopes only.

Row anatomy — no relation badge, no per-row type label. Each row carries a leading dashboard glyph, the dashboard name, the updated time, and an Open affordance. Every row on this tab is a dashboard, so no row repeats a Dashboards type label, and a row does not advertise where the dashboard lives — whether it is homed here or merely listed here is not surfaced. A dashboard's canonical home never moves: this tab neither re-homes nor deletes. The single place the home / listing difference shows is the Remove control, which appears only on a removable row — a secondary listing, which can be unlisted — and never on a dashboard homed in this scope. Removability is read from the presence of Remove alone; there is no Home or Listed badge. Open navigates to the dashboard's canonical surface exactly as in §VIII — the tab points, it never renders a dashboard inline.

Example — a team page, Dashboards tab selected (one homed row, no Remove; two listings, with Remove); Settings is the second tab

Growth

Team

The dashboards in Team: Growth.

  • Pipeline health — Q3

    updated 20 minutes ago

    Open
  • Revenue attribution

    updated 2 hours ago

    Open
  • Onboarding funnel

    updated yesterday

    Open
Example — a personal scope: the Dashboards tab is the only tab (no Settings pane), listing the user's own dashboards, and there is no add-to-scope

Personal

Your scope

The dashboards you own.

  • Weekly focus

    updated 3 hours ago

    Open
  • Reading queue

    updated yesterday

    Open

IX.1 · Add to scope — the picker over dashboards the scope may list

The picker's pool is the acting user's own dashboards; only scope-visible ones are directly addable. Add dashboard opens a picker over the dashboards the acting user can see, excluding any already homed or listed here. This is the cinatra#1886 collection-add contract made visible: a candidate is addable iff the actor may see it AND a generic member of the scope could already see it (the scope-vantage guard) — the two reads the contract composes with the scope-write gate (§IX.2). A candidate the actor can see but the scope cannot yet is not directly addable and is never widened silently: it still appears in the picker, but only with its recourse (below) — never an in-place add.

Recourse when the scope can't see it — the promotion request. When a candidate is invisible to the scope, its row names the visibility it must reach for the scope to see it and offers the promotion request in that vocabulary — team on a team tab, organization on an organization tab (cinatra#1437; cinatra#1886), and a team target is offered only to a member of that team (a non-member sees no offer). The request is the recourse, not an immediate add: the dashboard lists only once its visibility has actually widened and been granted. A project tab has no promotion recourse — a project is a resource refinement, not a visibility tier — so a candidate the project cannot see is simply not addable there, stated plainly with no offer (fail-closed, exactly as the contract yields a null recourse for that scope kind).

Example — the add-to-scope picker (addable rows + a scope-invisible candidate offering the promotion request)
Add a dashboard to Team: Growth

Lists an existing dashboard here as a reference — its canonical home does not move. Only dashboards this team can already see are listable.

  • Expansion pipelinethe team can already see this
  • Win/loss reviewthe team can already see this
  • Personal experimentsprivate — the team can't see it yet
The contract's structured denial, rendered

The picker never adds a dashboard the scope can't see, and never widens one silently. Such a candidate — actor-visible but scope-invisible — is surfaced only as a promotion request naming the exact target visibility (team / organization), and a team target only to a member of that team; on a project tab, where no promotion widen applies, it reads as not addable with no offer. This is the cinatra#1886 evaluation order made visible — actor-see, then scope-write (§IX.2), then scope-see — with the recourse offered only on the scope-see failure (gates 1–2 having passed), and only when the actor could actually submit it.

IX.2 · Who sees Add and Remove — the scope-write gate

The management controls are gated to the scope's managers. Add dashboard and every row's Remove appear only to a principal who may write (manage) this scope's Dashboards collection. Grounded in the shipped scope-authority vocabulary, that is a team admin on a team tab, an organization admin or owner on an organization tab, and a project admin or owner on a project tab. This is the design-side shape of the collection-add contract's injected actor-may-write-scope decision (cinatra#1886 C2, gate 2) — the gate cinatra#1897 (B4) owns: the precise predicate is #1897's to implement, and the population it names is pinned here.

Everyone else sees a read-only tab. A member without write authority still sees the Dashboards tab and every row — homed and listed alike — and opens any of them; they simply get no Add affordance and no Remove control. Suppression, not a disabled control: a management action the member cannot take is not rendered. The full add is authorized only when all three hold — the actor may see the dashboard, the actor may write the scope, and the whole scope may see the dashboard; when the first two hold but only the scope-see gate fails, the §IX.1 promotion recourse is offered (a failure of either earlier gate is a plain denial, no recourse).

Example — the same tab for a scope manager vs. a member without write access (the permission axis)
Scope manager — Add + Remove shown
Add dashboard
Revenue attribution Remove
Member — read-only, still sees every row
no Add
Revenue attribution Open
Read stays universal; only management is gated (ruling 5 holds)

Ruling 5 makes a dashboard always visible to everyone in its scope — this tab keeps that: every member sees every homed and listed dashboard and can open it, with no admin-gated visibility anywhere. The scope-write gate covers curation of the collection — adding and removing listings — a different axis from access to the dashboard. Gating who may add or remove a listing is not the admin-gated access ruling 5 forbids; the dashboard itself stays open to all in scope.

Example — the tab across its load states (permission-denied is the read-only member view, §IX.2)
empty

No dashboards in this scope yet

A manager can Add an existing dashboard, or create one that homes here.

loading
error

Couldn't load this scope's dashboards

X. Axes the spec carries — responsive · theme · state

Breakpoints · single cream theme · the data-state set

Every new surface honors the three axes the Application Design family carries. The affordances above (§VI–§IX) are specified once and hold across breakpoint, theme and state — a conformance run checks each axis, so the spec names them.

Responsive. The library sits at the wide data width (max-w-7xl). On a narrow viewport the toolbar wraps — the search field takes the full row, the Type / Scope / Upload controls wrap beneath it, and Upload stays reachable (never behind an overflow). The type picker and the inline marketplace tab are bounded panels that shrink to the viewport (max-width:100%); the marketplace pack list is single-column already. The dashboard pointer detail card wraps to the narrow width — its scope chips flow to a new line and the Open dashboard affordance drops beneath the metadata — and any wide table scrolls inside its own container rather than widening the page. The Dashboards tab (§IX) wraps too — the entity-page tablist (Dashboards, and Settings on the shared scopes) keeps its single underline row, its Add dashboard affordance drops beneath the tab strip, each row's controls flow to a new line rather than overflowing, and the add-to-scope picker is a bounded panel that shrinks to the viewport (max-width:100%).

Theme. There is one theme — the shared cream palette. Every new surface draws only from the shared tokens (the :root block); no affordance introduces a colour, and the base style block is identical to every sibling spec (Conventions · self-contained specs). Set on cream.

State. The machine-checkable state axis is the closed data-state set — loading, error, empty, plus kind:artifact — carried on the conformance surfaces above. The richer interaction states each affordance moves through are shown as example frames and bound to their outcomes via data-action. The table names them so a build covers each:

Example — interaction states per new affordance
Affordance Interaction states data-state
Upload (§VI)idle · drag-over · uploading · typed · refusedloading · error · kind:artifact
Type picker (§VI.1)choosing · suggestion · confirmed · auto-surfacedloading · error
Marketplace tab (§VII)browse · installing · request-sent · empty · errorempty · error · loading · kind:artifact
Dashboard pointer (§VIII)loading · ready · open-dashboard · errorloading · error
Dashboards tab (§IX)read-only · manager · empty · loading · error · open-dashboard · remove-listingempty · error · loading · kind:artifact
Add-to-scope picker (§IX.1)listable · adding · added · promotion-offer · not-addable · errorempty · error · loading