# Talent Desk — Marque

A creator talent agency's desk, built as a template on the Reckon design system.
Fifteen screens, one fixture set, one route table. The user is a talent manager
with a roster of creators; the job is matching them to brand campaigns, keeping
deals moving, and making sure everybody gets paid.

Open `TalentDesk.dc.html`.

## Files

| File | Holds |
|---|---|
| `TalentDesk.dc.html` | The template entry. Loads `ds-base.js`, `kit.css`, `boot.js`. |
| `ds-base.js` | Loads this design system. One line to repoint in a consuming project. |
| `boot.js` | Loads the product's files in order and publishes `window.TalentDeskApp`. |
| `enums.js` | Six status fields, the classification lists, the agency policy constants, the derived flags, the transitions. |
| `nav.js` | The route table and the projections the shell renders. |
| `fixtures.js` | 9 creators, 13 brands, 3 managers, 10 briefs, 5 deals with 17 deliverables, 6 notifications. The only data in the product. |
| `metrics.js` | Every figure any screen renders. Nothing here holds data. |
| `kit.css` | Layout only. Every colour, radius, shadow and step is a token. |
| `lib/` | `contracts.js`, `ui-runtime.js`, `command-palette.js` — copied in, domain-free, byte-identical to every other product's copies. |
| `MarqueParts.jsx` | The product's local components (below). |
| `MarqueShell.jsx` | The top-nav shell. |
| `*Screen.jsx` | The seven screens. |
| `App.jsx` | Routing plus all of the commit machinery. |

## The screens

| Route | Screen | Primary display | Commit model |
|---|---|---|---|
| `/` | Roster — "the book" | Full-width creator bands, hairline-divided | Instant |
| `/talent/new` | New creator | A form: who, audience, rate card | **Staged** |
| `/talent/:id` | Creator | A document: audience, availability, rate card, campaigns | Instant |
| `/talent/:id/statement` | Statement | One table — campaign by campaign, fee to net | Instant |
| `/briefs` | Briefs | The triage deck over the pipeline ledger | Instant |
| `/briefs/new` | New brief | A form: who sent it, the ask, the lines, the dates | **Staged** |
| `/briefs/:id` | Brief and match | The fit ledger, three axes per creator | **Staged** |
| `/brands/:id` | Brand | A document: briefs, campaigns, money, what we owe them | Instant |
| `/deals/:id` | Deal | The deal sheet: contract rail, timeline, fee ledger | Instant |
| `/deliverables` | In flight | The three-week ladder over grouped work rows | Instant |
| `/deliverables/:id` | Deliverable | The ask, the history, what it did | Instant |
| `/performance` | Against the promise | The promise wall | Instant |
| `/performance/:id` | Wrap report | One campaign's wall, its posts, the renewal line | Instant |
| `/money` | Money | Two facing ledgers with the commission as the spine | Instant |
| `/inbox` | Inbox | Notification lines grouped by day | Instant |
| `/search` | Search results | Grouped hits, each naming what it matched | Instant |
| `/account/team` | Team and ownership | A card per manager over their book | Instant |

Plus the two staged account forms (`/account/profile`, `/account/agency`) and
the eight auth screens below.

There is no brands list and no deals list. Both are reached through the work that
names them — a brief, a deal, a deliverable, an invoice — and both sit under
`/briefs` in the route table, so the breadcrumb resolves and Briefs stays the
active destination. Brands deliberately did not take a sixth slot in the bar:
nobody opens a brand cold, and six items would have spent the last slot the
shell has.

## Where records come from

Every entity the product lists can be made in it, per AGENTS §5:

- **New creator** — the roster header, a staged form at `/talent/new`. Reach,
  engagement, the rate range and the open briefs they would fit are computed from
  the rows as you type, by the same helpers every other screen calls.
- **New brief** — the briefs header, a staged form at `/briefs/new`. A brand not
  on file is created with it. As the deliverable lines change, the screen states
  the cheapest complete quote on the book against the budget being typed — the
  same `rosterFloor()` the match screen and the pass dialog run.
- **Book the deal** — a staged dialog on a pitched brief, four fields, because a
  deal is made out of a brief rather than from nothing. It creates the deal with
  its contract `drafted`, one deliverable per line the brand asked for due evenly
  across their window, a held draft invoice and a payout awaiting funds, and it
  moves the brief to won. The engagement half of the promise is the creator's own
  computed rate; the reach half is what the manager commits to.

Invoices and payouts are not created by hand: each is made with its deal,
because an invoice with no deal behind it is money owed for nothing.

## Decisions taken once, for the whole product

- **Shell**: top-nav, **inferred** rather than requested. Five top-level
  destinations is under the six the sidebar shell wants, and the record work
  happens inside a destination rather than across many of them, so the
  destination count decided it.
- **Density**: editorial at the top of a page (48px display type, 32px gutters,
  air above the fold) against dense ledgers where the work happens (32px rows,
  14px type, hairlines). The contrast is the point.
- **Separation**: hairlines and full-bleed `--sidebar` washes on the
  `--background` page plane. A card is used only where a thing is genuinely
  liftable off the page: the deal sheet, an inbound brief, a rail panel.
- **Active state**: foreground token plus a filled `--accent` pill.
- **Type signature**: an uppercase, wide-tracked mono eyebrow above every figure
  and every section; names set large in the heading face. Every eyebrow, every
  identifier and every technical string is Paper Mono; everything a person reads
  as a sentence is Cal Sans.
- **Portraits are squared, never circles.** A roster is a book, not a contacts
  list, and one geometry is used wherever a creator appears — 104px on the
  roster band, 56px on a fit row, 36–40px in a work row.
- **Recession**: an *open* record the manager cannot act on is recessed — a
  deliverable sitting with a brand, a payout held behind an unpaid invoice.
  Recessed rows keep their link, their badge and their clock, lose the primary
  control, and name who is being waited on. Closed and paid records are plain,
  not recessed: finished is not blocked.
- **Signature devices**: the booking bar (90 days from today, one geometry
  everywhere a creator's availability appears), the fit ledger (three axes, one
  row), the week ladder, the promise wall, and the facing ledgers.
- **One action per row.** The record's name is the link; the row carries the one
  transition its status allows, and anything else lives in the row menu.

## The two chains

Neither is written twice in the fixture.

```
rate card × quantity  →  organic fee  →  × (1 + usage uplift)  =  quote
negotiated fee (stored once on the deal)
                      →  × commission rate  =  agency commission
                      →  − commission       =  the creator's net
```

```
deal.feeCents  →  the invoice's amount  (what the brand owes us)
               →  the payout's amount   (what we owe the creator, net)
```

An invoice's due date is derived too — net-30 from issue — so changing the terms
moves every date and every overdue count with it. A creator's reach and
engagement are computed from their platform rows, not stored, so a follower
count changed in the fixture moves the roster, the fit axes and the performance
figures together.

**Suppression.** A derived figure is stated as a word, not a zero, when the
record makes it meaningless: `deliveryIndex` returns null for a campaign that is
not finished, and the screens print "In flight" or "Not yet measurable". Half a
campaign at 35% of promise is not underdelivery, it is Tuesday.

## Act-ability

Three predicates, one per destination that carries a count.

- `BRIEF_FLAGS.needsTriage` — the nav badge, the "On your desk" heading and the
  triage deck all call it.
- `DELIVERABLE_FLAGS.needsAttention` — revisions requested, overdue, or due
  inside the week. The nav badge, the "Needs you" section and its count are one
  function.
- `INVOICE_FLAGS.isOverdue` — the Money badge counts one kind of record (invoices
  past due), and the screen's lede names the payouts that are blocked behind it
  rather than folding two kinds into one number.

## Verbs, named once

