# sweep-activity-posts — Activity and Posts, one hand

> **Sub-scope of the design sweep, opened 2026-08-01** by operator order:
> *"the activity and posts section — UI/UX and functionality testing and
> improvement."* Session C of three; the sibling session authors the seven
> other held mockups and does not touch these two screens.
>
> This file is the **direction agreed before the first mockup file** —
> the thing round 3 did not have, and the stated reason nine of its eleven
> sheets were rejected (`docs/DECISIONS.md` § Design & UI, amended 2026-07-30).
> Every line below is an operator ruling from the 2026-08-01 grill, ledgered in
> `docs/DECISIONS.md`. Nothing here is inferred.

## Objective

Activity and Posts, redesigned and rebuilt as one coherent pair: Activity as
two honestly-separate lenses on one dataset, Posts as a second place a decision
can actually be made. Mockup first, reacted, then built from the mockup's
layout outward.

## What is being authored

| # | Artifact | Note |
|---|---|---|
| 1 | `design/tiles/sweep-activity-posts/activity.html` | ONE sheet: filter control + list + inline detail panel as one composition |
| 2 | `design/tiles/sweep-activity-posts/posts.html` | First design round Posts has ever had |
| 3 | One BINDING/DEMO/forks sheet covering both | Same triple-bound format as round 3 |

Authored by ONE hand, screen by screen, against this direction. **Never fanned
out** — creative output, per the Spawning law in `KICKOFF-PROMPT.md`.

## The locked direction

### Activity

- **One screen, one sheet.** The list and the detail panel are one CSS grid
  already (`activity.css:63`, `.cm-activity__split`). Round 3 drew them as two
  files whose sheets had to negotiate the shared table; the seam is admitted in
  its own annotations (`sweep-phase1-round-3/ANNOTATIONS.md:151`).
- **Split honestly.** The queue and the log get designs fitted to their own
  jobs rather than one table compromised into serving both. Same dataset, two
  lenses. The lens stays a URL param (`?preset=needs`), because saved views are
  URL-only (DECISIONS 2026-07-24).
- **The queue keeps channel + outcome + post.** `c98c245` (agent-scope-15 A4)
  made the server narrow the needs set by the same composed fields the Feed
  filters on; that work stands. Dropped from the queue: the Source facet
  (a waiting review row carries no provenance — offering it could only empty
  the queue), faceted option counts, export.
- **Pagination stays on both lenses.** Operator, same session, overriding the
  draft that dropped it from the queue.
- **Filtering is ONE button that expands a menu** holding the options. Not
  inline facets, not a text query, not click-a-value-in-a-row. Round 3's strip
  held seven control types and three layers of indirection (a badged
  More-filters button, chips mirroring what was inside it, and a line
  apologising for a dropped filter) to fit six facets into one row.
- **Sorting is the column header.** Click to sort; no sort control elsewhere.
- **The detail panel adapts to the case.** The feed carries every activity the
  AI ran, not only comments and interpretations. Six kinds, each already
  carrying its own server-side detail caption (`agent-feed.service.ts:217-223`):

  | Kind | Detail caption | Action shape |
  |---|---|---|
  | Comment | comment | label |
  | DM | message | label · approve |
  | Interpretation | caption | label |
  | Moderation | comment | moderate (keep / delete) |
  | Flow run | step | label |
  | CRM | what happened | crm_action |

  Plus `dm_opener` across its three states. The panel is designed per case, and
  the left/right relationship (what the list row shows vs what the panel adds)
  is decided once, per kind, in the sheet.

### Posts

- **Actionable.** A draft is approvable and a hidden comment keep-or-deletable
  from the post itself. Today it is a read-only `DecisionInset`
  (`Posts.tsx:849-851`). One store, one decision, two contexts to reach it from.
  Whatever acts in Activity acts identically here, or the two disagree.
- **The grid ruling stands** (DECISIONS 2026-07-16): square tiles, centre hover
  overlay, thumbnails mandatory, ads a separate tab, IG-style comment rows.
  This round redesigns around it, not against it.

## Acceptance

**Mockups** — operator reaction on the sheet. That is the only oracle for a
design round; a green suite says nothing about how a screen looks.

**Build** — the mockup and the build side by side (DECISIONS 2026-07-30: an
approved mockup binds the UI, not only the behaviour), plus:

```
cd apps/web    && npx vitest run --pool=forks --poolOptions.forks.maxForks=2
cd apps/api    && npx vitest run --pool=forks --poolOptions.forks.maxForks=2
cd packages/shared && npx vitest run --pool=forks --poolOptions.forks.maxForks=2
pnpm --filter @channels/web typecheck && pnpm --filter @channels/web build
```

Backend changes get an independent read-only review before the gate (operator
rule 2026-07-22, DECISIONS 6b). A screenshot of both screens at 1440 and 375 —
neither has ever been looked at since its fixes; every test on them is jsdom.

## Out of scope

The seven other held mockups (sibling session). The flows canvas (sibling
session). Analytics. Any engine change. Overview, even though its dossier
shares a file with Posts.

## Where this got to

**Design is done through round 3 and reacted to. Nothing is built. Posts is not
started.** The live state, the backend work implied, and the landmines are in
**`plans/rethink/activity-posts-handoff.md`** — read that, not this section, for
current state. This file remains the direction the work was authored against.

Round 3 added, all operator-ruled 2026-08-01 and ledgered in `docs/DECISIONS.md`:
the logic became a **registry** with a completeness check that ships as a vitest
case; every decisive act is **overrulable** with three honest availabilities;
**the 2am rule** (a kind a contact waits inside may never be `held`, so DM and
flow end in `owed`); the outcome vocabulary he chose; corrections **collected,
never auto-injected** into the prompt; and a **Source** control that opens the
absolute source rather than our copy.

## Open — surfaced, not reconciled

- **⚠ Filter mechanism vs the round-3 fork ruling.** `docs/DECISIONS.md`
  (round-3 fork rulings) states *"a filter set inserts as a ROW inside the table
  container, never a fly-out"*. The operator has now specified *"a button that
  expands a menu"*, which is a fly-out. **Working assumption, stated rather than
  silently reconciled:** the menu is where filters are CHOSEN, and the active
  filters render as a row inside the table container — both rulings hold under
  that reading. If he meant the row goes away entirely, the round-3 line needs
  amending and he says so.
- `?` The visual reference for a decision console. ManyChat is the stated
  ergonomics reference for flows and has no equivalent surface; 21st.dev is
  reference-only. Unasked, unresolved — carried into the sheet as a fork.

## Known state, verified this session

- agent-scope-15 Phase A has landed A1–A4 (`agent-feed.service.ts:339-356`,
  commits `ff1bf12` / `80192d2` / `c98c245`). **A5 is moot** — `Triggers.tsx`
  was deleted with the trigger store (`d4de9f5`). **A6 is NOT done**: there is
  no `overflow-x` container on the six-column Activity table; `activity.css`
  carries only a text-ellipsis at :140.
- `Posts.tsx:880` renders the raw engine words `delete` / `escalate` as pill
  labels, where Activity maps them to keep / flag (`Activity.tsx:66`). A plain
  no-engineering-vocabulary violation, to fix in the build.
