# Situations rebuild — mockup annotations (BINDING vs DEMONSTRATIVE)

> Required by `docs/DECISIONS.md` (2026-07-22): *"Every mockup ships with a BINDING
> vs DEMONSTRATIVE annotation sheet — BINDING = build exactly this, DEMO =
> illustrative only. Executor gates check against the BINDING list."*
>
> **Round 2**, 2026-07-25. Mockup source: `plans/rethink/situations-rebuild-mockup-round-2.html`
> (committed so round 3 can build on it; round 1's source was not kept and that cost
> a rebuild). Published artifact: the same URL round 1 used, updated in place.
> Orders: `plans/rethink/situations-rebuild-intake-2026-07-25.md`, including the
> **"Mockup round 1 — operator reaction"** section. Token law: `design/tokens.md` /
> `apps/web/src/styles/tokens.css`.
>
> This sheet is the same content as the artifact's final sections, kept in the repo
> so the executor gate has it without opening a URL.

## How the mockup was authored

`Situations.tsx`, `situations.css`, `content-editors.tsx`, `TriggerModuleEditor.tsx`
and `KnowledgeModuleEditor.tsx` were **not opened**, on purpose: the operator's
verdict on every prior round was *"every mockup of situations didn't incorporate
and improve its design but inherit it."*

**One exception, taken deliberately in round 2**: the operator ruled that the
Global tab must keep the distinctive styling it has today rather than render plain.
So `situations.css:53-66` (`.cm-sit__tab--global`) and `:120-135`
(`.cm-sit__globalsec` / `__globalrow` / `__globallabel`) were read, and nothing
else. Those values are reproduced unchanged. The situation strip's own chrome was
**not** read — its presence and contents are binding, its exact look is "whatever
it is today" (see DEMO).

Otherwise the screen is designed from the orders plus five data sources only:
`instruction-headings.ts` (the five headings + plain-words labels),
`prompt-seed.ts` (real prose), `schema.prisma` (`KnowledgeDocument`),
`components/ui/` + `ui.css` + `tokens.css` (the primitives it must be built with),
and the **live database** (the nine real knowledge documents, read 2026-07-25).

## Round 1 verdicts, and where each landed

All sixteen are in the artifact's ledger. The seven that changed BINDING text:

| His correction | What changed in this sheet |
|---|---|
| Global keeps its distinct design | New BINDING rows "Global keeps its treatment" + "Global content shape" |
| The blue strip stays | Round 1's absence "the blue situation strip" is **deleted**; replaced by BINDING "The situation strip stays" |
| See the exact prompt muted, Edit dominant | New BINDING row "Recessive vs dominant" |
| Menu reads View / Edit / Delete | BINDING "The row menu" replaces round 1's "Read it / Change it / Delete it" |
| KB view is a side panel | BINDING "Knowledge opens a side panel" replaces round 1's "renders in place inside the Knowledge card" |
| Guardrails is ordinary | Round 1's "Safety limits as a locked block" is **deleted**; replaced by "Guardrails is an ordinary field" + "Nothing is fixed" |
| Per-channel additions seamless | BINDING "Per-channel additions are one box" replaces round 1's channel-row-with-header wording |

Also deleted from round 1 as rejected: the one-line note beside each heading (was
DEMO), "Read all of it" (was DEMO), and the save-bar caption. The save bar itself
moved from DEMO to **BINDING**, because he approved it.

## BINDING — build exactly this

| Element | What exactly |
|---|---|
| Tab bar | Four tabs in this order: Global · Conversations · Comment replies · Moderation. The three situation tabs are underline tabs: active is `accent-ink` text at 600 plus a 2px `accent` underline. No divider label, no changed dot, no arrows, no stepper. The strip scrolls sideways inside itself; the page never scrolls sideways. |
| Global keeps its treatment | The Global tab renders **filled**, not as a fourth underline tab: `accent-soft` background with `accent-ink` text, top corners `radius-sm`, and `accent` fill with `accent-contrast` text when active. Keep `.cm-sit__tab--global` exactly as it is. Removing the `SITUATIONS` label must not change it. |
| Global content shape | Global's five headings render as plain hairline-separated headed rows inside **one** card (today's `.cm-sit__globalsec` / `.cm-sit__globalrow` / `.cm-sit__globallabel`), never as five module cards. This is what makes Global visibly not a situation. |
| The situation strip stays | Keep the strip on every tab, and keep today's `.cm-sit__strip` chrome. It carries the situation name, its channel chips, and its one-line description. Only the tab-bar `SITUATIONS` label and its separator are removed. **No other bar furniture is stripped.** |
| Page action row | Exactly two actions in read mode, in this order and this weight: **See the exact prompt** recessive, **Edit** dominant. No third action. No per-heading edit button anywhere on the page. |
| Recessive vs dominant | **See the exact prompt**: transparent background, `muted` text, `fw-label` (500), no border, `hover` background on hover. **Edit**: `cm-btn--primary`. The two must not read as siblings of equal weight. |
| One Edit, one Save, per tab | Edit puts every prose field on the active tab into edit mode at once. One Save commits all of them, Cancel discards all of them. Knowledge rows are outside this mode and save on their own. |
| Tabs lock during edit | While editing, the other three tabs are non-interactive. A one-line message appears **only when a locked tab is clicked**, never as standing chrome. This settles gap 9 and is the dirty guard from gap 5: no page of edits can be lost to a tab switch. |
| Five heading blocks | Role · Goal · Tone · Knowledge · Guardrails, in that order, read from `instruction-headings.ts`. One card each on the situation tabs. One field each, except Knowledge which has no prose field at all. **No commentary under a heading. No note beside a heading.** |
| Guardrails is an ordinary field | One field under Guardrails, in the same card shape as Role, Goal and Tone. No red overlay, no `bad` tint, no lock icon, no "always on" label, no separate Edit button, and **no nested block inside it**. The rail prose is the editable text of that field and it saves with the page's single Save. |
| Nothing is fixed | No field, block or row on this screen is read-only, non-removable or edit-gated. `NON_REMOVABLE_SECTION_IDS` (`prompt-seed.ts:285`) goes, and no typed-confirm dialog guards any field. |
| Read mode renders the markdown | `##` lines render as a sub-heading at `fs-md`/600 `ink`. Body prose `fs-md`/400 `ink-2` wrapped near 68 characters. Bullet lists and inline code render. No raw markdown characters left visible. |
| Edit mode is the raw markdown | One plain `textarea.cm-input` per heading holding the whole field. Never a rich editor, never one box per sub-heading. One hint under it. |
| Every box is the same border | Every card, field box, knowledge box and channel segment: `surface` background, **1px `line`** (`#E4E4E7`), radius 6. Live text controls use the sheet's input spec (1px `line-strong` `#D4D4D8`, radius 4). **No box on this screen may be darker than `line-strong`, and no box may fall back to a browser default border.** |
| Per-channel additions are one box | A channel addition is a segment **inside the field's own box**, separated from the base only by a 1px `line` hairline, marked only by the channel chip on its left. No header, no label, no "Added on one channel only", no title of any kind. When a channel has nothing added, **nothing renders**: no divider, no empty slot, no placeholder row. |
| Per-channel selector | No pills, no checkbox row, no multi-select. Adding one is a plain menu, edit mode only, listing only the channels not used yet. Each existing segment carries its own Remove. |
| Append-only, and never suggest otherwise | A channel segment always sits **below** the base text inside the box and reads in that order, which is what carries the append semantics. The edit placeholder says "added after the text above". No control anywhere may offer replacing, overriding, or switching off the base text. Replacement is a dead end and must not exist in the UI or the model. |
| Four channels | WhatsApp · Instagram · Facebook · Website, using the four approved chip tokens unchanged. Instagram keeps the one named gradient utility. Website uses `ch-web` with `ink` text. |
| Knowledge is rows only | No prose field under Knowledge on any tab, Global included. Rows come from `KnowledgeDocument`. Each row: title `fs-sm`/600, "Updated …" under it, channel chips when the doc is channel-scoped, and a `⋯` at the end. Clicking the row opens the view. One **Add Knowledge** button under the list. |
| The row menu | Exactly three items, in this order, each with an icon: **View** (eye), **Edit** (pencil), **Delete** (trash). Delete is separated by a hairline and set in `bad`. No other item, no other wording. |
| Knowledge opens a side panel | Viewing or editing a document opens a panel from the **right**, over a scrim, `width: min(440px, 100%)`, full height. The page title, action row, tab bar and strip **stay on screen** beside it: the panel must never replace the page headers. Below about 440px it fills the window width. Escape, the scrim, and an explicit close all dismiss it. |
| Knowledge view panel | Title, "Updated …", channel scope when set, the content in the panel's own scrolling body, and a footer with Delete on the left and Edit on the right. |
| Knowledge edit panel | Field labelled "What's this about?" for the title, a content textarea, a "Where it is used" channel picker, and a footer: Delete · Cancel · Save. Independent of the page Edit mode. |
| Text knowledge only | No "add a link" versus "add text" choice anywhere. One kind of row. `kind` and `data` are **unused in production** (verified 2026-07-25, all nine rows `kind='article'`, `data IS NULL`) so the UI treats every document as plain text. |
| Save bar | A sticky bar at the foot of the page while editing, holding Cancel and Save, with **no caption**. One Save must be reachable without scrolling to the top. |
| Empty heading state | An empty heading still shows its card and its name: one muted line in read mode, a real empty textarea with a placeholder in edit mode. Never hidden, never collapsed, never an em dash. Settles gap 6. |
| Primitives | Buttons are `cm-btn` variants, inputs and textareas `cm-input`, labelled rows `cm-field`, chips `cm-chip`. No bespoke control, no unstyled native field, no hand-duplicated CSS. The four field kinds that render with no primitive today all get one. |
| Every colour, size, radius, space | Every value is a `--cm-*` token from `tokens.css`. Nothing invented. Any value the build needs that the sheet lacks is an operator question, not a local choice. |
| Responsive | Clean at 320 / 375 / 768 and desktop. Single column, cards full width. Nothing scrolls horizontally except the tab strip and the panel's own body. Container queries, not viewport breakpoints. |
| Focus | Every interactive element shows a visible 2px `accent-ink` outline on keyboard focus, including the row, the `⋯`, the menu items and the panel controls. The KB row is a `div role="button" tabindex="0"` so the `⋯` can be a real nested button. |
| Light theme only | The approved sheet has no dark tokens ("Dark mode is a later toggle"), so the product surface commits to light on purpose. Inventing a dark palette would break the token law. |