Match creators · Pass on brief · Reopen brief · Chase the brand · Send pitch ·
Send for signature · Chase signature · Mark signed · Void the deal ·
Submit to brand · Chase the creator · Mark approved · Record revisions ·
Mark published · Issue invoice · Chase payment · Record payment ·
Release payout.

Irreversible (confirm dialog): **Release payout**, **Void the deal**.
Consequential and reversible (undo toast): everything else. Actions needing two
or more fields — Pass on brief, Record revisions, Mark published — open a staged
dialog from an instant screen.

Two commits move more than one record on purpose:

- **Record payment** marks the invoice paid *and* moves the creator's payout from
  `awaiting_funds` to `due`, because that is what actually happens.
- **Mark published** writes the post's reach, engagements and saves into the
  fixture, so the promise wall moves the moment you close the dialog.

## The awkward states, and where to find them

| State | Where |
|---|---|
| Exclusivity conflict | `/briefs/BR-26-038` — Marisol Duarte fits every axis for Sable & Salt and is exclusive to Lumen Beauty in beauty until 31 Jan 2027. Stated three times: a full-width alert, a marked row in "Cannot take it", and the reason on the disabled control. |
| A brief nobody fits | `/briefs/BR-26-047` — Cindr, $9,000, dating. Nobody on the book works in the category and the cheapest complete quote is $26,000, so the primary weight moves from "Match creators" to "Pass on brief". |
| Revisions requested, and late | `/deliverables` — Ferro's "One-pan Sunday" came back on 18 August with a note from the brand; the revised cut was due yesterday. |
| An overdue payment | `/money` — Tessellate Home is 22 days past due on $31,000, which is why Juno Park's $24,800 cannot move. The two sit opposite each other on the facing ledgers. |
| A campaign that underdelivered | `/performance` — Tessellate came in at 75.8% of the reach we promised. |
| A contract nobody has signed | `/deals/DEAL-26-021` — Nook Living, eight days out, with the invoice held behind it. |

## Local components — reported, per §9

Built here because the inventory has no equivalent; all of them are layout or
composition over existing primitives, and none invents a colour or a type size.

- **`Portrait` / `Who`** — creator identity, squared, at one geometry.
- **`BookingBar` / `Availability`** — the 90-day availability track plus the one
  sentence that explains it. Used on the roster and in the coming-free list.
- **`PlatformLadder`** — followers and engagement per platform, plus the total,
  all computed.
- **`DueClock`** — the forward-looking date cell (see friction note below).
- **`Figure`**, **`Eyebrow`**, **`LedgerRow`**, **`KindList`**, **`RecordHead`**,
  **`Crumbs`**, **`PageHead`**, **`ExportMenu`**, **`RouteNotBuilt`** — small,
  repeated, unremarkable.
- **`FormDialog`** — a staged dialog with two to four fields, so an instant
  screen can collect a small form without becoming staged itself.
- **`Floating` / `FloatingTip`** (in `lib/ui-runtime.js`) — portalled menu,
  popover and tooltip. The design system's own overlays now portal too; these stay
  for their different API (one element as the trigger, a render callback that
  receives `close`, no compound Trigger/Popup pair).
- The triage deck, the fit ledger, the contract rail, the week ladder, the
  promise wall and the facing ledgers are CSS in `kit.css`, not components: each
  is a one-per-screen composition.

## Coverage

Every value of every status field has a fixture except three, all reachable only
by taking the action that leads there: `contract.status: drafted` and
`contract.status: void` (void an unsigned deal) and `deliverable.status:
approved` (approve something the brand has back).

## Known gaps

- Responsive behaviour is declared once in `kit.css` (1280 / 1120 / 1000 / 900 /
  767) and the shell's own collapse once in `MarqueShell.jsx`. Checked at desktop
  and at the stacking breakpoints in the preview; the narrow end wants a real
  device.
- Auth (§7) is covered for the password strategy: sign-in, sign-out, the four
  reset screens and lockout. MFA, OTP, SSO and provider sign-in are out of
  scope, and nothing links to them.
