Skip to content

fix(app-shell): a route whose object read the server answered shows the server reason, not "check your connection" - #29883

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

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

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steedos · project thread

Fixes #29728

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:应用路由读取对象定义失败时,如果服务器其实已经回答了(5xx,或带有本版本不认识的错误码的 404),页面现在显示服务器给出的原因和错误码,而不再让人「检查网络连接」。
  • 为什么改:服务器明明说了原因,界面却让用户去排查网络,误导用户也误导排障;列表页、记录页、应用页早已按同一规则显示服务器原因,路由级这一处是最后一个。
  • 风险与回滚:风险低。适配器只是在抛出的包装错误上多带一个 details.originalError,类、code、statusCode、message 都不变;网络中断和 403 仍显示原来的文案,重试行为不变。回滚即还原本 PR。
  • 先看:ui/packages/data-objectstack/src/index.ts(getObjectSchema 的非 404 分支)、ui/packages/app-shell/src/providers/MetadataProvider.tsx(失败读取保存服务器回答)、ui/packages/app-shell/src/console/AppContent.tsx(RouteObjectReadFailed)。
  • 两分钟验证:在 ui/ 下运行 pnpm exec vitest run packages/app-shell/src/console/__tests__/AppContent.disabledPackageDeepLink-13052.test.tsx,预期 18 个用例全部通过,其中 503 用例显示 SERVICE_UNAVAILABLE 和服务器原文。
  • 需要你做的:无,席位落地。

What changes

On main, an app route whose by-name object read failed drew "Check your connection and try again" whatever happened: an answered 503, an answered 500, a 404 with a code this build does not know, a network drop and a 403 all drew the same sentence. Measured on 99c06965 through the real ObjectStackAdapter under the real MetadataProvider and AppContent.

The provider never saw the server's answer. requestObjectSchema throws Error{status, code, message} with the server's own code and sentence, but getObjectSchema's non-404 catch rewrapped it with createErrorFromResponse and kept no reference to it. A 503 became ConnectionError CONNECTION_ERROR "Connection error: ...", a 500 became UNKNOWN_ERROR, and a network drop got an invented status 500.

  • @object-ui/data-objectstack: getObjectSchema's non-404 rewrap now carries the transport error as details.originalError, as its 404 arm (MetadataNotFoundError) and three other wrap sites in the package already do. The wrapper's class, code, statusCode and message are unchanged, and createErrorFromResponse keeps its signature.
  • MetadataProvider: readObjectThroughAdapter unwraps details.originalError for any ObjectStackError (before, only MetadataNotFoundError). A failed by-name read stores answeredLoadFailure(err) beside the failed name, in a new answers record on the scope entry. Nothing is stored for a refused read (forbidden / unauthorized), which keeps its state as PageView does. The record is dropped when the name is answered or invalidated, and returned beside failed / refused.
  • appObjectScope: failedAnswer is the answer of the first failed name that has one. It is carried on the object, page and dashboard route paths.
  • AppContent: RouteObjectReadFailed draws answer.message in place of the network sentence, plus a code chip (route-object-read-failed-code) in PageView's shape. No i18n rows were added, and Retry is unchanged.

Tests

  • AppContent.disabledPackageDeepLink-13052.test.tsx: a new top-level describe over the REAL adapter driven by a fake fetch. It pins:
    • an answered 503 (wrapped envelope), a 500 (flat envelope) and an unknown-code 404 each show the server code and sentence, and not the network sentence;
    • a page route that binds the object shows the 503 answer too;
    • CONTROLs: a network drop keeps the network sentence with no chip; a 403 keeps today's state; RESOURCE_NOT_FOUND and OBJECT_PACKAGE_DISABLED keep theirs;
    • Retry still asks once more and clears the answer, and a retry that gets no answer draws the network sentence again.
  • getObjectSchema.test.ts: a new describe pins the wrapper's class, code, statusCode and message, plus details.originalError, for a 503, a 500 and a network drop, and a 404 CONTROL.
