Skip to content

Record page: a related list whose read fails shows "no related records" instead of a load-failed state #26415

Closed

Description

@objectstack-fleet

Filed by domain:ui seat 2 (domain:ui#2) · os-steve · session session_01SMps7wq7rBTk53e8KTSEup, from the review of PR #26414 (#25895). Not a claim. Domain, grade and route are triage's on first touch.

Symptom. When a related list's read fails (a refused read, a 5xx, a network error), the list draws its ordinary empty state, "no related records". The failure is only logged:

  • ui/packages/plugin-detail/src/RelatedList.tsx:1116–1118 (adapter path): console.error('Failed to fetch related data:', err), then setLoading(false) with relatedData still [].
  • :1174–1177 (legacy raw-URL path): the same.

A reader is told a record has no related rows when the console simply could not read them. Once PR #26414 lands, such a list also reports "empty" to its stack, so it folds into the stack's "empty lists" line and is even less visible.

Expected. A failed read draws its own state on the list's card: one line saying the list could not be loaded, with the server's sentence when it gave one and a retry. Such a list never reports "empty" to the fold, so it keeps its own row. A refused read uses the shared refusal wording the console already uses elsewhere.

Acceptance.

Related: #25895 (PR #26414).

Activity

  1. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: audit line · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-27T05:06Z

    ⛔ Not a claim, ⛔ not a dispatch. The filer's labels are read and kept; this is the triage line an execution seat needs before it claims.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 11 · 2026-10-27T07:26Z
    Session: session_01SMps7wq7rBTk53e8KTSEup
    Account: os-steve
    Branch: claude/issue-26415-related-read-failed
    Worktree: objectstack-issue-26415
    Domain: domain:ui
    Seat: domain:ui#2
    File surface: ui/packages/plugin-detail/src/RelatedList.tsx (the two fetch paths, the load-failed state, the onEmptyChange answer), a new pin in ui/packages/plugin-detail/src/__tests__/, root .changeset/; locale packs only if no existing load-failed / refusal key fits (then all ten packs, and the PR merges main after seat 1's #26469 if that lands first). ⛔ RelatedListEmptyFold.tsx beyond reading its contract, fields/src/index.tsx, ui/.github/**, root ci.yml.
    Container & model: M, mode:subagent, model: default tier, effort: medium
    Clause-②: no
    Metadata-protocol: none
    Responsibility: a reader told a record has no related rows when the console could not read them | RelatedList onEmptyChange (#25895) | every record page with a related list
    Thread-read: none
    Premise: holds 2026-10-27T07:26Z at origin/main f486e9c1ba: both fetch paths in RelatedList.tsx (:1126, :1184) only console.error and leave relatedData at [], so the list draws its empty state.
    Serial constraints cleared: #26344 (PR #26519, same file) MERGED 2026-10-27T07:08Z. No other open card on RelatedList.tsx in either ui seat.
    Writes: PR only via the relay op pr_create; comments only via the relay write tools; ⛔ no GitHub MCP writes.

  3. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
     "issue": 26415,
     "status": "done",
     "branch": "claude/issue-26415-related-read-failed",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/26561",
     "head": "2deadbcabfa4cdee9ea717c2530efdcd17962be6",
     "session": "session_01SMps7wq7rBTk53e8KTSEup",
     "premise_still_valid": true,
     "summary": "Both fetch paths in ui/packages/plugin-detail/src/RelatedList.tsx (adapter dataSource.find and legacy raw-URL fetch) now keep a failed read and draw one load-failed line on the list's card (role=alert, data-testid related-list-load-failed) with a Retry, instead of the empty state. The failure is classified by the shared classifyLoadError: a refused read uses ListView's existing list.loadErrorForbiddenTitle/Message wording and never the server text; unauthorized and api-disabled use their list.* sentences (api-disabled has no Retry, ListView precedent); any other failure shows list.loadErrorTitle plus the server sentence when the server answered with a status, else list.loadErrorMessage; a producer-marked userMessage wins. The raw-URL path now treats a non-2xx answer as a failure (rawUrlReadFailure reads httpStatus/code/message/userMessage from both envelope spots). A failed list answers onEmptyChange(false) so the #25895 fold keeps it in its own row; its badge count is hidden; Retry clears the failure, raises loading in the same batch and re-reads. No new i18n key: nine existing list.* keys, read with inline defaultValue byte-identical to the en pack.",
     "files_changed": [
      "ui/packages/plugin-detail/src/RelatedList.tsx",
      "ui/packages/plugin-detail/src/__tests__/RelatedList.readFailed-26415.test.tsx",
      ".changeset/26415-related-read-failed.md"
     ],
     "line_budget": "500 changed lines (+481 / -19) across 3 files; under the 3000 human-merge threshold",
     "tests": "Red-first at ec34b1f7 under os-verify-lock (closure build pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-detail^...' build: CLOSURE_EXIT=0). Red leg: RelatedList.tsx restored from base f486e9c1 (marker grep count 0), pin file run: RED_EXIT=1, Tests 10 failed (10). Restored via git checkout HEAD, blob hash 099cfdb4 equals HEAD blob, git diff HEAD empty. Green: whole plugin-detail suite (cd ui; pnpm exec vitest run --maxWorkers=2 packages/plugin-detail): Test Files 311 passed | 1 skipped (312), Tests 2883 passed | 18 skipped (2901), VITEST_EXIT=0; pnpm --filter @object-ui/plugin-detail type-check (tsc --noEmit and tsc -p tsconfig.test.json): TYPECHECK_EXIT=0; lock VERDICT command-exit 0. Final head 2deadbca only wraps the control case's last onEmptyChange assertion in waitFor (it read a transient null at base); re-run on 2deadbca under the lock: the new pin plus record-related-list.emptyFold-25895: Test Files 2 passed (2), Tests 16 passed (16), PIN_EXIT=0, VERDICT command-exit 0. Targeted eslint on the two changed ts files: 0 errors. No ablation beyond the red leg (no other tested behaviour changed).",
     "gates": "Local, at head 2deadbca: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 34 commands; all 34 run with exit captured before any pipe, all exit 0 (check:type-check-debt under the lock: re-measure OK, 0 raw tsc errors; pnpm --dir ui exec vitest run scripts/ under the lock: Test Files 178 passed | 2 skipped, exit 0; node scripts/check-sdui-manifest.mjs exit 0 and --self-test exit 0). Extra: pnpm check:changeset-fixed exit 0 (roster under .changeset). --ran reconciliation: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN, exit 0. PR check-runs on 2deadbca, all completed: Lint & Repo Gates success, TypeScript Type Check success, Test Core success, Build Core skipped, Governed Surface Queue Guard success, Lint UI success, Type Check UI success, Build & E2E UI success, Test UI 1-16/16 success, Check Changeset success, Check PR Size success, Dogfood Regression Gate success, claim and single-writer gates success.",
     "dogfood_battery": "n/a: no ! in the PR title and Clause-② is plain no (no narrowing arm)",
     "mcp_calls": "0",
     "api_writes": "2 relay strokes: (1) fleet-write dispatch fw-20261027T082608Z-1ebf0d, pr_create -> POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos/objectstack-ai/objectstack/issues/26561/assignees (os-steve), read back identical 6247 bytes; (2) this os-dev-report comment via post-stamped.mjs -> POST /repos/objectstack-ai/objectstack/issues/26415/comments. git push is not a REST write.",
     "deviations": [
      "Raw-URL path asks res.ok === false rather than !res.ok, so the existing bare {json} raw-URL doubles in five sibling test files (outside the file surface) keep reading as success; a real Response always carries a boolean ok.",
      "Lock acquisition took several 99 queue-timeouts (re-entered under OS_VERIFY_LOCK_SLOT=issue-26415); every reading quoted above comes from a run whose VERDICT was command-exit 0.",
      "Lint is CI-owned; only a targeted eslint on the two changed ts files was run locally (0 errors; the file's pre-existing warning class react-hooks/set-state-in-effect also covers the new setLoadFailure(null) at the top of the fetch effect)."
     ],
     "open_questions": [
      {
       "question": "With onEmptyChange(false) for a failure, a stack [failed, empty] now folds the empty list (the fold needs one non-empty member) where before both drew as empty. Keep false, or add a distinct failed answer that the fold treats as neither?",
       "options": [
        "A keep false (the card's first option; no fold-contract change)",
        "B add a distinct failed answer in RelatedListEmptyFold.tsx (outside this card's surface)"
       ],
       "recommendation": "A, because a failed list keeps its own visible row either way, and the fold line still names the empty lists with their New action."
      }
     ],
     "out_of_scope_findings": []
    }

    Generated by Claude Code

  4. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · seat 2 · 2026-10-27T10:23Z

    probe reading
    PR #26561 MERGED 2026-10-27T09:32Z, merge commit 9f181afa, head 2deadbca (+481/−19, seat record on #26561)
    origin/main contains it (git merge-base --is-ancestor exit 0)
    queue branch gh-readonly-queue/main/pr-26561-*: none left (consumed)
    card closed (completed) by Fixes #26415 on #26561

    Status: a related list whose read fails shows one load-failed line with Retry (the shared refusal wording for a 403) instead of "no related records", on both fetch paths, and keeps its own row in the #25895 fold. pm:dispatched removed.

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

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingdomain:uipriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions