From f06d35bff21268322306f13c8dfa330504289712 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 19 Aug 2026 13:55:58 +0000 Subject: [PATCH] =?UTF-8?q?docs(showcase):=20the=20record=20header=20speak?= =?UTF-8?q?s=20CEL=20=E2=80=94=20correct=20the=20stale=20dialect-split=20n?= =?UTF-8?q?ote=20(#9281)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Measured in a real browser against the pinned objectui (.objectui-sha 82a94170c4058d451ce3ac179d99296d90554479), signed in as the seeded admin, on both Field Zoo specimens. The authoring-rules bullet claimed the record header evaluates action predicates on objectui's legacy JS evaluator, so `.size()`, `.contains()`, `.matches()`, `in` and `today()` all throw there and fail-closed hide the action. That is stale (pre-objectui#3314 behaviour). Measured on Specimen — Full, the record header `⋯` menu offers all 51 header-located gates, including every CEL-only construct. On Specimen — Minimal the same menu offers 10 — the ones whose predicate is true on that record. The verdicts invert per record (`NOT — !boolean` appears only on Minimal; `tags — any` only on Full), so the header is evaluating, not passing everything through. No `page:header action "…" visible` fail-closed warning was emitted on either record. That outcome also clears the risk this note was carrying: every gate in this file opens with `has(...)` since #8990, and `has()` is not a function in the legacy evaluator — had the claim still been true, all 51 would have been silently absent from the header today. Prose only. No predicate, action, gate or pin is changed. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_019yDEhPBC3tcGkW9bkce1HM --- .../src/ui/actions/predicate-matrix.action.ts | 87 +++++++++++++------ 1 file changed, 62 insertions(+), 25 deletions(-) diff --git a/examples/app-showcase/src/ui/actions/predicate-matrix.action.ts b/examples/app-showcase/src/ui/actions/predicate-matrix.action.ts index 445bdaa61d..b8c889dd5e 100644 --- a/examples/app-showcase/src/ui/actions/predicate-matrix.action.ts +++ b/examples/app-showcase/src/ui/actions/predicate-matrix.action.ts @@ -89,15 +89,29 @@ * them at all, so a predicate using one silently stops being portable. * - **A relation field is its FOREIGN KEY**, on every surface and on the * server: `record.f_lookup == ""`, never `record.f_lookup.id`. - * - **The RECORD HEADER does not speak CEL** (objectui#3521). It evaluates - * header-action predicates on objectui's legacy JS evaluator, so `.size()`, - * `.contains()`, `.matches()`, the `in` operator and stdlib calls like - * `today()` all THROW there and fail-closed hide the action, while the same - * predicate is correct in the row kebab and the selection bar. Every gate - * below that uses one of those is therefore absent from the `⋯` menu today - * and present in the list — see `ZooDialectSplitAction` for the one-screen - * comparison. Predicates built only from `==` / `!=` / `<` / `&&` / `||` / - * `!` agree on both. + * - **The RECORD HEADER speaks CEL, like every other record surface.** + * Measured in a browser at the pinned objectui (`.objectui-sha` + * `82a94170c405`), on both specimens: `.contains()`, `.size()`, + * `.matches()`, the `in` operator, `has()` and stdlib calls like `today()` + * all evaluate on the record header and in its `⋯` overflow. Where the same + * action is also declared elsewhere it agrees: the row `⋮` menu matches the + * header on both specimens, and so does the selection bar for the six + * specimens the view names in `bulkActions`. `record_header` / `record_more` + * hand the predicate to `evalRowPredicate` WHOLE, so the + * `{ dialect: 'cel' }` envelope `defineAction` normalizes to is routed to + * the canonical engine rather than flattened onto a dialect guess. + * + * ⚠️ This bullet used to say the opposite — that the header ran objectui's + * legacy JS evaluator and fail-closed hid every CEL-only gate (objectui#3521, + * pre-#3314 behaviour). That was stale, and it is recorded rather than + * deleted because of what it would have cost: after #8990 every gate in this + * file opens with `has(...)`, which the legacy evaluator answers with + * `"has" is not a function`, so had the claim still been true, all 51 + * header-located gates would have been silently absent from the `⋯` menu. + * Measured instead: the menu offers them, and hides exactly the ones whose + * predicate is false on that record. ⛔ Do not restore the claim from a + * source read — it is falsifiable in a browser in one click, which is the + * only way it was ever settled (objectstack#9281). * * ## `visible` has three authoring forms, one meaning * @@ -227,26 +241,49 @@ export const ZooUserIdentityGateAction = defineAction({ }); /** - * **The dialect split.** One action, one predicate, two surfaces — and, as of - * this writing, two different answers. + * **The dialect PIN.** One action, one CEL-only predicate, declared on all + * three `RECORD_SURFACES` — and, measured, one answer on each of them. + * + * `.contains()` is ordinary CEL, and this action is where "the record header + * runs the same engine as the row kebab" is falsifiable in one click. Measured + * in a browser at pinned objectui `82a94170c405`, signed in as the seeded + * admin (objectstack#9281): + * + * | surface | Specimen — Full | Specimen — Minimal | + * |---|---|---| + * | record header `⋯` (`record_more`) | OFFERED | absent | + * | row `⋮` menu (`list_item`) | OFFERED | absent | + * + * ⚠️ The selection bar is deliberately NOT a row of that table: this action is + * not named in either view's `bulkActions` (`field-zoo.view.ts`), so it never + * reaches that surface at all — an absence of DECLARATION, which would read as + * an absence of dialect support if tabulated beside the other two. The + * three-surface agreement is pinned instead by the neighbours that ARE declared + * on all three: `ZooVisibleStringAction` and its two twins, and + * `ZooRelationGateAction` — whose `.size()` call is CEL-only and which likewise + * appears on all three for Full and on none for Minimal. * - * `.contains()` is ordinary CEL and evaluates correctly wherever the canonical - * engine runs: the row `⋮` menu, the selection bar, conditional formatting. The - * RECORD HEADER does not run that engine — it evaluates header-action - * predicates on objectui's legacy JS evaluator — so the same predicate throws - * there (`.contains` is not a JS string method) and the action is fail-closed - * hidden. Measured, not assumed: the browser console carries - * `[page:header] action "…" hidden: its predicate threw`. + * Both columns are load-bearing. `f_textarea` is `'Line one\nLine two'` on Full + * and `null` on Minimal, so the RIGHT column is what proves the header is + * evaluating rather than offering everything: the same action on the same + * surface flips with the record. The browser console carries no + * `page:header action "…" visible` fail-closed warning on either record — the + * predicate is answering, not faulting. * - * The same split hides every CEL-only construct on that one surface — - * `.size()`, `.matches()`, the `in` operator, and stdlib calls like `today()` - * (`"today" is not a function`). Filed as objectui#3521; kept here as a LIVE - * fixture rather than papered over, because "works in the list, silently gone - * on the detail page" is invisible to any test that exercises one surface. + * ⚠️ This action used to be framed as **the dialect SPLIT** — the live + * demonstration that the header ran objectui's legacy JS evaluator and threw on + * `.contains()` (objectui#3521), with "when #3521 lands, this action should + * appear in the header too — that is the test." That test has since passed: + * objectui#3314 stopped the header hand-unwrapping the envelope, and + * `evalRowPredicate` routes `{ dialect: 'cel' }` to `@objectstack/formula`. The + * fixture keeps its name and its value — inverted. It no longer demonstrates a + * split; it is the sentinel that FAILS if a surface ever demotes to the legacy + * dialect again, which is why it keeps all three `locations` rather than being + * deleted along with the claim. * * Compare it against `ZooVisibleStringAction` above, whose predicate uses only - * operators BOTH dialects share and therefore agrees on every surface. When - * #3521 lands, this action should appear in the header too — that is the test. + * operators both dialects share: that one agreed across surfaces even while the + * split was real, so it cannot detect a demotion. This one can. */ export const ZooDialectSplitAction = defineAction({ name: 'showcase_zoo_dialect_split',