Ablations (committed head, ablation-replace.mjs, restored blob == HEAD)
  • Drop the provider unwrap (back to MetadataNotFoundError only): 5 of 9 new route pins went red (503, 500, page 503, network CONTROL, retry-to-network). The unknown-404 pin stayed green, because a 404 is still unwrapped.
  • Drop the answers record (if (answer && false)): 6 of 9 went red (503, 500, unknown-404, page 503, both Retry pins).
Verification
  • Related sweep: 301 app-shell test files naming appObjectScope / AppContent / MetadataProvider / route-object-read-failed (6 batches, all green), plus the whole data-objectstack suite (114 files, green).
  • turbo run type-check --filter=@object-ui/app-shell --filter=@object-ui/data-objectstack: 66/66 tasks.
  • eslint on the six touched files: 0 errors.
  • Derived gates (dispatch-gates --commands): 36 derived, 36 run, all exit 0, reconciled with --ran (0 NOT-MEASURED), at ff352f41. The head effe42c8 adds only a merge of one unrelated main commit (cloud-connection).
  • Sweep, type-check and ablations ran at 2a886b17 / ff352f41; the touched files are unchanged since.

Acceptance notes

  • Verdict edge: with the unwrap, a non-404 status that carries RESOURCE_NOT_FOUND or OBJECT_PACKAGE_DISABLED is now read by its code: an absence or a refusal, not a failure. No server path is known to answer that way.
  • Finding, filed by the seat: createErrorFromResponse stamps 500 on any error with no status. As a result, every getObjectSchema caller that reads the wrapper takes a network drop for an answered 500 (43 source files, 58 call sites under ui/). This PR reads the cause instead and leaves the wrapper as it is.

Generated by Claude Code

…at the server said

The by-name object read kept only the failed name, so a 5xx or a 404 with a
code this build does not know drew "check your connection". getObjectSchema
now keeps the transport error as the rewrap's cause; the provider stores the
answer (answeredLoadFailure) beside the failed name and the route draws it.

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

Copy link
Copy Markdown
Contributor Author

domain:ui#2 · zhuangjianguo · session_01QsR2jRFuJz9QBZqPVHYVhJ

维护者速读

  • 改了什么:应用页面读取对象定义失败时,如果服务器给了回答(5xx,或带有本版本不认识的错误码的 404),页面现在显示服务器的原话和错误码,不再一律提示「请检查网络连接后重试」。只有请求没有得到任何回答(断网)时才提示检查连接。
  • 为什么改:Studio: the publish sheet and load errors speak in plain words, and a load error carries the server's real reason #29704 已把列表、记录、相关列表和应用页面改成这条规则,路由级的「对象读取失败」是剩下的一处。实测 503、500、未知错误码的 404、断网、403 在 main 上全部显示「检查连接」;原因是 getObjectSchema 把服务器的回答重新包装后丢掉了原始错误,连断网也被标成 500。
  • 风险与回滚:前端改动,无服务端、无数据改动。@object-ui/data-objectstack 抛出的错误多带一个 details.originalError(与其 404 分支和另外三处一致),错误的类、code、statusCode、message 都不变。403、「对象不存在」「软件包已停用」三种状态保持原样,重试按钮不变。回滚即 revert 本 PR。
  • 先看:ui/packages/app-shell/src/providers/MetadataProvider.tsx(failedReadAnswer 与 answers 映射)、ui/packages/app-shell/src/console/AppContent.tsx 的 RouteObjectReadFailed、ui/packages/data-objectstack/src/index.ts 的 getObjectSchema 异常分支。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/app-shell/src/console/__tests__/AppContent.disabledPackageDeepLink-13052.test.tsx packages/data-objectstack/src/getObjectSchema.test.ts,预期全绿。
  • 需要你做的:无。席位验收后进合并队列。

席位意见: 第一步实测推翻了卡片的前提:问题不只在 provider,适配器本身就把服务器的回答丢了。席位据此在编码前修订认领,加入 data-objectstack 的一处改动,并要求不改已导出的 createErrorFromResponse 的签名。适配器把断网标成 500 的问题影响所有 getObjectSchema 调用方,本 PR 只在路由读取上绕开它,根因另行登记为 #29885。

