diff --git a/docs/web-ui.md b/docs/web-ui.md
index 034370de..f64f5c69 100644
--- a/docs/web-ui.md
+++ b/docs/web-ui.md
@@ -241,33 +241,52 @@ reminders surface at the bottom as a sibling list (#460 —
they share enough conceptual ground to live together).
**N3W SCH3DUL3 / QU3U3D SCH3DUL3S** — operator-managed
-scheduled prompts (#444 / #459). Lists every schedule with
-its description, targets, body, recurrence interval, next-fire
-time, and per-target last-result. Per-row controls: a
-`↯ fire now` button sends an out-of-band manual pulse to
-every active target (#467 — recurring schedules keep their
-cadence; one-shots are consumed after the manual fire), an
-`✎ edit` button opens an inline edit form (#474 — body /
-description / interval / next-fire / targets all editable;
-targets are a multi-select diff'd against the original active
-set so unchecked-was-active = `targets_remove`, checked-not-
-originally-active = `targets_add`; submit PATCHes
-`/api/schedules/{id}`), and a
-`CANC3L` button cancels the whole schedule
-(`POST /api/schedules/{id}/cancel`). Individual target chips
-have their own cancel links. An inline creation form lets
-the operator queue a new schedule directly:
-targets (multi-select checkboxes drawn from live container
-names + `operator` + `manager`), prompt body (textarea),
-first-fire datetime-local (pre-filled to 5 minutes from now),
-an interval composer (#466 — preset chips for common
-durations + separate d/h/m/s number fields with a live
-"↻ every …" preview; all-zero = one-shot),
-and an optional human-readable description. On submit the
-form POSTs to `/api/schedules` as JSON; the tab pill shows
-the count of active schedules (at least one live target not
-yet cancelled). Refreshed on tab activation and after each
-submit/cancel. Backed by `GET /api/schedules`.
+scheduled prompts (#444 / #459). **Single-table layout**
+(#535): each schedule is one `
`; columns are
+`# | src | next | every | owner | body | …agents… | actions`.
+Agent columns are dynamic — `operator` + `manager` + every
+live container + any extra name that appears as a target on
+some schedule but isn't a current container (same
+`buildTargetChips` membership rule the new/edit forms use,
+so table and forms agree on what's addressable). Column
+headers tilt -45° via CSS so each column reads as a narrow
+~28px strip; per-agent cells render as:
+
+- **active target** → `` that cancels just
+ that one target on click (replaces the per-row ✕ from the
+ pre-#535 targets sub-table)
+- **cancelled target** → muted `✕` glyph (no button —
+ re-adding goes through the edit form's targets multi-select)
+- **not a target** → empty cell
+
+Per-schedule action column: a `↯ fire now` button sends an
+out-of-band manual pulse to every active target (#467 —
+recurring schedules keep their cadence; one-shots are
+consumed after the manual fire), an `✎ edit` button expands
+an inline edit form as a colspan'd row directly under the
+schedule's row (#474 — body / description / interval /
+next-fire / targets all editable; targets are a multi-select
+diff'd against the original active set so unchecked-was-
+active = `targets_remove`, checked-not-originally-active =
+`targets_add`; submit PATCHes `/api/schedules/{id}`), and a
+`✕` button cancels the whole schedule
+(`POST /api/schedules/{id}/cancel`).
+
+An inline creation form above the table lets the operator
+queue a new schedule directly: targets (multi-select
+checkboxes drawn from live container names + `operator` +
+`manager`), prompt body (textarea), first-fire datetime-local
+(pre-filled to 5 minutes from now), an interval composer
+(#466 — preset chips for common durations + separate
+d/h/m/s number fields with a live "↻ every …" preview;
+all-zero = one-shot), and an optional human-readable
+description. On submit the form POSTs to `/api/schedules` as
+JSON; the tab pill shows the count of active schedules (at
+least one live target not yet cancelled). Refreshed on tab
+activation and after each submit/cancel. Backed by
+`GET /api/schedules`. No backend changes for the table layout
+— it renders entirely from existing `schedulesState` +
+`containersState`.
**QU3U3D R3M1ND3RS** — reminders agents have scheduled for
themselves (via the `remind` tool) but not yet delivered.