Rebuild Compliance Repos tab list
# C3 — Repos tab (matrix list, summary line, grouped states, first visit)
Part of epic "Simplify and improve Compliance UI clarity". Rebuilds the Repos tab of `/compliance`. Depends on C1 (`gap_count`).
## Scope (Architecture §3.2)
Files: `apps/web/src/pages/compliance/ReposTab.tsx` (default export, no props; reads `setup`/`matrix` through its own queries — create it if C2 has not; if C2 created it by moving today's matrix body verbatim, rebuild it here; file overlap only, rebase), `MatrixView.tsx`, `SetupPanel.tsx` copy, `CampaignDialog` reached from ⋯. Tests `MatrixView.test.tsx`, new `ReposTab.test.tsx`, e2e matrix/setup/campaign cases in `apps/web/e2e/compliance.spec.ts`.
- **Render path (merge-order safe):** if C2 has not merged when you land, replace `CompliancePage.tsx`'s matrix header and body (today ~lines 79–186, the 6-button header and the direct `MatrixView` render) with `<ReposTab />`, so your toolbar is what users see; C2 later wraps it in the Repos `TabPanel`. If C2 has merged, its Repos panel already renders `ReposTab`.
- **Observed (interim):** the Observed toggle's successor is C2's Rules tab `mode=observe` filter. If C2 has not merged when you land, keep *Show observed* as an item in the Repos ⋯ (R6: no capability may vanish between merges); drop it once the Rules tab exists.
- **Toolbar:** Order select (filter) on the left. ⋯ on the right with *Rescan all*, *Choose projects…* (today's "Change selection", `?setup=1`) and *Start campaign*, each shown only to an editor (`canStart`). **No standing button.** While a scan runs, a status line adds one transient control: `Scanning 3 of 8 · [Stop]` (Stop only when `scan.can_stop`).
- **Summary:** one line, not a five-tile card: `Average L2 · L0 1 · L1 3 · L2 4 · 2 waived · Scanned 3 d ago`. Zero parts omitted (no "0 waived"). The campaign appears only while a campaign exists. "Most common gap" is removed. The count line says `8 projects · 5 not scannable` so grouped rows are never silently lost.
- **Rows (Q3):** `ui/feed/FeedList` (not `DataTable`). One row per project: title = project name (`titleLines: 'all'`, wraps, never ellipsised) linking to the repo page; slots Level (`LevelBadge`, plus `⚑ n` with "n waived" on hover when n > 0) · Gaps (`gap_count`; show `—` when `row.level === null` **or** `gap_count` is undefined) · Next gap (the rule **name links to the rule page**; its why on hover); row ⋯ *Rescan* (when `can_scan`), *Auditor run* (when there is one). Dropped from each row: the stack line, "Scanned …" and the auditor link. The five pillar columns leave the list (the pillar × level grid lives only on the repo page).
- **Stale rows:** a row whose result predates the current scan carries a small "earlier result" marker, date on hover. The summary line says once `3 results from an earlier scan`. No per-row sentence.
- **Rows with no result are grouped, never repeated.** Below the list, `FeedSection` groups, each said once:
- **Not scannable (n):** grouped by `(targetCellState(row.target_state), row.target_error)` (`not_scannable_no_node`, `not_scannable_no_repo`; `target_error` hint from `scans.unscannable`). Sentence + fix link from today's `NotScannableHint` mapping (`parts.tsx:~119-135`): no node → "No node hosts these projects." + **Add to a node** (`/nodes`); a hint → the hint + **Open nodes**; no repo → "No repository connected." + a **Connect a repo** link per project (`/projects/:id`). Project names follow as links.
- **Scan failed (n):** grouped by `scanErrorPhrase(target_error)`; phrase said once per group, full error on hover per row, each row keeps its own Rescan (or "Ask an editor to rescan" once per group for a reader who cannot rescan).
- **Not scanned yet (n):** rows in `not_scanned` with no level; one sentence for the group.
- **Phone:** one card per project with name, level, gaps and next gap. No horizontal scroll.
- **First visit / no rows:** the tab body is `SetupPanel` (projects + pack, one primary **Start scan**). The tabs stay visible. Copy trimmed: heading `Scan your repos`; pack select shows `name · v1.2` with `Ready-made` as secondary text.
Dropped/moved (Architecture §6, this child's rows): Start campaign → Repos ⋯; Change selection → Repos ⋯ "Choose projects…"; Stop scan → transient beside "Scanning n of m"; matrix pillar columns + each cell's repeated next gap, "Most common gap" tile, five-tile summary card, per-row stack line, per-row "Scanned …"/`Result from` sentence/auditor link, per-row repeated error sentence and "Ask an editor to rescan", per-row observed list (Observed toggle moves to the Rules tab Mode filter, built by C2), repeated not-scannable sentence per row, pack name · version on the matrix subtitle.
## Original request (verbatim, epic Task 10032)
> Go over Compliance process with oam-demo project in prod as an example. The Compliance UI seems to be too busy, very confusing, not clear where user actually sets up rules, etc. Clean up and make more straight and intuitive. Remove repetitions and stating obvious facts
## Settled requirements (PRD, verbatim)
- **R2: Less on screen (A2).** Each page has at most **3 visible header actions** at ≥1280px. The rest go in one overflow menu or move to the tab that owns them (e.g. Settings).
- **R3: No repetition, no obvious facts (A6, A7).** Every item F2–F10 in §4 is resolved: the repeated text is removed or said once, and the obvious label is dropped. The spec names how each one is resolved. Specifically, no rendered Compliance surface:
- shows a status badge that only restates the section it sits in (e.g. "Fail" under "Gaps");
- shows a field whose value is the default ("every repo", zero counts, "None") unless it is editable there;
- repeats the same sentence on more than one row of a list (a shared state is said once for the group);
- shows the same fact twice in one header (badge + sentence, or pack + version in the header *and* the subtitle).
- **R4: One name, one destination (A3).** A rule's name opens the same kind of page wherever it appears: the rule page. Evidence for a repo × rule is a separate, explicitly labelled action or link.
- **R5: Consistent words (A3).** Mode values use their labels (Observe/Advise/Nudge/Gate/Lock) everywhere. Every label follows `docs/standards/ui-copy-standards.md`.
- **R6: Dropped features are explicit (D5).** The architecture spec lists every removed feature with a one-line reason. Anything not listed stays reachable. No feature disappears silently.
- **R7: Available on dev (charter).** The redesigned UI is the only Compliance UI: no flag or toggle gates it. It is live on the dev control plane (`dev.jaah.ai`) for an operator whose org has Portfolio Compliance on.
Operator decisions: D3 full redesign to the UI style guide; Architect Q1/Q2: **no capability is dropped** — rare actions move into a ⋯ menu or appear only in context; Q3 a Repos row = level + gap count + next gap. PRD S4: first-visit setup stays a short single step.
**Binding design principle:** a page shows at most one always-visible primary action; any other action goes into the page's ⋯ (`ui/OverflowMenu`), a row's ⋯, or appears only while its condition holds. Every rendered mode goes through `modeLabel`.
Full design docs: the epic's PRD and Architecture artifacts (Tasks 10032 / 10033); §-numbers refer to the Architecture document; its §6 "Dropped / moved" table is binding.
States covered: an org with **no scan yet**, **a scan running**, **results present**, **a project not scannable**, a **ready-made** active pack and an **org (forked)** pack. A viewer is either **compliance:view only** or **compliance:edit**, and `guard_enabled` is **on** or **off**. Unless a criterion names a state, it holds in all of them. Desktop = 1440×900; phone = 390×844 (`docs/standards/ui-style-guide.md` § Checking a page).
Credentials & reach (Architect §7, verbatim):
- AC1–AC17 (dev console UI), guard **on** states — `me/jaah/debug/main/operator-login` (owner/admin in several dev orgs). Environment: dev control plane via its public ALB **`https://app.dev.jaah.ai`** (`docs/architecture.md:185`). Target tenant: **Org B** — **verified**: `credential_get` succeeded. A sign-in to `app.dev.jaah.ai` returned 200. Switching to Org B returned 200, its session reads `compliance_enabled` and `guard_enabled` true, and `GET /api/compliance/setup` returned 200 (10 projects, 3 packs). The internal `dev.jaah.ai` ALB timed out from this node (tailnet-only), so oracles use `app.dev.jaah.ai`.
- AC1–AC17, guard **off** states — Same credential. Target tenant: **TimeSaver2** — **verified**: the switch returned 200, `compliance_enabled` true, `guard_enabled` false, and `GET /api/compliance/setup` returned 200 (2 projects, 2 packs)
- AC6 (compliance:view-only viewer) — Same credential: the login is `owner` in Org B (admin covers non-admin). The coder adds a view-only member through the console's Users/Groups UI at verify time and signs in as it — **verified** (owner identity probed above; creating the member is a console step, not a missing credential)
- AC8 / AC16 data (≥2 not-scannable projects, fresh results) — Same credential, Org B set up through the console (projects with no node, a rescan) — **verified** (Org B reachable with 10 projects; data is arranged at verify)
- AC18 (`./dc check quick`, unit + e2e suites) — Nothing beyond the worktree — **verified** (worktree only)
Out of scope:
- Do NOT touch `/compliance/verify` (`apps/web/src/pages/ComplianceVerifyPage.tsx`), the task manifest (`apps/web/src/components/tasks/ComplianceManifest.tsx`), org compliance signing (`apps/web/src/pages/settings/complianceSigning/`), `apps/web/src/pages/orgs/OrgSettingsTab.tsx`, `apps/web/src/pages/customers/ComplianceDeleteDialog.tsx` or the plan feature (`apps/web/src/pages/products/plan/PlanFields.tsx`).
- Do NOT change scan/score semantics: `apps/api/app/routes/compliance.py` matrix/repo/cell computation, levels, the auditor runner, the daily-scan cron behaviour, Guard enforcement in `apps/jaahd/guards/`.
- Do NOT change permissions: `compliance:view` / `compliance:edit` and the `RequireCompliance` / plan gating stay as they are.
- Do NOT add a second sidebar entry for Compliance, and do NOT add a feature flag for the new UI.
- Do NOT remove any capability silently; a removal is allowed only if listed in Architecture §6 (which drops none).
- No data migration: stored packs, rules, waivers, facts and scans are untouched.
- Do NOT remove the matrix `observed` API field (deferred follow-up).
- Do NOT build the tab shell, Rules/Guard/Settings tabs, repo page or rule page (C2, C4, C5).
Done means:
- AC7 (Repos): ≤3 visible header actions — At 1440×900, on the Repos tab, count the visible buttons/links in the toolbar (excluding the overflow ⋯ and the tabs). There are ≤3 (design target: none standing; Stop only while scanning).
- AC8: No duplicate sentence across rows — On Repos with ≥2 not-scannable projects (on dev, ensure ≥2 projects have no node), no sentence (e.g. "No node hosts this project.") appears on more than one row. Run `[...document.querySelectorAll('main *')].map(e=>e.childNodes.length===1&&e.textContent.trim()).filter(t=>t&&t.length>20)` in the console: no string longer than 20 chars that describes a state (not a rule or project name) appears twice.
- AC11 (next-gap link; replaces the PRD's "matrix cell's gap popover", since pillar cells leave the list per Architecture Q3): Click a row's next-gap rule name on Repos: it lands on that rule's page.
- AC12 (Repos): Mode labels — Search the Repos tab's rendered text: no lower-case raw mode token (`observe`, `advise`, `nudge`, `gate`, `lock`) stands as a value; the label is used.
- AC15: First visit is one step — On dev with an org that has no scan (or after clearing the selection), `/compliance` shows the project + pack choice and one primary **Start** action above the fold at 1440×900. One click starts a scan, and the Repos tab shows rows streaming.
- AC16 (Repos): Readable, not just smaller — At 1440×900 and 390×844 on Repos: no project or rule name truncated to an ellipsis (names wrap); no horizontal page scroll; columns don't wrap single words mid-word. **Tightened:** attach 1440 and 390 screenshots of Repos (results present, scan running, with not-scannable groups, first visit) to the report; the count line's total equals listed rows + grouped rows.
- No capability lost: Rescan all, Choose projects…, Start campaign, Stop, per-row Rescan, Auditor run and (until C2 lands) Show observed are each reachable for an editor (click each on dev); `gap_count` `—` rule unit-tested.
- AC18: `./dc check quick` passes; `MatrixView.test.tsx`, `ReposTab.test.tsx` and the matrix/setup/campaign cases of `apps/web/e2e/compliance.spec.ts` pass, updated to the new structure.
Outcome
Repos tab rebuilt (FeedList: level · gaps · next-gap rule link; grouped no-result states; one summary line; actions in ⋯; Stop beside "Scanning n of m"). PR #6931 merged 684fa284. Quick gate, 273 unit, 32 e2e and 30 API tests green; visual check at 1440 and 390 was clean. Deploy run was superseded by a newer push, so the dev oracles were not run. Memory: none applied.
Artifacts
Activity
- Draft
- Ready
- Runner started
- Runner ended (done)