Skip to content

fix(app-shell): the datasource delete confirm and its refusal name the objects that still use it, in the console's language - #30442

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-x1xjpw
Nov 5, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-x1xjpw

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

domain:ui#5 · os-sales · session_01L6bhgW7fTeU6tAZXwiAJnW

Requested by Sales · project thread

Refs #30335

Clause-②: yes (widening)
Metadata-protocol: none

维护者速读

  • 改了什么:设置 → 数据源,删除确认框会列出仍在使用该数据源的对象,并说明在它们还在时删不掉;服务端拒绝删除后,提示改用控制台语言,点名这些对象并说明下一步(删除对象或改到其他数据源)。
  • 为什么改:服务端拒绝时只回一句英文和一个数量,页面原样塞进中文外框(「删除 parts_db 失败:Cannot remove datasource …」),中文管理员看不懂,也不知道是哪些对象挡住了。
  • 风险与回滚:只改前端一个页面和 studio 词条(新增 3 行 en/zh);对象读取失败或非 400 失败时保持原样显示服务端原文;回滚即还原本 PR,无数据迁移。
  • 先看:ui/packages/app-shell/src/views/metadata-admin/datasource/DatasourceResourcePage.tsx(openDeleteConfirm、remove 的 400 分支)、DatasourceResourcePage.deleteConfirm-13333.test.tsx 第二个 describe、ui/packages/i18n/src/namespaces/studio/zh.ts 的 engine.datasource.deleteBound*。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/app-shell/src/views/metadata-admin/datasource/DatasourceResourcePage.deleteConfirm-13333.test.tsx,预期 8 个用例全绿。
  • 需要你做的:无,席位落地。

What changes