### Absences — also BINDING

Each of these was on the old screen and is deliberately nowhere on the mockup.

- **Parked for Flows** — no box. The text moves to a markdown file in the repo and the UI never mentions it.
- **"Add an instruction under …"** — no form. Something extra is a paragraph typed into that heading's field.
- **The changed-mark family** — no `EDITED`, no `SEE CHANGE`, no `Default | Now` diff, no green dot on a tab.
- **Automatic values** — Who they are / Returning contact / After hours / Lead progress are not on this screen. The screen also does **not** say they were removed: they relocate to the Automation section on the agent node (scope-5).
- **Learned examples** — not under Knowledge, not anywhere, on any tab. They stop feeding the prompt entirely and stay as graded labels in Activity.
- **Read-only anything** — nothing marked `FIXED`, nothing locked, nothing behind a typed confirm.
- **Tab-bar furniture** — no `SITUATIONS` divider label, no separator, no stepper, no arrows. Everything else on the bar stays.
- **Three ways to stop something** — no Removed sentinel, no Restore, no Move back, no crossed-out gutter, no "Replaced here", no "Contributes nothing".
- **Per-channel replacement** — no override, no "use instead of", no switch that turns the base text off for a channel.
- **"Read all of it"** — no expander, no truncation, nowhere.
- **Commentary under a heading** — no "Who the bot is", no "What it is trying to do", no "How it sounds".
- **A note beside a heading** — none, of any wording.

## DEMONSTRATIVE — illustrative only

| Element | Note |
|---|---|
| All field prose | Real seed text from `prompt-seed.ts`, trimmed for length. The build reads the seed; it never copies from the mockup. Which sub-headings a field carries is data. Sub-heading names are the plain-words labels already in `instruction-headings.ts`. |
| Global's prose | Invented, and flagged: `prompt-seed.ts` has **no Global entries at all**. Which text seeds Global is an open data question (OPEN 2). |
| Which headings come out empty | Comment replies and Moderation show Goal and Tone empty to demonstrate the empty state. The real mapping comes from `headingFor()`. |
| The strip's exact chrome | The **presence** and contents of the strip are BINDING; its exact look is whatever it is today. The mockup reconstructs it as `info-soft` with an `info` title because `situations.css`'s strip rules were deliberately not read. Keep the real one. |
| Which channels each situation runs on | Data, not a design call. Conversations shows four chips, the comment tabs show two. |
| Strip copy | The one-line description per situation is illustrative and belongs in the same data file as the headings, never hardcoded in the screen. |
| Empty-state wording | "Nothing here yet. Press Edit and type what you want." is a suggestion. |
| The edit hint | "Start a line with ## to name a part." is a suggestion. A hint being present is BINDING. |
| "Put back the original wording" | A quiet ghost at the foot of a field in edit mode, rendered only where a seeded default exists and the text has actually changed. This is the surviving undo from order 11. Wording illustrative; no badge, no marker, no diff panel. |
| Prompt panel contents | The assembled text and its layout are the executor's. The real drawer already exists. Reusing the side panel for it is a suggestion, not a requirement. |
| Menu and panel chrome | The popover's and panel's border, radius and shadow. `design/tokens.md` has no popover or panel spec (see Tokens). |
| Knowledge row meta | "Updated 9 Jun 2026" is real on all nine rows, which is the point: every date is identical (see OPEN 3). Whether to show a date at all is worth deciding. |
| The channel picker on the KB form | "Where it is used" as chip toggles, with "nothing picked means every channel". Shape illustrative; per-channel Knowledge itself is approved and needs one additive `channels String[]` column. |
| Type family | The artifact falls back to the system face because the host blocks font files. The build keeps Inter. |
| The window frame and width buttons | Artifact scaffolding so 320 / 375 / 768 can be checked. Not part of the screen. |

## The nine live knowledge documents (investigated 2026-07-25)

The operator suspected the seeded KB titles/dates/counts were "probably only
reformatted text". Read out of the live DB (`knowledge_documents`, read-only
SELECT):

**The content is real. The metadata is not.**

- All nine are coherent short reference articles (502 to 711 characters) about
  Silver Oak and Dubai property: company blurb, services list, buying process,
  costs and fees, Golden Visa, mortgages, ROI, communities, FAQ. Not reformatted
  prompt text, not filler. Safe to present as the knowledge library.
- All nine were written by **one seed script in a single second**:
  `created_at = updated_at = 2026-06-09 14:59:38`. **None has ever been edited.**
  So "last updated" is identical on every row and carries no information, and no
  document is operator-authored or operator-reviewed.
- `kind = 'article'` and `data IS NULL` on **all nine**. The `area` / `process` /
  `fees` / `faq` structured kinds from the `20260612000000_knowledge_structured`
  migration have **never been used in production**. Round 1's warning ("some carry
  structured kinds, inspect before shipping") is answered: none do. The rebuild can
  treat every row as plain text, which is exactly the text-only model ordered.
- `enabled = true` on all nine. Chunking: eight documents are one chunk each,
  "Buying property in Dubai" is two, so ten `knowledge_chunks` back the library.
- Categories in use: `company`, `service`, `process`, `fees`, `visa`, `mortgage`,
  `roi`, `area`, `faq`. **`roi` is not in the schema comment's enumerated list**
  (`schema.prisma:1214`), so `category` is already free text in practice.
- One title carries an em dash ("Buying property in Dubai — the process"), which
  the token sheet's copy rules forbid in prose. It is data, not copy, so the
  mockup shows it verbatim, but it is worth a rename.

The mockup uses these nine real titles and their real content, so the operator is
looking at his own data.

## OPEN — needs the operator before it can be built

1. **The prompt text changes.** Collapsing the old per-heading pieces into one
   field per heading moves the assembled prompt, and he has separately ordered
   learned examples out of it. The byte-identical golden pin every previous
   Situations package gated on **cannot hold**. Explicit sign-off needed
   (intake gap 3).
2. **Global versus Conversations.** Still unanswered from round 1. Global now looks
   clearly different, so the *visual* problem is solved, but the *data* problem is
   not: `prompt-seed.ts` has no Global entries at all, so nothing seeds this tab
   today. What text belongs on Global?
3. **Every knowledge date is identical.** All nine seeded in the same second, none
   edited. "Updated 9 Jun 2026" on all nine rows is true but useless. Keep the
   date, or show nothing until a document is actually edited?
4. **The replay gate.** Nothing on the mockup carries it. If the manual "test this
   against my labelled examples" button replaces the automatic gate, it belongs
   beside Save and must show the cost before spending (intake gap 4).
5. **Textarea type size.** Long prose reads at `fs-md` (15px) but the field is
   edited in a `cm-input` textarea, which the sheet specs at `fs-sm` (13px). The
   mockup edits in mono at `fs-xs` so the `##` markers are obvious. Pick one.
6. **The quiet-ghost button needs a name.** See Tokens below.

`agent-scope-10-kickoff.md:92-94` and `scope-board.md:131` still say seven
headings. He granted planning-file authority, so those two lines should simply be
corrected to five rather than surfaced again.

## Tokens the design needed

- **Nothing invented.** Every colour, size, radius and space on the product surface
  is a `--cm-*` value copied from `tokens.css`. Verified in-browser: the only
  border on the whole product surface darker than `line-strong` is the accent
  border of the primary button, which is correct.
- **One new button variant, no new token.** The recessive *See the exact prompt*
  needs a quiet ghost, because `cm-btn--ghost` is `accent-ink` and competes with
  Edit. Built from sheet values only: transparent, `muted`, `fw-label`, `hover` on
  hover. It needs a name in `ui.css` (`cm-btn--quiet` in the mockup). **Operator's
  call on the name; no new token required.**
- **`bad` is now used in one place only:** delete actions. The Guardrails block
  that used `bad` / `bad-soft` in round 1 is gone.
- **The popover and the side panel have no chrome spec.** `design/tokens.md` specs
  Card, Pill, Input, Segmented control, Table, Nav item, Stat tile and Focus. It
  does **not** spec a popover menu or a side panel. Both are built from existing
  primitives (`surface`, 1px `line-strong`, radius 6, `hover` rows) plus a shadow.
- **The shadow is the only un-tokened construct**, twice: the popover's and the
  panel's. If zero un-tokened values are wanted, both fall back to a 1px
  `line-strong` border alone and the panel loses its lift. **This is the one thing
  the sheet genuinely lacks.**
- **The scrim is also un-tokened.** A side panel over content needs a dim layer and
  there is no scrim/overlay token. The mockup uses `ink` at 28%.

## Conflicts between the operator's own orders

Surfaced, not reconciled:

1. **"Make everything match the good Knowledge box" vs the input spec.** He wants
   every box to match the Knowledge boxes (1px `line`). The token sheet's
   Input/field spec mandates 1px `line-strong` for text controls. The mockup uses
   `line` for every read-mode box and `line-strong` only for live inputs, per the
   sheet. Neither is black, so the complaint is answered either way, but the two
   rules do not produce one value. **Ruling wanted if strict parity is required.**
2. **"Website is a channel, four channels everywhere" vs what the channels do.**
   There are no WhatsApp or Website comments, so Comment replies and Moderation
   show two chips, not four. Per-channel *additions* and per-channel *Knowledge*
   offer all four. Read as "four channels exist", not "every situation runs on
   four".
