From b1ec1e19a71a6db14bf9820da7f73b5cbe9aea3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Fri, 7 Aug 2026 09:24:23 +0000 Subject: [PATCH] =?UTF-8?q?record=20the=20close=20of=20=C2=A74?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/workspace-panel-todo.md | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/docs/workspace-panel-todo.md b/docs/workspace-panel-todo.md index 3b16a2b4..71a6a040 100644 --- a/docs/workspace-panel-todo.md +++ b/docs/workspace-panel-todo.md @@ -193,9 +193,9 @@ malformed row turning that into a white screen loses the result at the one momen `config` added a new unvalidated field to an already-unvalidated tree. The work itself is safe in `chat_session_events`, which is why recovery must offer "restore default layout" rather than SQL._ -**The first three are resolved — `64961d4` (the boundary) and `ef036df` (the other two).** §4 is the last -Tier A item that was still whole; what is left of it is validate-on-write, below, which is the cheap half -of a rule now enforced on both sides of the wire but nowhere in the database. +**§4 is closed** — `64961d4` (the boundary), `ef036df` (validate-on-read and the `'[]'` default), +`92b8905` (validate-on-write). "A layout is an object" is now stated in four places: the column, the +route, the read, and the renderer's willingness to survive being handed something else anyway. - [x] **Add an error boundary.** `grep -rln "componentDidCatch\|getDerivedStateFromError\|ErrorBoundary\|errorElement" src` returned **nothing** across the whole repo. @@ -231,11 +231,24 @@ of a rule now enforced on both sides of the wire but nowhere in the database. omitted too. `bun db:push` planned exactly the four expected `ALTER`s plus the two known-harmless `pk_music_now_playing` lines. -- [ ] **Validate on write.** The whole path is `unknown`: `useDashboardState.ts:46` → +- [x] **Validate on write.** The whole path is `unknown`: `useDashboardState.ts:46` → `servers/api/dashboards/dashboards.ts:45,83` → `queries/dashboards.ts:51,97` → cast `as never` at - `:114` to satisfy drizzle → jsonb. No zod, no CHECK on any of the three layout columns. Given + `:114` to satisfy drizzle → jsonb. No zod, no CHECK on either layout column. Given `databases/CLAUDE.md`'s stance that the schema is the source of truth for _contents_, a CHECK on `jsonb_typeof(layout) = 'object'` is the cheap half. + **Resolved `92b8905`** — both halves, because they do different jobs. + `ck_dashboards_layout_object` / `ck_screens_layout_object` are the backstop: nothing reaches those + columns without passing them, including a psql session or a future writer nobody has written yet. + NULL stays legal — it means "none stored", which is the honest state of a dashboard created without + a layout. But a constraint violation surfaces as a 500 that tells the caller nothing, so the PATCH + dispatcher checks the same rule first and answers `400 "ws-layout-x" must be a layout object`. + Deliberately **not** a full `LayoutNode` schema: this is the wrong-container class of bug — the one + that crashes `normalizeLayout` — and a structural validator would need the client's `zoom`/`config` + vocabulary in the database layer to say anything more. + Verified live: `db:push` planned exactly the two `ADD CONSTRAINT`s plus the known + `pk_music_now_playing` churn and was stable on a second run; `update … set layout = '[]'` and + `= '3'` are both rejected by Postgres; array, number and string bodies to `ws-layout-{id}` and + `screens/{name}` all 400, and an object still round-trips. - [x] **A bare number is squatting in a framework namespace.** _(verified in the live DB)_ `apps/Soulseek/shared.ts:12` built `screens/soulseek-zoom/${panelId}` and passed it to @@ -579,8 +592,7 @@ _(move items here with the commit and a one-line resolution)_ layers, none of which was enforcing anything before: the columns stopped defaulting to a value that could not be a layout, the read stopped handing over one that is not, and the render stopped taking the whole app down when something else does. Per-item detail in §4. - What this does **not** do is validate on write — §4's last open item. The database will still accept - any jsonb at all; the two new guards mean it can no longer reach a renderer. + What this did **not** do is validate on write; that followed in `92b8905`, closing §4. - [x] **A layout write can no longer be quietly undone by an older one.** _(`b0a32ae` + `81ad3ef`, branch `agent-coordination-mvp`)_ — Tier A5 and A7, and with them the whole of §5.5, which empties Tier A. @@ -596,6 +608,11 @@ _(move items here with the commit and a one-line resolution)_ `config` — the panel that was just given an agent's name goes anonymous, and the agent stops being addressable through it. +- [x] **A layout column cannot hold a non-layout.** _(`92b8905`, branch `agent-coordination-mvp`)_ — + §4's last item, and the end of §4. A CHECK on each layout column (NULL or `jsonb_typeof = 'object'`) + plus the same rule in the PATCH dispatcher, so the caller gets a 400 naming the key rather than a + 500 naming nothing. Detail in §4. + --- ## 8. Dead code sweep