席位核对 — ACCEPT · domain:ui#2 · head effe42c8 · 2026-11-03T09:54Z

  • 形态: draft PR on main, Fixes #29728 on its own line. 7 files, +391/−26. Tier S: check-governed-merges --pr 29883 says not governed, under 3000 lines. The surface matches claim 6598456131 and amendment 6598716684: MetadataProvider.tsx, appObjectScope.ts, AppContent.tsx, data-objectstack/src/index.ts (only getObjectSchema's non-404 catch), two test files, and one changeset (@object-ui/app-shell + @object-ui/data-objectstack, patch).
  • 前提实测: on main 99c06965, using the real ObjectStackAdapter + fake fetch under the real provider and route, an answered 503, an answered 500, an unknown-code 404, a network drop and a 403 all draw "Check your connection". RESOURCE_NOT_FOUND → not-found and OBJECT_PACKAGE_DISABLED → package-disabled were already right. The provider never saw the answer: getObjectSchema rewrapped it through createErrorFromResponse with no cause (503 → CONNECTION_ERROR, 500 → UNKNOWN_ERROR, network → invented 500). Report: comment 6600085145.
  • 门禁 check-runs (pinned to head effe42c8e07f7894c7e2cdfdc800f6629013fd76): 55 runs: 44 success, 11 path-skipped, none failed or cancelled, including Lint & Repo Gates, TypeScript Type Check, Type Check UI, Test UI (16/16), Build & E2E UI, Live E2E UI, Lint UI, Check Changeset and Governed Surface Queue Guard. Dev local: 301 app-shell and 114 data-objectstack test files green, type-check 66/66, eslint 0 errors, derived gates 36/36 exit 0 reconciled with --ran; --tier derives no mandate.
  • Code read:
    • The adapter sets details.originalError on the wrapper at the call site. createErrorFromResponse keeps its signature, and the new test pins class, code, statusCode, message and originalError for 503, 500 and a network drop, plus a 404 CONTROL.
    • readObjectThroughAdapter unwraps the cause for any ObjectStackError, where it used to unwrap only MetadataNotFoundError. The refusal and absence verdicts run on the cause as before.
    • failedReadAnswer is answeredLoadFailure(err), except for forbidden/unauthorized, which keep their fixed copy (PageView's rule).
    • entry.answers is set only on a failed read. It is dropped when the name is answered and on invalidate, and it rides beside failed/refused outside the published shape.
    • failedAnswerOf takes the first failed name with an answer, on the object, page and dashboard paths.
    • RouteObjectReadFailed draws answer.message instead of the network sentence and a code chip in PageView's shape. Retry still invalidates the failed names.
    • Pins: 503, 500, unknown-code 404 and a page route show the code and sentence. CONTROLs: a network drop keeps the network sentence with no chip; 403, RESOURCE_NOT_FOUND and OBJECT_PACKAGE_DISABLED are unchanged.
    • Ablations: drop the unwrap → 5/9 red (the unknown-404 pin stays green because 404 was always unwrapped); drop the answers map → 6/9 red.
  • 接受的偏差:
    • withoutRefusal is generalised to withoutName<T> in the same file.
    • A non-404 that carries RESOURCE_NOT_FOUND or OBJECT_PACKAGE_DISABLED is now read by its code, because the cause is unwrapped. No known server path answers that way, and reading the server's code is the more faithful verdict.
    • Status is not stored, because no reader draws it.
  • 范围外去向: createErrorFromResponse stamps 500 on any status-less error, so every other getObjectSchema caller (58 call sites in 43 files under ui/) still reads a network drop as an answered 500. Filed as data-objectstack: createErrorFromResponse stamps status 500 on an error that got no answer, so a dropped connection reads as an answered server error #29885 (finding, domain:ui). No Skills-docs: line is owed.
  • 落地条款: Clause-②: no. @object-ui/app-shell is private, and @object-ui/data-objectstack changes no export, type or signature: details is already Record<string, unknown>, and originalError is the key its other wrap sites set. Metadata-protocol: none, patch. No contract review is owed. Land by pr_ready + automerge_enable once every check is green on this head.

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 tests 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.

console: a route whose object read was answered (5xx, or a 404 with an unknown code) says "check your connection" instead of the server's reason

1 participant