- Pagination: 9 creators, 10 briefs and 17 deliverables do not need it, so the
  list screens end in a stated count rather than a `Pagination`. A real agency
  book would need it on the roster first.
- Creator portraits come from the design system's avatar assets, which are 160px
  square. The roster band is designed around a 104px portrait for that reason; a
  real deployment would want proper photography and the band would carry it.

## Account and auth

The account menu now has screens behind it rather than a "registered, not built"
notice — AGENTS §7.2: every entry point rendered has a screen.

- **Manager profile** and **Agency and commission** are staged forms. The
  commission rate is deliberately read-only: every deal stores the rate it was
  signed at and the ledgers read that, so the screen shows how far the book has
  drifted from the standard instead of offering a control that would lie.
- **Sign out** and **Sign in** render on the auth shell: its own shell, no
  navigation owner, one layout variant (centred card), one credential strategy
  (password). No providers, because this project ships no `assets/providers/`
  marks and §7.4 forbids drawing them. Designed states: idle, field-invalid,
  submitting, credentials-rejected with the attempts left, locked, signed-out —
  plus the four password-reset screens and `/account-locked`. MFA and SSO remain
  out of scope, so nothing offers them.

## Added in this pass

- **`/brands/:id` — the client record.** Brand names were string literals
  repeated across briefs, deals and invoices; a brand is now a fixture entity
  with an id, and `record.brand` is derived from `BRANDS` at the bottom of
  `fixtures.js`. Nothing about how a brand behaves is stored: when we first
  heard from them, what they have spent, how many days they take to pay and what
  we are exclusive to them for are read off the records that name them.
- **`/deliverables/:id` — the post as a record.** The ask it came from, the
  history read off the timestamps it already carries (Ferro's revision note now
  has somewhere to live), the numbers it did and its share of the campaign.
- **`/talent/:id/statement` — the creator's earnings statement.** One table,
  fee to commission to net, with the effective commission rate across their book
  rather than today's standard, and a section naming what is holding a payout.
- **`/performance/:id` — the wrap report.** One campaign against its promise,
  its posts ranked by reach, the usage window, and the sentence to open the
  renewal with. Suppression holds: an in-flight campaign prints a word.
- **`/inbox`** — the notifications panel as a screen, read state per line rather
  than one flag, unread and everything as two tabs over one list.
- **`/search`** — the palette's results screen, reached from a palette footer
  row. The palette and the screen run one function (`searchDesk`).
- **`/account/team`** — the three people on the desk, each with the book they
  own. Ownership is a `managerId` on the creator; the screen reads it and
  deliberately offers no control, and the coverage ledger states the unassigned
  count even when it is nought.
- **Password recovery and lockout** (`MarqueResetScreens.jsx`): the four reset
  screens §7.5 requires plus `/account-locked`, all on the same centred-card auth
  shell. Sign-in now carries "Forgot password?" at the password field's trailing
  label edge and counts rejections — the third lands on `/account-locked` with a
  real countdown and a recovery path. `PASSWORD_RULES` in `enums.js` is both the
  live checklist and the validation.
- **Usage-rights expiry** (`RIGHTS_FLAGS` in `enums.js`): the usage window was
  priced but never tracked. `usageEndsAt` in metrics now delegates here, and
  Corriente's paid-social window ending 2 September surfaces on the brand record
  and the wrap report.

## Added in an earlier pass

- **`/talent/:id` — the creator record.** The roster's rows led nowhere; a creator is now a document you read top to bottom (audience, availability and exclusivity, rate card, campaigns against promise, what we owe them). Local addition: `.mq-meter`, the delivered-against-promised bar, drawn only where `deliveryIndex` is not suppressed.
- **Preview page menu** (`lib/template-menu.js`, byte-identical across the kit): the floating button bottom-right lists every registered route, grouped, and carries the theme control for the auth screens that render no shell.