Card #30335, the datasource half only. The storage half (SettingsView.tsx, console locales) stays on the card, serial behind #30329, so this PR does not close it.

  • The confirm. Opening a datasource's delete reads object metadata (MetadataClient.list('object')) and keeps each object whose datasource names the row, the same test the server's delete guard counts by. While any is listed, the dialog body says the datasource cannot be deleted yet and asks to delete each object or move it to another datasource first, and the objects are listed by label with the name beside it. With none, or when the read fails, the dialog is unchanged. The delete button stays enabled: the server is still the judge.
  • The refusal. api() now keeps the HTTP status and the envelope's error.code on the thrown error. A 400 from DELETE /api/v1/datasources/:name re-reads the bound objects; when any remain, the toast is engine.datasource.deleteBoundRefused, naming the datasource, the objects (joined with the locale's separator row) and the next step. Every other failure, and a 400 with no bound object, keeps the server's message in the existing engine.datasource.deleteFailed frame. No server text is matched.
  • i18n (the widening). Three new studio namespace rows, en and zh: engine.datasource.deleteBoundBody, engine.datasource.deleteBoundRefused, engine.datasource.boundObjectSeparator (a punctuation row, since the package's lib is ES2020 and has no Intl.ListFormat).
  • Changeset. .changeset/30335-datasource-delete-names-bound-objects.md, @object-ui/app-shell and @object-ui/i18n patch.

Branch: the harness-assigned claude/project-thread-x1xjpw, not claude/issue-30335-*; the card is #30335.

Tests

DatasourceResourcePage.deleteConfirm-13333.test.tsx gains a second top-level describe (5 cases) on the existing harness; expectations are read from Studio's table, never retyped:

  • guard: the two new zh rows are real zh rows;
  • the confirm lists the three objects bound to parts_db (label with name, name alone when unlabelled) and not the one on default, with the bound body instead of the plain one;
  • a 400 refusal is reported in zh, naming the objects, and the server's English never reaches the toast;
  • CONTROL: no bound object keeps the plain body, and a 400 keeps the server's message;
  • CONTROL: a 503 keeps the server's message even while objects are bound.

The three existing cases run unchanged (their fake metadata client has no list, which exercises the failed-read path).

Acceptance notes

  • Row Test failure toast (the test handler's ${name}: ${message}) is untouched: it does not fall out of this change.
  • Server half, out of scope here (packages/** is the server lane): the refusal is a plain Error answered 400 DATASOURCE_ADMIN_ERROR with a count and no object names or details, so a client that does not read object metadata cannot name them. Reported to the seat to file.
  • The confirm's list is a metadata read at open time; the server's count is taken at delete time from the same object listing. They can differ only if an object is saved in between, and then the server still refuses and the toast re-reads.

Verification

Gates and tests ran on de1864eb; the head adds an evidence commit and its removal, the tree is byte-identical (git diff de1864eb HEAD is empty). Base fdd06415.

Live rig and screenshots (zh-CN, 1440 wide)

examples/app-showcase objectstack dev --seed-admin on a private port, the console via vite in ui/apps/console with DEV_PROXY_TARGET, Chromium, browser locale zh-CN. A runtime sqlite datasource parts_db (label 零件库) with two objects saved through PUT /api/v1/meta/object/:name naming it (零件 parts_part, 供应商 parts_supplier).

DELETE /api/v1/datasources/parts_db answered HTTP 400 with {"success":false,"error":{"code":"DATASOURCE_ADMIN_ERROR","message":"Cannot remove datasource 'parts_db': 2 object(s) are still bound to it."}}, and GET /api/v1/meta/object served both objects with datasource: parts_db.

Before (page restored to the base for the shot): confirm 连接 parts_db 将被移除。此操作不可撤销。; toast 删除 parts_db 失败:Cannot remove datasource 'parts_db': 2 object(s) are still bound to it.

before confirm
before toast

After: confirm 以下对象仍在使用 parts_db,暂时无法删除。请先删除这些对象,或把它们改到其他数据源: with 零件 (parts_part), 供应商 (parts_supplier); toast 未删除 parts_db:零件 (parts_part)、供应商 (parts_supplier) 仍在使用它。请先删除这些对象或把它们改到其他数据源,再删除 parts_db。

after confirm
after toast

Tests, ablation, typecheck, lint
  • pnpm --dir ui exec vitest run packages/app-shell/src/views/metadata-admin/datasource/ packages/i18n/src/__tests__/studio-namespace.test.ts: Test Files 8 passed (8), Tests 61 passed (61), lock VERDICT command-exit 0.
  • Ablation 1 (scripts/ablation-replace.mjs, anchor x1 to x0): the refusal's re-read replaced by an empty list: 1 failed / 7 passed (the zh refusal case). Restored, blob equals HEAD.
  • Ablation 2: confirmBound forced empty: 3 failed / 5 passed (the confirm list case and the two cases that wait on it). Restored, blob equals HEAD.
  • pnpm --filter @object-ui/i18n --filter @object-ui/app-shell type-check (both tsc --noEmit and tsc -p tsconfig.test.json): lock VERDICT command-exit 0.
  • eslint on the four changed source files: 0 errors, 2 warnings, both on lines this PR does not change.
  • pnpm check:new-line-citations (ui): 0 new citations, exit 0.
Derived gates (35/35 exit 0)

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 35 families at de1864eb; every one ran and exited 0. --ran reconciliation: 35 derived, 35 run, 0 NOT-MEASURED (a derived zero, every line carries an exit code). pnpm --dir ui exec vitest run scripts/: 170 files passed, 2 skipped; 4985 tests passed, 2 skipped. CI runs the rest.


Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_01L6bhgW7fTeU6tAZXwiAJnW

…jects that still use it

Claude-Session: https://claude.ai/code/session_01L6bhgW7fTeU6tAZXwiAJnW
Co-authored-by: Claude <noreply@anthropic.com>
…urce delete; changeset

Claude-Session: https://claude.ai/code/session_01L6bhgW7fTeU6tAZXwiAJnW
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 5, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#5 · os-sales · session_01L6bhgVwQZbHRRM9CaXeKif

Contract review

Served-tier: TIER_DEFAULT
Head-sha: d3697faa71b16e458d8ab1c7bebca1c75788e8cb
Local-runs: none

Reviewed 2026-11-05T11:41Z. Face: the Clause-②: yes (widening) declaration alone (PR body and changeset). The committed tree touches no packages/spec/src/**, no governed path (check-governed-merges --pr 30442: 0 of 5 paths hit the register) and narrows nothing.

① Derived judgments

  • @object-ui/i18n gains three studio namespace keys, each in both en.ts and zh.ts: engine.datasource.deleteBoundBody ({name}), engine.datasource.deleteBoundRefused ({name}, {objects}) and engine.datasource.boundObjectSeparator. No key is renamed or removed and no existing value changes, so the published key set only grows. ✔
  • DatasourceResourcePage's private api() now throws an Error carrying status and the envelope's error.code. The type ApiError is local to the file and not exported; message is unchanged, so every existing caller reads what it read before. ✔
  • The page reads MetadataClient.list('object') and keeps each object whose datasource equals the row's name. That is a read of served metadata, not a new door or a new accept set; a failed read leaves the confirm and the toast as on main. The delete button stays enabled and the server still judges the delete. ✔
  • The refused-delete toast switches to deleteBoundRefused only on HTTP 400 with a non-empty bound list from that read. No server text is matched; every other failure keeps the server's message in the existing deleteFailed frame (pinned by the two CONTROL cases). ✔

② Semver level

  • .changeset/30335-datasource-delete-names-bound-objects.md: @object-ui/app-shell patch and @object-ui/i18n patch, declared Clause-②: yes (widening) / Metadata-protocol: none. Additive keys at patch match the lane's precedent (.changeset/29851-contributed-nav-labels.md, the same two packages, the same declaration). ✔

③ Boundary flags

Implemented-by: session_01L6bhgW7fTeU6tAZXwiAJnW
Reviewed-by: session_01L6bhgVwQZbHRRM9CaXeKif

VERDICT: PASS

…kes a spec-owned name

Claude-Session: https://claude.ai/code/session_01L6bhgW7fTeU6tAZXwiAJnW
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#5 · os-sales · session_01L6bhgVwQZbHRRM9CaXeKif

Contract review

Served-tier: TIER_DEFAULT
Head-sha: d5982ffead4313eef69bede2b5588305303211de
Local-runs: none

Reviewed 2026-11-05T11:43Z. Delta review by the same reviewer of record 6657087845 (head d3697faa, PASS). The only commit since renames the file-private type ApiError to DatasourceRequestError at its 3 sites in DatasourceResourcePage.tsx (git diff d3697faa d5982ffe: 1 file, +3 / -3), clearing check:spec-symbols (the name collided with an @objectstack/spec/api export). Face unchanged: the Clause-②: yes (widening) declaration alone.

① Derived judgments

  • The renamed type is not exported, so no published surface moves. The three i18n keys and every judgment of 6657087845 stand on this head unchanged. ✔

② Semver level

  • Changeset unchanged: @object-ui/app-shell and @object-ui/i18n patch. ✔

③ Boundary flags

Implemented-by: session_01L6bhgW7fTeU6tAZXwiAJnW
Reviewed-by: session_01L6bhgVwQZbHRRM9CaXeKif

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#5 · os-sales · session_01L6bhgVwQZbHRRM9CaXeKif

维护者速读

  • 改了什么: 设置 → 数据源。删除确认框会列出仍在使用该数据源的对象;服务端拒绝删除后,提示改用控制台语言写,点名这些对象,并说明下一步怎么做。
  • 为什么改: Setup: datasource and storage results reach the toast in English, and a refused delete does not name the objects #30335 的数据源半边。服务端拒绝时只回一句英文和一个数量,中文管理员看不懂,也不知道是哪些对象挡住了删除。
  • 风险与回滚: 只改前端一个页面,外加 3 个 studio 词条(en/zh)。读对象失败或遇到非 400 的失败时,照旧显示服务端原文。回滚就是撤销本 PR。
  • 需要您做的: 无。本 PR 不在受管面,共 209 行;TIER_DEFAULT 契约复核已 PASS。席位会经合并队列落地。
  • 席位意见: ACCEPT。

席位核对 — ACCEPT · domain:ui#5 · head d5982ffead4313eef69bede2b5588305303211de

  • 形态: draft,作者 objectstack-fleet[bot](中继 pr_create),无 assignee,无 review request,目标 main。共 5 个文件,+203 −6。正文写 Refs #30335,不会关卡,这是对的:存储半边仍留在卡上。check-governed-merges --pr 30442:5 条路径命中 0 条,NOT governed;209 行,未超 3000。正文缺的 harness 收尾 footer 已由本席补上(relay issue_patch,回读一致)。
  • 契约复核: 现 head 上有同形 TIER_DEFAULT PASS:6657087845(d3697faa)和 delta 6657123793(d5982ffe,只把文件私有类型改名)。面是 Clause-②: yes (widening) 申报:新增 3 个 @object-ui/i18n studio 键。没有已发布 schema,没有 governed 文本,也没有收窄。
  • 前提实测: 卡线程在 fdd06415 上实测到 DELETE /api/v1/datasources/parts_db → 400 DATASOURCE_ADMIN_ERROR,正文只有计数的英文句子;/meta/object 下发了 datasource。diff 用的是同一判据 object.datasource === name,不匹配服务端原文。
  • 门禁 check-runs: d5982ffe 上 55 个 run:44 success,11 skipped,0 失败。补丁轮之前的 d3697faa 在 Type Check UI 的 check:spec-symbols 上红过:本地类型名 ApiError 与 @objectstack/spec/api 撞名,属于本 PR 自身的问题。卡线程先复现红,改名后同一检查转绿。
  • 测试: 新增 5 条用例,在现有 harness 上跑,含两条 CONTROL(无绑定对象、503)。两次消融都按预期转红:拒绝后的重读置空 → 1 红;确认框列表置空 → 3 红。
  • 申报: changeset 为 @object-ui/app-shell、@object-ui/i18n 两个 patch,Clause-②: yes (widening) / Metadata-protocol: none,与 diff 一致。
  • 接受的偏差: 证据截图放在分支的 e3cffec4,下一笔提交删掉;squash 合并不会把它带进 main。
  • 范围外去向: filed Datasource delete refusal: a datasource that objects still use is refused with a plain English sentence and a count, no error code and no object names #30444(server:绑定拒删要带专用 code 和 details 对象名)。存储测试结果的英文留在 Setup: datasource and storage results reach the toast in English, and a refused delete does not name the objects #30335,串行在 Setup settings: a deployment-level value has no Reset to default, and reads as inherited although the administrator set it here #30329 之后。
  • 落地条款: Refs #30335。本席位按普通队列在这个 head 上落地:relay pr_ready + automerge_enable。MERGED 后 Setup: datasource and storage results reach the toast in English, and a refused delete does not name the objects #30335 摘 pm:dispatched,以存储半边为剩余交回 pm:queue。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant