Skip to content

Commit 16e2122

Browse files
test(ui): the Automations Problems double serves the draft door's node path (#28192) (#28227)
<!-- claude-projects-attribution: {"github_login":"os-tesla"} --> _Requested by **Tesla** · [project thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6JaoBtX2c3X95gPkWQR9dnEYaY)_ Clause-②: no Metadata-protocol: none Fixes #28219 ## 维护者速读 - 改了什么:只改了一个测试文件。Studio「自动化」页「问题」列表与节点单元格一致性的测试里,模拟草稿读取的替身现在像真实服务端一样,把节点配置值发现的路径改写成草稿诊断的写法(`nodes.1.config.fields.id`)。 - 为什么改:#28192 让这类发现带上了键路径 `flows[0].nodes[1].config.fields.id`;真实草稿接口会把它改写成条目相对路径,但测试替身直接透传原始路径,导致 main 上 `Test UI (13/16)` 一直红、合并队列不断踢出 PR。 - 风险与回滚:不动任何产品代码,不发包;回滚即 revert 本 PR。 - 先看:测试文件里新增的 `itemRelative`(对照 `packages/metadata-protocol/src/metadata-diagnostics.ts` 的 `itemRelativeIssuePath`),以及重新钉住的两行断言。 - 两分钟验证:`pnpm --dir ui exec vitest run packages/app-shell/src/views/studio-design/AutomationsPillar.problemsAgree-27799.test.tsx`,预期 3 条全过。 - 业务含义:问题行现在直接定位到节点本身(显示节点名「Write」,悬停为节点 id),与节点单元格说的是同一句「需要表达式」,这正是 #28192 想要的行为;Studio 没有回归。 ## What changed `draftVerdict` in the test's server double built `_diagnostics` from `validateStackExpressions` with `path: i.path ?? i.where`. Since #28192 (`3efccfd5`) a node config-value finding carries `path: 'flows[0].nodes[1].config.fields.id'`. The real draft door re-spells a gate path found on the written item to the dotted item-relative form in `itemRelativeIssuePath` (`packages/metadata-protocol/src/metadata-diagnostics.ts` L388–400, called from `withPublishVerdict` L422; pinned by the `[#27828]` case in `protocol.draft-publish-verdict.test.ts` L466, `nodes.1.config.fields.name`). That helper is not exported and `@object-ui/app-shell` does not depend on metadata-protocol, so the double mirrors it (for `type: 'flow'`, prefix `flows[0]`) and cites the line. With the door's spelling the Problems row is located at the node itself: `targetLabel` (`ProblemsPanel.tsx` L54–57) renders the node's display name `Write` with the node id as `title`. Before #28192 the row fell back to the `where` prose (`node 'write' … config.fields.id`), which is what L211–212 pinned. They are re-pinned to the node-located shape. The case's subject is unchanged: the row still reads the cell's own refusal, `NEEDED()` (the assertion before the target pins, which stayed green throughout). ## Verification - Red on `origin/main` @ `7811c3f6` (≥ `3efccfd5`): `AssertionError: expected 'flows[0].nodes[1].config.fields.id' to contain 'node \'write\''` — 1 failed | 2 passed. - Green on `3efccfd5^`: by reasoning from L62, not a second build — before #28192 lint gave a node config finding no `path`, so `i.path ?? i.where` served the `where` prose that L211–212 pinned. - Door spelling only (before the re-pin): `expected 'Write' to contain 'node \'write\''` — the row names the node by its label; cell and row agree. - With the fix: 3 passed (3). - Ablation: `path: i.path ?? i.where` restored verbatim ⇒ reds again with `Received: "flows[0].nodes[1].config.fields.id"`; fix restored, `git diff HEAD` empty. - Gates: `dispatch-gates.mjs --commands` derived 24 commands for this one file; ran record below. Ran record (in progress; completed record follows in the os-dev-report): 14 of 24 exit 0 so far; `pnpm --dir ui exec vitest run scripts/` exit 1 (7 tests in 2 files, under triage against main — this diff touches no script). ## Acceptance notes - File surface: this one test file only. - Changeset: none. `@object-ui/app-shell` is private and its `files[]` ships no test sources, so the diff publishes nothing; `skip-changeset` rides the create (as #28193 did). - No Studio change: the Problems row still names the node, so no revert of `3efccfd5` is needed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01L9qibvQ7smY6Y87y1pRDaY Co-authored-by: Claude <noreply@anthropic.com>
1 parent 3238750 commit 16e2122

1 file changed

Lines changed: 17 additions & 5 deletions

File tree

‎ui/packages/app-shell/src/views/studio-design/AutomationsPillar.problemsAgree-27799.test.tsx‎

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
* The client is a server double that judges the way the server does: the save
1515
* door parses `FlowSchema` (the flow parse does not run the value-slot rule),
1616
* and the draft read folds in the publish gate's finding, which is the real
17-
* `validateStackExpressions` from `@objectstack/lint`, located by its `where`
18-
* as the gate locates a node's config value. The canvas is the real
17+
* `validateStackExpressions` from `@objectstack/lint`, located at the node's
18+
* config key path re-spelled item-relative as the door does. The canvas is the real
1919
* `FlowPreview` beside a button that selects the Update Records node.
2020
*/
2121

@@ -55,11 +55,22 @@ function schemaErrors(body: unknown): Entry[] {
5555
return parsed.success ? [] : parsed.error.issues.map((i) => ({ path: i.path.map(String).join('.'), message: i.message }));
5656
}
5757

58+
/**
59+
* The door re-spells a gate path found on the written item item-relative
60+
* (`flows[0].nodes[1].config.fields.id` → `nodes.1.config.fields.id`) and keeps
61+
* any other as written: mirrors `itemRelativeIssuePath` in
62+
* `packages/metadata-protocol/src/metadata-diagnostics.ts`.
63+
*/
64+
function itemRelative(path: string): string {
65+
if (path !== 'flows[0]' && !path.startsWith('flows[0].') && !path.startsWith('flows[0][')) return path;
66+
return path.slice('flows[0]'.length).replace(/\[(\d+)\]/g, '.$1').replace(/^\./, '');
67+
}
68+
5869
/** The draft read's `_diagnostics`: the parse, plus what the publish gate refuses. */
5970
function draftVerdict(body: Record<string, unknown>): { valid: boolean; errors?: Entry[] } {
6071
const publish = validateStackExpressions({ flows: [body] })
6172
.filter((i) => (i.severity ?? 'error') === 'error')
62-
.map((i) => ({ path: i.path ?? i.where, message: i.message, code: 'expression-invalid' }));
73+
.map((i) => ({ path: itemRelative(i.path ?? i.where), message: i.message, code: 'expression-invalid' }));
6374
const errors = [...schemaErrors(body), ...publish];
6475
return errors.length ? { valid: false, errors } : { valid: true };
6576
}
@@ -208,8 +219,9 @@ describe('Automations page — Problems and the node agree on a blank expression
208219
expect(screen.queryByText(EMPTY())).toBeNull();
209220
const row = target.closest('li')!;
210221
expect(within(row).getByText(NEEDED())).toBeInTheDocument();
211-
expect(target.textContent).toContain("node 'write'");
212-
expect(target.textContent).toContain('config.fields.id');
222+
// The row is located at the node itself (#28192): its name, keyed by its id.
223+
expect(target.textContent).toBe('Write');
224+
expect(target.getAttribute('title')).toBe('write');
213225
expect(screen.getByTestId('auto-problems-count')).toHaveTextContent('1');
214226
});
215227

0 commit comments

Comments
 (0)