Skip to content

fix(app-shell): a failed comments or activity read on a record page shows a load-failure notice with a Retry, not the empty state - #29358

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-rcy6ga
Nov 2, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-rcy6ga

Conversation

@objectstack-fleet

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

Copy link
Copy Markdown
Contributor

domain:ui#1 · zhuangjianguo · session_01QsR2jUJ19MstFvFhjgiYTu

Requested by Steedos · project thread

Fixes #29188

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

维护者速读

  • 改了什么:记录页的评论读取或活动读取失败(5xx 或网络断开)时,讨论面板、「动态」面板和「动态」标签页不再显示「暂无评论」/「暂无活动记录」,而是显示「无法加载此记录的评论/活动」并带「重试」按钮;读取失败期间「动态」标签不再显示数字 0。重试只重新读取失败的那一半。
  • 为什么改:staging 上托管环境的评论读取持续返回 500(cloud#3485),页面却显示「暂无评论」,用户以为自己写的评论丢了,故障只能在开发者工具里看到。
  • 风险与回滚:低。被拒绝(401/403)、空结果、404、接口被对象禁用和 400 的处理都与之前相同;只有 5xx 和无状态的网络失败走新状态。@object-ui/react 的 DiscussionContextValue 和时间线组件新增三个可选成员,均为加宽。附件面板 main 上已经能显示失败与重试,本 PR 只加了测试。回滚即 revert 本 PR。
  • 先看:① ui/packages/app-shell/src/views/RecordDetailView.tsx 的 feedReadVerdictOf 与 handleRetryFeed,以及 feed effect 里的 retrying 分支;② ui/packages/plugin-detail/src/RecordActivityTimeline.tsx 的 failedNotice;③ ui/packages/plugin-detail/src/renderers/record-activity.tsx 的计数置空。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/app-shell/src/views/RecordDetailView.feedLoadFailure-29188.test.tsx,预期 10 个测试全绿,覆盖评论 500、活动 500、重试、空结果对照、拒绝对照和附件 500。
  • 需要你做的:无,席位落地。

What changes

A record page's comments read and activity read each end in one of three verdicts per source: answered, refused or failed. Before this change a read could only be refused or not, so a 5xx fell through to the empty state.

  • failed is a 5xx, or a rejection with no status at all (the request never got an answer): classifyLoadError's network, minus the 4xx statuses it also folds there (feedReadVerdictOf in RecordDetailView.tsx). A 404 (a table the deployment does not serve), an object that withholds its API (api-disabled) and a 400 (rejected) stay answered, as before. refused is unchanged (isRefusedFeedRead, objectui#11195).
  • The notice. RecordActivityTimeline draws "We couldn't load the comments for this record." / "…the activity…" (zh: 无法加载此记录的评论。/ 活动。) with a Retry button, in place of the empty state when no rows came back and above the rows when some did. This applies to the panel, the 动态 tab (stream) and the 动态 panel (peek). Only the i18n sentence is rendered: no status code, no server text. The header count, the collapsed chatter label and the 动态 count (record:activity's reported count and RecordTimelineCountProbe) are unknown (no number) while a read has failed, as for a refusal.
  • Retry (handleRetryFeed) re-reads only the failed sources, directly through dataSource.find, never the page payload (which already answered). It does not touch the stream count the payload gave. The button is disabled while the re-read is in flight over rows on screen, and a retry that fails again keeps the notice.
  • Wiring. DiscussionContextValue (@object-ui/react) gains the optional activityFailed, commentsFailed and onRetryFeed. record:activity and record:chatter forward them, and RecordChatterPanel and RecordActivityTimeline take them as optional props. A host that sets none of them renders exactly as before.
  • Locale keys: detail.activityLoadFailed, detail.commentsLoadFailed and detail.retryLoadFeed in all ten packs (en, zh, ar, de, es, fr, ja, ko, pt, ru) at the en place, worded as each pack's attachmentsLoadFailed / retryLoadAttachments, plus the provider-less defaults in useDetailTranslation.ts. The eight non-en/zh packs came in claim amendment 2, after all-locales-key-parity.test.ts went red on Test UI 14/16 at 89ec072.
  • Attachments: no code change. Measured on main, a 500 on the attachments read (payload part or direct GET) already shows RecordAttachmentsPanel's "We couldn't load the attachments for this record." with a Retry, and the count store holds no value, so no badge is drawn. The payload path is now pinned.

Contract review

Seat self-review, per the 10:43Z triage ruling. Every addition is optional and additive; no member is removed, renamed or narrowed.

Surface Addition Default when absent
DiscussionContextValue (@object-ui/react) activityFailed?: boolean, commentsFailed?: boolean, onRetryFeed?: () => void no failure state, no Retry
RecordActivityTimelineProps (@object-ui/plugin-detail) the same three props false, false, no Retry button
RecordChatterPanelProps (@object-ui/plugin-detail) the same three props, forwarded as above
detail locale namespace (@object-ui/i18n) activityLoadFailed, commentsLoadFailed, retryLoadFeed, in all ten packs n/a (every pack carries them)

No spec, metadata or wire shape changes; the server's page payload is read as before.

Pins changed

  • RecordDetailView.feedRefusal-11195.test.tsx, "a 500 is not "no permission"": an activity 500 now shows the load-failure notice instead of the empty state. It still asserts the no-permission line is absent.
  • RecordDetailView.feedLoading.test.tsx (claim amendment 1, test only): its rejections were bare new Error('403 FEEDS_DISABLED') / new Error('404 sys_activity') with no status. A statusless rejection is what a network failure looks like, so three rows went red. Each rejection now carries the status its message names, in the ObjectStack client's shape (httpStatus, code), as the 11195 test already does.
    • "leaves the loading state when a feed read REJECTS (both rejecting)": the comment read is a real 403 now, so the panel settles on the comments no-permission line (objectui#11195's behaviour for a 403 since that card) instead of the empty copy. The no-spinner assertion is unchanged.
    • "leaves the loading state when only one read rejects and the other resolves empty": the activity 404 carries its status and keeps the empty copy. Assertions unchanged.
    • "leaves the loading state when both reads reject" (authored discussion node): the same as the first row.

Tests

  • New RecordDetailView.feedLoadFailure-29188.test.tsx (10 tests). It uses the real ObjectStackAdapter over a held transport, the RecordDetailView.recordOpenOneRequest-28470 rig, under StrictMode. It covers a comments 500 as a payload part and as a direct GET (notice, Retry, no empty copy, no leak, 动态 tab with no count), Retry re-reading only sys_comment directly (no activity re-read, the notice gone once it answers), a retry that fails again, an activity 500 both ways, the empty-200 controls both ways, the 403 refusal control (no-permission line, no Retry) and the attachments payload 500 pin.
  • New RecordActivityTimeline.loadFailed-29188.test.tsx (9 tests), through the real en and zh packs and the record:chatter block under a host DiscussionContext, plus the peek face. It covers the notice in place of "No comments yet" (zh: 无法加载此记录的评论。, never 暂无评论), Retry calling the host, rows kept with the notice above them, the activity line, no Retry when the host offers none, a refusal and a failure side by side, the empty control, and the disabled Retry while loading.
  • Measured before the change, on main db6b408, with a temporary copy of the 28470 rig (deleted before code): comments 500 → "No activity recorded" and an Activity0 tab badge; attachments 500 → the panel's unavailable state with Retry and no count.
  • Ablation (scripts/ablation-replace.mjs, feedReadVerdictOf forced back to answered for a 5xx): 7 of 17 red across the new page test and the 11195 file. Those are the six failure rows and the flipped 11195 row; the controls, the refusal row and the attachments pin stayed green. Restored: blob equals HEAD, git diff HEAD empty.
  • Related sweep from ui/ under scripts/pm/os-verify-lock.sh, --maxWorkers=2, at merged head 89ec072: the 109 test files that name the timeline, the chatter panel, DiscussionContext, the feed renderers or the refusal flags, plus the RecordDetailView tests that read sys_comment / sys_activity / the payload / the timeline, plus the two new files. 111 files, 2609 tests, exit 0. apps/console/src/__tests__/record-block-record-reach.test.tsx: 13 passed.
  • pnpm exec eslint on the 13 changed files: 0 errors; the warnings are the files' existing any and hook warnings.
  • At 53d0b77 (the eight locale packs): packages/i18n/ suite 95 files, 1443 passed, 13 skipped, exit 0; all-locales-key-parity.test.ts with the timeline test: 54 passed.

Acceptance notes

  • Claim 6576137247, amendment 1 (RecordDetailView.feedLoading.test.tsx, test only) and amendment 2 (the eight other locale packs). The diff stays inside them.
  • The verdict reads classifyLoadError's network kind and then excludes the 4xx statuses that kind also holds, so a status-carrying 404 keeps the empty state the feedLoading pins describe. This matches the claim's "5xx or network" reading.
  • An object that withholds its API (api-disabled) or answers 400 still shows the empty state, as the claim keeps. Neither is retryable, so a Retry would be wrong advice there.
  • Premise check: the issue's attachments half does not reproduce on main (see Tests); it is pinned rather than changed.

Verification

  • node scripts/pm/dispatch-gates.mjs --ran at 53d0b77 (re-run after the locale commit; the derived list is unchanged): 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN; every recorded exit is 0.
  • pnpm --dir ui exec vitest run scripts/ at 53d0b77: 169 files passed, 2 skipped (4903 tests). node ui/scripts/check-new-cross-file-line-citations.mjs: 0 new citations.
  • pnpm exec turbo run type-check --filter=@object-ui/app-shell --filter=@object-ui/react --filter=@object-ui/i18n --filter=@object-ui/plugin-detail --concurrency=2 at 89ec072: exit 0, 68 of 68 tasks.
  • node scripts/check-sdui-manifest.mjs: exit 0.
  • node scripts/check-changeset-no-major.mjs --base origin/main, node scripts/check-adr-0087-registration.mjs --base origin/main, node scripts/check-closing-keyword-parity.mjs and node scripts/check-partof-closing-keyword.mjs, with this body as the event: exit 0 each.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (not touched)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes. (CI runs the repo lint)
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: its generated artifacts are regenerated and committed. (not touched)
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QsR2jT854xvoBjntpsRE1F

…hows a load-failure notice with a Retry, not the empty state

A 5xx or a request that never got an answer used to settle the discussion
panel on its empty state and the timeline tab's badge on 0. The feed reads now
record answered | refused | failed per source; a failed one draws a notice
with a Retry that re-reads only the failed sources, directly. Refusals, empty
answers and 404s keep today's handling.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jT854xvoBjntpsRE1F
…essage names

A rejection with no status now reads as a network failure and shows the
load-failure notice, so the rows that meant a 403 or a 404 carry it, as the
ObjectStack client does. A refused comment read settles on its no-permission
line.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jT854xvoBjntpsRE1F
ar, de, es, fr, ja, ko, pt and ru carry activityLoadFailed,
commentsLoadFailed and retryLoadFeed at the en place, worded as each pack's
attachmentsLoadFailed and retryLoadAttachments already are.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jT854xvoBjntpsRE1F
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · zhuangjianguo · session_01QsR2jUJ19MstFvFhjgiYTu

席位核对 · ACCEPT · 2026-11-02T13:33Z

Head-sha: 53d0b7741902e701809c9bd5311117fc811be05f

  • Shape:
    • The PR is a draft against main. Fixes #29188 is on body line 4, after the attribution block, under claim 6576137247 and amendments 6576605324 (RecordDetailView.feedLoading.test.tsx, test only) and 6577995786 (the eight other locale packs).
    • 22 files, +824 / −64, inside the amended surface. check-governed-merges --pr 29358: not governed, 888 changed lines, under the human-merge threshold.
    • Clause-②: yes (widening) and Metadata-protocol: none start their lines in the body and the changeset, as claimed. The changeset is patch.
    • Before this note the seat applied the dev's corrected body: the locale line, the Contract review row and the amendment line now name all ten packs.
  • Read in the diff:
  • Contract review (Clause-② yes, widening; the seat's own self-review per the 10:43Z triage ruling):
    • DiscussionContextValue (@object-ui/react) gains three optional members: activityFailed?: boolean, commentsFailed?: boolean and onRetryFeed?: () => void. They are added to the memo's dependencies. When absent there is no failure state and no Retry, which is today's behaviour.
    • RecordActivityTimelineProps and RecordChatterPanelProps (@object-ui/plugin-detail) gain the same three optional props, forwarded. They default to off.
    • detail.activityLoadFailed, detail.commentsLoadFailed and detail.retryLoadFeed are in all ten packs, and all-locales-key-parity passes on this head.
    • Nothing is removed, renamed or narrowed. There is no spec, metadata or wire change. Verdict: passes as a widening.
  • Pins changed: RecordDetailView.feedRefusal-11195 (an activity 500 now shows the notice; the "not no-permission" assertion is kept) and three RecordDetailView.feedLoading rows. Those rows' stubs now carry the status their message names. The PR body explains each.
  • Gates (check-runs on this head): 44 success, 11 skipped, 0 failed or pending. mergeable_state is clean. The earlier red at 89ec072d was all-locales-key-parity (eight packs lacked the keys), fixed under amendment 2.
  • Tests (dev):
    • New tests: 10 and 9 cases. Ablating the verdict to answered reds 7 of 17, restored.
    • Sweep at 89ec072d: 111 files, 2,609 tests, plus console 13 of 13. At 53d0b774: the i18n suite 95 files with 1,443 passed, and parity plus timeline 54 passed. Type-check 68 of 68. Derived gates 34 of 34.
  • NOT MEASURED: the seat ran no live session. The 500 was measured by the dev on the real-adapter rig.
  • Findings: none.
  • Serial: fix(ui): the list flow at phone width: 44 px targets, inset panels, a card pager, the active sort on the bar, and Back that pops cleanly #29273 (Console at phone width: Lists flow audit and fixes at 375 and 390 px #29074, landed 46e24000) touched RecordDetailView.tsx's Back block. This PR is clean against main at this head.
  • Landing: ordinary queue landing, pr_ready then automerge_enable. Fixes #29188 closes the card on merge.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 2, 2026 13:34
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 2, 2026
Merged via the queue into main with commit e5a24dd Nov 2, 2026
64 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-rcy6ga branch November 2, 2026 13:53
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/l tooling

Projects

None yet

1 participant