From 74f7dd2eccb4f01716d2586e47238876c1405a77 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 8 Aug 2026 19:10:51 +0000 Subject: [PATCH 1/2] =?UTF-8?q?fix(app-shell,components):=20=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E7=AB=AF=E5=A3=B0=E6=98=8E=E5=8A=A8=E4=BD=9C=E7=9A=84?= =?UTF-8?q?=20visible=20=E9=97=A8=E6=8C=89=E3=80=8C=E5=B7=B2=E5=A3=B0?= =?UTF-8?q?=E6=98=8E=E3=80=8D=E5=88=A4=E5=AE=9A,visible:=20false=20?= =?UTF-8?q?=E4=B8=8D=E5=86=8D=E6=B8=B2=E6=9F=93=20(#3835)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DeclaredActionsBar 的门沿用真值判读 `if ((action as any).visible && !isVisible) return null`,于是 `visible: false` 落进「没声明门」分支,verdict 从未被查询,动作照样渲染。 这是 #3492 同族第五处,也是两条族群缓解在此都不成立的一处: - 动作 def 是服务端声明的(objectDef.actions[]、sys_approval_request), 不是手写视图 JSON。「ActionSchema.visible 是 ExpressionInputSchema、无 boolean 成员,objectstack build 产不出布尔形状」这条缓解在这条路径上不 成立 —— def 来自服务端 metadata 与进程内构造,布尔是自然写法。 - 该 bar 由宿主以普通 JSX 直挂(apps/console 的 ApprovalsInboxPage), packages/react 的 SchemaRenderer 不在这条路径上,#3812 判定组件级门休眠 的依据(宿主先求值 visible 再隐藏)不适用。这个门是该路径上唯一的门。 宿主是审批收件箱的记录区动作条:一个本该被 visible: false 关掉的审批动作 渲染成可点按钮,而本组件自己的 click handler 就是 POST 审批决定的那一步。 门统一到同族那一处命名定义 hasDeclaredVisibilityGate(`!= null && !== ''`), 从 @object-ui/components 的 barrel 新增一行 re-export 引入,不抄第五份 (#3142 的 locations 漂移形)。verdict 仍交给求值入口:toPredicateInput 原样透传布尔、useCondition 在 evaluateCondition 短路,故声明的 false 即 false,表达式取值的 visible verdict 一律不变。 先收紧常真桩,再落钉子:原套件把整个求值入口打桩成 `useCondition: () => true`,注释还写着测试动作不带 visible「所以用不到」—— 这让唯一挂载本组件的 套件里这道门从未被行使(#4984 族:fixture 让坏规则保持绿色)。现在改为只 替换动作派发,useCondition / toPredicateInput 用真实实现,四形状由出货语义 判定。 钉子:false 隐藏 / true 渲染 / 未声明渲染 / `''` 不算门 + 表达式两向 verdict + 隐藏动作在工具栏里不留任何可点面(按钮集断言,而非派发计数 —— 未被点击的 多余按钮在两侧都不派发,计数版修前也是绿的)。每例都带一个无门同伴动作, 使「未渲染」不会被误读成「整条 bar 返回 null」。 行为变化面(窄):只有 visible 为字面布尔 false(或其他非空 falsy)的声明 动作从渲染变隐藏;visible: true、`''`、未声明一律不变。 Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt Co-authored-by: Claude --- ...ts-export-declared-visibility-gate-3835.md | 21 ++ .../declared-actions-bar-visible-gate-3835.md | 58 ++++++ .../src/views/DeclaredActionsBar.tsx | 22 ++- .../__tests__/DeclaredActionsBar.test.tsx | 187 ++++++++++++++++-- packages/components/src/index.ts | 15 ++ 5 files changed, 286 insertions(+), 17 deletions(-) create mode 100644 .changeset/components-export-declared-visibility-gate-3835.md create mode 100644 .changeset/declared-actions-bar-visible-gate-3835.md diff --git a/.changeset/components-export-declared-visibility-gate-3835.md b/.changeset/components-export-declared-visibility-gate-3835.md new file mode 100644 index 0000000000..99678ebc49 --- /dev/null +++ b/.changeset/components-export-declared-visibility-gate-3835.md @@ -0,0 +1,21 @@ +--- +"@object-ui/components": patch +--- + +Export `hasDeclaredVisibilityGate` from the package barrel (objectui#3835) + +`hasDeclaredVisibilityGate(visible)` — "did this action DECLARE a visibility gate +at all?", i.e. `!= null && !== ''`, with the verdict left to the evaluation entry +— is the single definition objectui#3492 established and PR #3816 / #3825 / #3836 +applied to every member-action gate in this package and in `@object-ui/plugin-grid`. +It lived module-private in `src/renderers/action/visibility-gate.ts`. + +The family turned out to have a member outside these packages: +`@object-ui/app-shell`'s `DeclaredActionsBar` gates server-declared actions with +the same question and had the same truthiness bug (objectui#3835). Exporting the +one definition is what keeps that fix from becoming a fifth hand-spelled copy of +it — the drift shape objectui#3142 already had to unpick for `locations` in these +same files. + +Additive only: one `export` line, no behaviour change in this package. The +function is pure and dependency-free. diff --git a/.changeset/declared-actions-bar-visible-gate-3835.md b/.changeset/declared-actions-bar-visible-gate-3835.md new file mode 100644 index 0000000000..64587116ec --- /dev/null +++ b/.changeset/declared-actions-bar-visible-gate-3835.md @@ -0,0 +1,58 @@ +--- +"@object-ui/app-shell": patch +--- + +Server-declared actions declaring `visible: false` are now hidden instead of rendered as live buttons (objectui#3835) + +`DeclaredActionsBar` — the bar that renders an object's SERVER-declared actions +for one record at a `location`, with no per-action host code — asked truthiness +on the gate: `if (action.visible && !isVisible) return null`. `false && …` is +falsy, so `visible: false`, the most explicit way an author can say "never show +this", fell into the "no gate declared" branch, the verdict was never consulted, +and the action rendered for everyone. + +What that means on the page: the bar's host is the approvals inbox's +record-section toolbar (`apps/console/src/pages/system/ApprovalsInboxPage.tsx`), +so an approval action the metadata had switched off with `visible: false` +rendered as a live Approve / Reject / Reassign button — and this component's own +click handler is what POSTs the decision. One click was a real approve/reject +call on a request the declaration said not to offer a decision on. + +This is the fifth and last member of the objectui#3492 family (after +objectui#3758 / PR #3816 for the row-action surfaces and objectui#3812 / #3823 +for the action face), and the one whose two family-wide mitigations both fail: + +- The action defs are **server-declared** (`objectDef.actions[]`, + `sys_approval_request`), not hand-written view JSON. "`ActionSchema.visible` is + `ExpressionInputSchema` with no boolean member, so `objectstack build` cannot + emit this shape" does not apply on this path — the def arrives from server + metadata and in-process construction, where a boolean is the natural spelling. +- The bar is mounted as **plain JSX** by its hosts, so `packages/react`'s + `SchemaRenderer` — which evaluates a node's `visible` and hides it before the + component mounts, and which is why objectui#3812 judged the component-level + gates a dormant defensive layer — is not on this path at all. This gate was the + only one there. + +The gate now reads the family's one named definition, +`hasDeclaredVisibilityGate` (`!= null && !== ''`), imported from +`@object-ui/components` rather than re-spelled: five gates in three packages +asking one question must not drift into five answers. The evaluation entry is +untouched — `toPredicateInput` passes a boolean through and `useCondition` +short-circuits it instead of calling the expression engine — so a declared +`false` resolves to `false`, and every expression-valued `visible` keeps exactly +the verdict it had. + +Behaviour change surface, deliberately narrow: only a declared action whose +`visible` is the literal boolean `false` (or another falsy non-empty value) +changes, from rendered to hidden, which is what the declaration asked for. +`visible: true` still renders, `''` and an absent `visible` are still no gate at +all, and the bar still renders no chrome when its located set is empty. + +The suite that covered this component could not have caught it: it stubbed the +whole predicate entry constant-true (`useCondition: () => true`), with a comment +saying the test actions omit `visible` "so this is unused" — which made the gate +unreachable from the only tests that mount this component (the objectui#4984 +family, where a fixture keeps a broken rule green). That stub is gone; the suite +now runs the real `useCondition` / `toPredicateInput` and doubles only the action +dispatch, so all four shapes (`false` hides / `true` renders / undeclared renders +/ `''` is not a gate) are judged by the shipped evaluation semantics. diff --git a/packages/app-shell/src/views/DeclaredActionsBar.tsx b/packages/app-shell/src/views/DeclaredActionsBar.tsx index 14beb77326..98e73cee8e 100644 --- a/packages/app-shell/src/views/DeclaredActionsBar.tsx +++ b/packages/app-shell/src/views/DeclaredActionsBar.tsx @@ -31,7 +31,7 @@ */ import React, { useCallback, useMemo, useState } from 'react'; -import { Button, Separator, cn } from '@object-ui/components'; +import { Button, Separator, cn, hasDeclaredVisibilityGate } from '@object-ui/components'; import { ActionProvider, useAction, @@ -187,7 +187,25 @@ const DeclaredActionButton: React.FC<{ } }, [action, execute, loading, objectName, record, actionLabel, actionConfirm, actionSuccess, t]); - if ((action as any).visible && !isVisible) return null; + // Does the action DECLARE a `visible` gate? `hasDeclaredVisibilityGate` + // (`!= null && !== ''`) is the one definition on the question, imported rather + // than re-spelled. This gate used to ask truthiness, which classified + // `visible: false` — the most explicit "never show this" an author can write — + // as "no gate declared", skipped the verdict, and rendered the action for + // everyone (objectui#3835, the fifth member of the objectui#3492 family). + // + // The stakes here are the highest of the family: the actions are + // SERVER-declared (`objectDef.actions[]`), so "the spec's `visible` has no + // boolean member, `objectstack build` cannot emit one" does not apply, and this + // bar is mounted as plain JSX by its hosts — `packages/react`'s + // `SchemaRenderer`, which hides a `visible`-carrying node before its component + // mounts, is not on this path. This is the only gate on it, in front of the + // approvals inbox's Approve / Reject buttons. + // + // The verdict stays with the evaluation entry above: `toPredicateInput` passes + // a boolean through untouched and `useCondition` short-circuits it instead of + // calling the expression engine, so a declared `false` is `false`. + if (hasDeclaredVisibilityGate((action as any).visible) && !isVisible) return null; const iconName = typeof (action as any).icon === 'string' ? (action as any).icon as string : undefined; // Map the spec's action `variant` enum (primary|secondary|danger|ghost|link) diff --git a/packages/app-shell/src/views/__tests__/DeclaredActionsBar.test.tsx b/packages/app-shell/src/views/__tests__/DeclaredActionsBar.test.tsx index 18f008749c..6a21aaa3b7 100644 --- a/packages/app-shell/src/views/__tests__/DeclaredActionsBar.test.tsx +++ b/packages/app-shell/src/views/__tests__/DeclaredActionsBar.test.tsx @@ -18,14 +18,28 @@ import React from 'react'; // Capture the execute dispatch from the shared runner. const executeSpy = vi.fn().mockResolvedValue({ success: true }); -vi.mock('@object-ui/react', () => ({ - ActionProvider: ({ children }: { children: React.ReactNode }) => <>{children}, - useAction: () => ({ execute: executeSpy }), - // `visible` predicate: our test actions omit `visible`, so this is unused, - // but keep it truthy so a `visible`-carrying action would still render. - useCondition: () => true, - toPredicateInput: (v: unknown) => v, -})); +// Only the action DISPATCH is doubled here. The predicate entry +// (`useCondition` / `toPredicateInput`) is the REAL one (objectui#3835). +// +// It used to be stubbed constant-true, with a comment saying our test actions +// omit `visible` "so this is unused" — which made the component's `visible` +// gate unreachable from this suite for as long as it existed, so the truthiness +// bug objectui#3835 reports lived here untouched (the objectui#4984 family: a +// fixture keeping a broken rule green). A re-spelled stub would not fix that: +// `visible: false` only reaches "hidden" if `toPredicateInput` passes the +// boolean through and `evaluateCondition` short-circuits it, so a stub is a +// second copy of exactly the semantics under test. `@object-ui/react`'s barrel +// is cheap for the light `dom` project (`packages/components`' own gate suite +// imports it unmocked), so the gate below is judged by the shipped evaluation +// entry instead. +vi.mock('@object-ui/react', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + ActionProvider: ({ children }: { children: React.ReactNode }) => <>{children}, + useAction: () => ({ execute: executeSpy }), + }; +}); // The runtime is exercised in its own suite; here it's an inert shell so the // bar mounts without the full auth/i18n/router provider stack. @@ -54,13 +68,27 @@ vi.mock('@object-ui/i18n', () => ({ useObjectTranslation: () => ({ t: (key: string) => `t:${key}` }), })); -vi.mock('@object-ui/components', () => ({ - Button: ({ children, onClick, ...props }: any) => ( - - ), - Separator: () =>
, - cn: (...args: any[]) => args.filter(Boolean).join(' '), -})); +// The components barrel stays doubled (its full graph is what the light `dom` +// project deliberately does not load), but `hasDeclaredVisibilityGate` is pulled +// from its real source module — the ONE definition objectui#3492 established and +// PR #3816 / #3825 / #3836 spread across the other four member-action gates. A +// re-spelled `v != null && v !== ''` here would be a fifth copy of it living in +// a test double, and would keep this suite green no matter what the shipped +// predicate does (objectui#3142 is what copies of one answer cost). The module +// is a dependency-free pure function, so importing it directly costs nothing. +vi.mock('@object-ui/components', async () => { + const { hasDeclaredVisibilityGate } = await import( + '../../../../components/src/renderers/action/visibility-gate' + ); + return { + Button: ({ children, onClick, ...props }: any) => ( + + ), + Separator: () =>
, + cn: (...args: any[]) => args.filter(Boolean).join(' '), + hasDeclaredVisibilityGate, + }; +}); import { DeclaredActionsBar } from '../DeclaredActionsBar'; @@ -372,3 +400,132 @@ describe('DeclaredActionsBar chrome localization (objectui#2762)', () => { expect(byName['outputs.notes'].helpText).toBe('t:actions.decisionOutput.helpMultiValue'); }); }); + +/** + * objectui#3835 — the declared-action `visible` gate. Fifth and hottest member + * of the objectui#3492 family: `if (action.visible && !isVisible) return null` + * asked TRUTHINESS, so `visible: false` — the most explicit "never show this" an + * author can write — was classified as "no gate declared", the verdict was never + * consulted, and the action rendered for everyone. + * + * Why this surface is the hot one, and why the family's mitigation does not + * cover it: + * + * • The actions are SERVER-DECLARED (`objectDef.actions[]` / + * `sys_approval_request`), not hand-written view JSON, so "spec's + * `ExpressionInputSchema` has no boolean member, `objectstack build` cannot + * emit one" does not apply — the def arrives from server metadata and + * in-process construction, where a boolean is the natural spelling. + * • `DeclaredActionsBar` is mounted as plain JSX by its hosts + * (`apps/console/src/pages/system/ApprovalsInboxPage.tsx:2014` and `:2055`), + * so `packages/react`'s `SchemaRenderer` — which hides a node whose + * `visible !== undefined` evaluates false, and which made the component-level + * gates of objectui#3812 dormant — is not on this path at all. This gate is + * the only one there. + * • What renders is the approvals inbox's record-section bar: Approve / Reject + * / Reassign. A `visible: false` approval action rendered as a live button is + * one click away from a real approve/reject call. + * + * The gate now asks `hasDeclaredVisibilityGate` (`!= null && !== ''`), imported + * from `@object-ui/components` rather than re-spelled — the verdict stays with + * the evaluation entry, which short-circuits a boolean instead of calling the + * expression engine. + * + * All FOUR shapes are asserted, and each one is load-bearing in a different + * direction: + * • `false` hides — the defect itself (red before the fix); + * • `true` renders and undeclared renders — the anti-mutation guards: "hide + * the action unconditionally" satisfies every `visible: false` assertion on + * its own and would otherwise leave the suite green; + * • `''` renders — green BEFORE and AFTER the fix, and (measured, not assumed) + * green even under a gate mutated to `visible !== undefined`. On THIS + * surface `''` is covered twice: over-tightening the gate hands `''` to the + * evaluation entry, and `toPredicateInput('')` is `undefined`, which + * `evaluateCondition` reads as "no condition → visible". So this case + * documents the intended semantics; it is not a mutation detector here, and + * a reader should not mistake its passing for proof that the gate's `!== ''` + * limb is exercised (the limb itself is pinned in `packages/components`, + * next to the definition). + * Every case carries the ungated `COMPANION`, so a passing "not rendered" can + * never mean "the whole bar returned null" (the bar renders nothing at all when + * its located set is empty — a distinct code path, two lines away). + */ +const COMPANION = { + name: 'approval_reassign', + type: 'api', + label: 'Reassign', + target: '/api/v1/approvals/requests/{id}/reassign', + locations: ['record_section'], +}; + +function renderWithGate(action: Record, record: Record = REQUEST) { + return render( + , + ); +} + +describe('DeclaredActionsBar — declared `visible` on a server-declared action (objectui#3835)', () => { + const APPROVE = { + name: 'approval_approve', + type: 'api', + label: 'Approve', + target: '/api/v1/approvals/requests/{id}/approve', + locations: ['record_section'], + }; + + it('visible:false → the declared action does not render', () => { + renderWithGate({ ...APPROVE, visible: false }); + expect(screen.queryByTestId('declared-action-approval_approve')).toBeNull(); + // The bar itself rendered — the assertion above is about the gate, not about + // an empty located set. + expect(screen.getByTestId('declared-action-approval_reassign')).toBeInTheDocument(); + }); + + it('visible:true → the declared action renders', () => { + renderWithGate({ ...APPROVE, visible: true }); + expect(screen.getByTestId('declared-action-approval_approve')).toBeInTheDocument(); + expect(screen.getByTestId('declared-action-approval_reassign')).toBeInTheDocument(); + }); + + it('no `visible` at all → the declared action renders (ungated stays ungated)', () => { + renderWithGate({ ...APPROVE }); + expect(screen.getByTestId('declared-action-approval_approve')).toBeInTheDocument(); + }); + + it('an empty-string `visible` is not a declared gate — the action still renders', () => { + renderWithGate({ ...APPROVE, visible: '' }); + expect(screen.getByTestId('declared-action-approval_approve')).toBeInTheDocument(); + }); + + it('an expression-valued `visible` keeps its verdict — false hides, true shows', () => { + const gated = { ...APPROVE, visible: 'status == "pending"' }; + const { unmount } = renderWithGate(gated, { ...REQUEST, status: 'approved' }); + expect(screen.queryByTestId('declared-action-approval_approve')).toBeNull(); + expect(screen.getByTestId('declared-action-approval_reassign')).toBeInTheDocument(); + unmount(); + renderWithGate(gated, { ...REQUEST, status: 'pending' }); + expect(screen.getByTestId('declared-action-approval_approve')).toBeInTheDocument(); + }); + + it('a hidden action leaves NO clickable surface in the toolbar', async () => { + // Hiding is not cosmetic on this surface: this component's own click handler + // is what POSTs the approve/reject call, so what matters is that the gated + // action contributes no button at all — not merely that a query by testid + // misses it. Asserting the toolbar's button SET (rather than clicking the + // companion and counting dispatches) is what makes this case move: an + // unclicked extra button dispatches nothing either way, so a + // dispatch-counting version of this test was green before the fix too. + renderWithGate({ ...APPROVE, visible: false }); + const buttons = screen.getByRole('toolbar').querySelectorAll('button'); + expect(buttons).toHaveLength(1); + expect(buttons[0]).toHaveAttribute('data-testid', 'declared-action-approval_reassign'); + fireEvent.click(buttons[0]); + await waitFor(() => expect(executeSpy).toHaveBeenCalledTimes(1)); + expect(executeSpy.mock.calls[0][0]).toMatchObject({ name: 'approval_reassign' }); + }); +}); diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index e2537d8f2d..280c2d8057 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -41,6 +41,21 @@ export { renderChildren } from './lib/utils'; export { cva } from 'class-variance-authority'; export { getLazyIcon, isLucideIconName, LazyIcon, toKebabIconName } from './lib/lazy-icon'; +// The member-action visibility gate — "did this action DECLARE a `visible` gate +// at all?" (`!= null && !== ''`), the single definition objectui#3492 +// established and PR #3816 / #3825 / #3836 applied to every member-action gate +// in this package and in `plugin-grid`. +// +// Exported because the family has a member OUTSIDE these packages: app-shell's +// `DeclaredActionsBar` mounts an object's server-declared actions as plain JSX +// (no `SchemaRenderer` in front, so its own gate is the only one on that path), +// and asked truthiness — `visible: false` rendered a live Approve/Reject button +// (objectui#3835). A re-export, not a copy: five gates in three packages asking +// one question must not drift into five answers, which is exactly the shape +// objectui#3142 had to unpick for `locations` in these same files. app-shell +// already depends on this package, so the direction costs nothing new. +export { hasDeclaredVisibilityGate } from './renderers/action/visibility-gate'; + // Export placeholder registration export { registerPlaceholders } from './renderers/placeholders'; From 9e7e15987d21e4d48ad1a20e914b5f9c52cc4f0c Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 8 Aug 2026 19:24:46 +0000 Subject: [PATCH 2/2] =?UTF-8?q?docs:=20=E4=BF=AE=E6=AD=A3=E8=B7=A8?= =?UTF-8?q?=E4=BB=93=E5=BC=95=E7=94=A8=20objectui#4984=20=E2=86=92=20objec?= =?UTF-8?q?tstack#4984?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 「fixture 让坏规则保持绿色」那一族是 objectstack(framework)仓的 #4984 (见其 packages/lint 内多处裸引用),objectui 侧要按本仓约定写成 objectstack#4984,否则指向本仓一个不存在的号。 Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt Co-authored-by: Claude --- .changeset/declared-actions-bar-visible-gate-3835.md | 2 +- .../app-shell/src/views/__tests__/DeclaredActionsBar.test.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/declared-actions-bar-visible-gate-3835.md b/.changeset/declared-actions-bar-visible-gate-3835.md index 64587116ec..93d3ce6711 100644 --- a/.changeset/declared-actions-bar-visible-gate-3835.md +++ b/.changeset/declared-actions-bar-visible-gate-3835.md @@ -51,7 +51,7 @@ all, and the bar still renders no chrome when its located set is empty. The suite that covered this component could not have caught it: it stubbed the whole predicate entry constant-true (`useCondition: () => true`), with a comment saying the test actions omit `visible` "so this is unused" — which made the gate -unreachable from the only tests that mount this component (the objectui#4984 +unreachable from the only tests that mount this component (the objectstack#4984 family, where a fixture keeps a broken rule green). That stub is gone; the suite now runs the real `useCondition` / `toPredicateInput` and doubles only the action dispatch, so all four shapes (`false` hides / `true` renders / undeclared renders diff --git a/packages/app-shell/src/views/__tests__/DeclaredActionsBar.test.tsx b/packages/app-shell/src/views/__tests__/DeclaredActionsBar.test.tsx index 6a21aaa3b7..59187399bc 100644 --- a/packages/app-shell/src/views/__tests__/DeclaredActionsBar.test.tsx +++ b/packages/app-shell/src/views/__tests__/DeclaredActionsBar.test.tsx @@ -24,7 +24,7 @@ const executeSpy = vi.fn().mockResolvedValue({ success: true }); // It used to be stubbed constant-true, with a comment saying our test actions // omit `visible` "so this is unused" — which made the component's `visible` // gate unreachable from this suite for as long as it existed, so the truthiness -// bug objectui#3835 reports lived here untouched (the objectui#4984 family: a +// bug objectui#3835 reports lived here untouched (the objectstack#4984 family: a // fixture keeping a broken rule green). A re-spelled stub would not fix that: // `visible: false` only reaches "hidden" if `toPredicateInput` passes the // boolean through and `evaluateCondition` short-circuits it, so a stub is a