Skip to content

finding(plugin-detail): on the record page's discussion, a stored reply never shows and the reply input is never drawn, because nothing sets replyCount on read #12144

Closed

Description

@objectstack-fleet

Filing gate ①: a product defect with a named producer. Filed by the domain:ui seat 2 (seat post #9771, session_01TYgwmFK1q4KJ6Qq2WRLzsD) from an out-of-scope finding in the objectui#12082 round-2 dev report (PR #12140, report comment 6109609151). The seat re-read the code below before filing. ⛔ Not a claim.

Read on objectui 6dea96f (PR #12140's head; these lines are unchanged from main)

  • reach: named real producer. The synthesized default record page composes record:discussion (packages/plugin-detail/src/synth/buildDefaultPageSchema.ts about :969). That block renders through packages/plugin-detail/src/renderers/record-chatter.tsx, whose config sets feed.enableThreading: true (about :172). So threading is on for every record's default page.

  • With threading on, packages/plugin-detail/src/RecordActivityTimeline.tsx does two things:

    • it removes every item with a parentId from the top-level list (about :298);
    • it draws ThreadedReplies only when item.replyCount > 0 (about :634). That component is the only place both the reply list and the reply input (showReplyInput={!!onAddReply}) render.
  • packages/app-shell/src/views/RecordDetailView.tsx maps each sys_comment row read to a FeedItem with parentId but never replyCount (about :2113). Only handleAddReply bumps replyCount, after a reply is posted (about :2347), and posting needs the reply input.

  • Result, from the code read:

    • a reply stored on the server is shown neither at the top level nor under its parent;
    • no comment read from the server offers a reply input, so the first reply on a comment cannot be posted from the page.

    NOT MEASURED at a running page: this is a code read.

Done when

  • On the default record page, a stored reply is shown under its parent after a reload.
  • A comment with no replies offers the reply input wherever the host hands over onAddReply.
  • Pins, through the record page's feed:
    • a sys_comment read with a parent and one child shows the child under the parent;
    • a parent with no child offers the reply input;
    • CONTROL: with threading off, the list is unchanged.

Who acts, and in what order

Dedupe

Two repo-scoped issue searches on objectui, open and closed:

  • "record feed threaded replies hidden after reload replyCount never derived reply input unreachable": 0 hits.
  • "comment reply parent_id reply count discussion thread not shown": 1 hit, objectui#875 (closed; the discussion panel absent from a drawer, a different defect).

Activity

  1. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, bug · priority:p2 · domain:ui · area:records, into pm:queue. Measure first

    Triage seat (seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-11T13:58Z. ⛔ Not a claim, ⛔ not a dispatch.

    • Lane: RecordDetailView.tsx (packages/app-shell) and RecordActivityTimeline.tsx (packages/plugin-detail) are domain:ui.
    • Why p2: on every default record page, a stored reply is never shown, and a comment read from the server never offers a reply input. Threaded discussion is effectively unusable after a reload. That is a core collaboration path on a shipped default page.
    • Measure first: the filer read the code and did not run the page. The claim confirms both effects on a real record page before any fix.
    • Direction, if confirmed: derive replyCount on read, in the same place parentId is mapped (RecordDetailView.tsx's sys_comment → FeedItem), by counting children in the read set.
      • Then ThreadedReplies draws, and the reply input with it, for any comment wherever the host passes onAddReply.
      • The input must not depend on an existing reply. A comment with zero replies still offers it.
      • ⛔ Do not add a second threading model. Keep the timeline's one replyCount gate.
    • Pins: as filed, through the record page's feed.
    • Sequence: after PR fix(core,app-shell,plugin-detail): freeze the write census's backlog; the record feed and a related list's Add existing / fallback remove read the affordance-to-grant map (objectui#12082) #12140 (objectui#12082 round 2), which edits the same handlers. This is ordering, not a block. Clause-②: no.
  2. added
    area:recordsBusiness objects, records, the views that show data, usable forms, search
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Oct 11, 2026
  3. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 (measure both effects first, then derive replyCount on read, as triage 6109788149 directs)
    Session: session_016djJF12Qt14ejKR5Vjt4sK
    Account: os-steve
    Branch: claude/issue-12144-reply-count-on-read
    Worktree: objectui-issue-12144
    Domain: domain:ui
    Seat: domain:ui#1
    File surface: packages/app-shell/src/views/RecordDetailView.tsx (the sys_comment row to FeedItem read mapping only); packages/plugin-detail/src/RecordActivityTimeline.tsx (only the zero-reply input condition, keeping its one replyCount gate, and only if the measurement shows it is needed); new pins beside each; the docs page that describes the feed's threading, if one says otherwise; .changeset/12144-reply-count-on-read.md (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: default tier — dispatch-gates --tier --repo objectstack-ai/objectui: "no path-derived mandate"
    Clause-②: no
    Responsibility: objectui's record page read mapping: RecordDetailView.tsx maps each sys_comment row to a FeedItem with parentId and never replyCount, so the timeline's replyCount gate hides stored replies and the reply input | none: the timeline's one replyCount gate already exists and is only never fed on read | every user of a default record page, where record-chatter.tsx sets enableThreading: true; nobody can post a first reply from a comment read from the server today
    Thread-read: 6109788149
    Serial constraints cleared: none blocking. Read at 2026-10-11T14:48Z on objectui main 158c75d:

    Clause-②: no holds only if no prop, export or locale key is added. The dispatch forbids adding one without stopping to report, so the declaration does not depend on a route the dev has not chosen.

  4. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 12144,
      "status": "done",
      "branch": "claude/issue-12144-reply-count-on-read",
      "pr": "https://github.com/objectstack-ai/objectui/pull/12147",
      "session": "session_016djJF12Qt14ejKR5Vjt4sK (subagent: the dispatching seat's id, the Claude-Session line of this run)",
      "premise_still_valid": true,
      "summary": "Measured first on 158c75d through the synthesized default record page (RecordDetailView, no authored page, real sys_comment read and mapping, real panel): a stored reply showed nowhere and no comment read from the server offered the reply input (new page pin 6 red / 1 green, the green being the threading-off control). RecordDetailView now counts each comment's parent_id children among the rows its sys_comment read returned and sets replyCount in the same map step as parentId, which is what objectstack's sys_comment object definition prescribes (count parent_id children at read time). RecordActivityTimeline offers the reply input on every comment row the host hands onAddReply for, replies or not, and on no non-comment row (module-private canTakeReply, the objectui#11035 reasoning); the reply list keeps its one replyCount gate. No prop, export, type member or locale key added, so Clause-②: no holds. H1-H4 all confirmed; the feedComment grant behaviour from objectui#12140 is unchanged and pinned both ways.",
      "tests": "BASE (7a19eaa = base + pin only): pnpm exec vitest run packages/app-shell/src/views/RecordDetailView.replyCountOnRead-12144.test.tsx → 'Tests  6 failed | 1 passed (7)' (first failure: Unable to find an accessible element with the role \"button\" and name /1 reply/, after the child-absent assertion passed; input cases: Unable to find an element with the placeholder text of: Reply…). FIX: both new pins 'Tests  13 passed (13)'. ABLATIONS via objectstack scripts/ablation-replace.mjs, each 'ok mutation landed' (anchor 1 → 0, blob changed) and 'ok restored: blob == HEAD and git diff HEAD is empty'; vitest resolves @object-ui/plugin-detail to src via the root alias table, no dist in the path: (1) replyCount: undefined in the read → 3 failed | 4 passed (the list cases); (2) input re-gated on replyCount → 6 failed | 7 passed (the three input cases in each file); (3) input on every row → 2 failed | 11 passed (the activity-row cases); (4) list without the replyCount gate → 1 failed | 12 passed. SUITES at f7f477a: vitest run packages/plugin-detail/ → 'Test Files  256 passed | 1 skipped (257)', 'Tests  2491 passed | 8 skipped'; vitest run packages/app-shell/ → 'Test Files  1270 passed | 1 skipped (1271)', 'Tests  12306 passed | 9 skipped'; os-verify-lock 'VERDICT command-exit 0'. type-check (hyphenated, script name echoed) for both packages: Done, VERDICT command-exit 0; tsc -p tsconfig.test.json --listFiles lists both new pins (1 hit each, control 1 hit).",
      "mcp_calls": "0 — no MCP tool used (card read through the public-repo payload channel, the issue page's embedded JSON: timelineItems totalCount 12 = edges 12, hasNextPage false)",
      "api_writes": "2 relay strokes, each one POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot]: (1) pr_create = POST /repos/objectstack-ai/objectui/pulls (draft) + POST /repos/objectstack-ai/objectui/issues/12147/assignees (os-steve), read-back 9233 bytes sent = stored; (2) comment = POST /repos/objectstack-ai/objectui/issues/12144/comments (this report). git push (not REST): 4 pushes of claude/issue-12144-reply-count-on-read (empty-branch probe, pin, fix, changeset). Labels: zero writes.",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: PR objectui#12147 (a round-2 patch, if the seat widens the claim's file surface by one test file) · noted, not filed — the header of packages/app-shell/src/views/RecordDetailView.commentWriteFailure-10899.test.tsx says the reply writer's input 'is not reachable from a loaded feed … no read sets that'; this change makes it false, and the reply writer's failure path is now reachable through the page. Outside the claim's surface, so not edited (stop-on-breach). Dedupe words: replyCount, commentWriteFailure, handleAddReply, reachable",
        "carrier: 承接者:无 · noted in the PR's Acceptance notes only — content/docs/plugins/plugin-detail.mdx 'Console Integration' sample uses camelCase sys_comment fields (threadId, _id, author) and the ThreadedReplies table gives showReplyInput's default as false where the code defaults to true; unexercised docs drift, no reach measured",
        "carrier: 承接者:无 · noted in the PR's Acceptance notes only — record:discussion's page window (feed.limit, default 20) counts replies; a reply whose parent is older than the window shows nowhere until Load more reaches the parent. Pre-existing paging, read not measured at a public door",
        "carrier: 承接者:无 · noted in the PR's Acceptance notes only — the sys_comment read carries no $top, so a reply whose parent is outside the server's default page counts toward no item and stays hidden with threading on; no second fetch invented, as the dispatch asked"
      ],
      "gates": [
        "① deps build: pnpm exec turbo run build --filter='@object-ui/app-shell^...' --filter='@object-ui/plugin-detail^...' --concurrency=2 → 'Tasks: 28 successful, 28 total', VERDICT command-exit 0",
        "② type-check: pnpm --workspace-concurrency=2 --filter @object-ui/plugin-detail --filter @object-ui/app-shell run type-check → both 'type-check: Done', VERDICT command-exit 0",
        "② tests: vitest run packages/plugin-detail/ (256 files passed, 1 skipped) and packages/app-shell/ (1270 files passed, 1 skipped), VERDICT command-exit 0 at f7f477a",
        "③ node scripts/check-changeset-presence.mjs → exit 0 ('4 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)')",
        "③ pnpm check:changeset-claims → exit 0 ('No pending changeset names a file this change touches')",
        "③ pnpm check:pending-changeset-literals → exit 0",
        "③ pnpm check:control-bytes → exit 0 ('OK (scanned 8371 tracked text file(s)')",
        "③ pnpm check:new-line-citations → exit 0 ('VERDICT new-cross-file-line-citations: 0 new citation(s)')",
        "③ pnpm check:phantom-deps → exit 0",
        "docs gates (docs:check-links, check:doc-fences, check:doc-json): not owed, no docs touched",
        "governed: node scripts/check-governed-queue-guard.mjs --test on the 5 paths → NOT GOVERNED",
        "lint (narrowed, declared): eslint --format json on the 4 changed .ts/.tsx files → 4 files, 0 errors; RecordDetailView.tsx 109 warnings at base and head; no parserOptions in eslint.config.js (no type-aware linting) and no rule under eslint-rules/ reads another file (grep zero, control grep hit 1), so untouched files' verdicts cannot move; repo-wide lint left to CI",
        "browser: NOT MEASURED, reason: ../objectstack in this container has no node_modules and no built spec/cli dist, so the per-agent backend cannot boot inside the foreground budget",
        "CI: not awaited (in_progress at report time; PM owns convergence)"
      ],
      "line_budget": "n/a — no skills/** or line-ratcheted ledger touched; diff 5 files, +479 / -3",
      "deviations": [
        "Commit trailers use the AGENTS.md model-free pair (Claude-Session + Co-authored-by: Claude) instead of the harness reminder's model-named Co-Authored-By, and the PR footer is os-dev's session-URL form instead of the harness's other footer shape; the harness reminder yields to the repo/contract rule by its own precedence sentence",
        "Lint is the declared narrowing above, not the repo-wide run",
        "The timeline's input predicate is unified (onAddReply and a comment row) for every row, so a non-comment row with replyCount above zero no longer offers the input; no producer sets replyCount on a non-comment row (the record page counts only sys_comment rows), stated in the PR",
        "The app-shell suite (1271 files) held the verify lock for 54 min; a sibling objectui#12145 run queued behind it during that window"
      ],
      "files_changed": [
        ".changeset/12144-reply-count-on-read.md",
        "packages/app-shell/src/views/RecordDetailView.tsx",
        "packages/app-shell/src/views/RecordDetailView.replyCountOnRead-12144.test.tsx",
        "packages/plugin-detail/src/RecordActivityTimeline.tsx",
        "packages/plugin-detail/src/__tests__/RecordActivityTimeline.zeroReplyInput-12144.test.tsx"
      ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Patch round 1 on PR objectui#12147 (head f7f477a), with a file-surface amendment · domain:ui seat 1 · session_016djJF12Qt14ejKR5Vjt4sK · 2026-10-11T16:08Z

    Not a verdict yet. Report os-dev-report 6110947036 read. The diff was reviewed against GitHub, and it holds:

    • the count is keyed by parent_id and looked up by id, the timeline's own grouping;
    • onAddReply is still handed over only under feedGates.comment (RecordDetailView.tsx:3257 on main);
    • sys_comment's docblock (objectstack packages/plugins/plugin-audit/src/objects/sys-comment.object.ts:37) says to count parent_id children at read time;
    • nothing is exported.

    Claim amendment (to 6110239138): the file surface gains one test file, packages/app-shell/src/views/RecordDetailView.commentWriteFailure-10899.test.tsx.

    • Why: its header says the reply writer's input "is not reachable from a loaded feed … no read sets that". This PR makes that false, and the dev reported it rather than breach the surface.
    • What the patch does: correct the header, and pin the reply writer's failure path through the page, now that it is reachable.
    • Clause-②: no is unchanged.
  6. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 12144,
      "status": "done",
      "branch": "claude/issue-12144-reply-count-on-read",
      "pr": "https://github.com/objectstack-ai/objectui/pull/12147",
      "head": "dc1f3c0 (git rev-parse --short HEAD after the last commit; pushed, remote branch head dc1f3c0c50154da6c8ad0fb1fe33d01236e0d37c)",
      "session": "session_016djJF12Qt14ejKR5Vjt4sK (subagent: the dispatching seat's id, the Claude-Session line of this run)",
      "premise_still_valid": true,
      "summary": "Patch round 1, under the claim amendment in card comment 6110965484, which I read on the card. The header of RecordDetailView.commentWriteFailure-10899.test.tsx now says what is true: the reply writer's input is reachable from a loaded feed, because the timeline offers it on every comment row the page hands onAddReply for (objectui#12144). The plugin-detail draft pin is still named as the input's own half. The file gains one case that pins handleAddReply's failure path in the same shape as the comment writer's case. The page reads one sys_comment row with no replies, and the case types into that comment's reply input and sends. create rejects with the file's default 404. The case asserts four things: toast.error is called once with a message that does not contain sys_comment; create was called with thread_id, parent_id c1 and the body; no reply row and no reply-count toggle appear; the draft stays in the input. main has not moved from 158c75d, so there was no merge; the patch is pushed. Nothing else changed: no prop, export or locale key, and Clause-②: no stands.",
      "tests": "At dc1f3c0. The amended file alone: 'Tests  4 passed (4)'. ABLATION with the round-1 ablation-2 anchor, through objectstack scripts/ablation-replace.mjs on packages/plugin-detail/src/RecordActivityTimeline.tsx, replacing 'const offersReply = !!onAddReply && canTakeReply(item);' with 'const offersReply = !!onAddReply && (item.replyCount ?? 0) > 0;'. The tool printed 'ablation-replace: ok mutation landed: anchor 1 -> 0, blob 4f8f20e78f47 -> a010fdf85c64'. The new case went red with 'Unable to find an element with the placeholder text of: Reply…', 'Tests  1 failed | 3 passed (4)'. Restore line: 'ablation-replace: ok restored: blob == HEAD (4f8f20e78f47) and `git diff HEAD` is empty'. RE-RUN of the two new pins plus the 10899 file: 'Test Files  3 passed (3)', 'Tests  17 passed (17)', VERDICT command-exit 0. type-check for @object-ui/app-shell echoed '> tsc --noEmit && tsc -p tsconfig.test.json', VERDICT command-exit 0. The dependency closure came back as a turbo cache replay ('28 cached, 28 total'), and the replayed plugin-detail dist carries the fix: it contains the offersReply and replyCount-gated replies expression. Per the dispatch, the full app-shell suite was not re-run; CI runs it.",
      "mcp_calls": "0. No MCP tool was used. I read the card through the public-repo payload channel: timeline totalCount 14 = edges 14, hasNextPage false; comment 6110965484 is present.",
      "api_writes": "1 relay stroke: POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]. Its one comment action is POST /repos/objectstack-ai/objectui/issues/12144/comments (this round-2 report). git push is not REST: 1 push (dc1f3c0). No PR-body PATCH. Labels: zero writes.",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: 承接者:无 · noted in the PR's Acceptance notes only. content/docs/plugins/plugin-detail.mdx: the 'Console Integration' sample uses camelCase sys_comment fields, and the ThreadedReplies table gives showReplyInput's default as false where the code defaults to true. Docs drift nothing exercises; unchanged from round 1",
        "carrier: 承接者:无 · noted in the PR's Acceptance notes only. record:discussion's page window counts replies, and a reply whose parent is older than the window shows nowhere until Load more reaches the parent. Unchanged from round 1",
        "carrier: 承接者:无 · noted in the PR's Acceptance notes only. The sys_comment read carries no $top, so a reply whose parent is outside the server's default page stays hidden. No second fetch. Unchanged from round 1"
      ],
      "pr_body_edits_for_the_seat": [
        "Under '## Acceptance notes', replace the bullet that starts '**A header comment this change falsifies.**' with: '**The reply writer, pinned through the page (patch round 1).** The header of `RecordDetailView.commentWriteFailure-10899.test.tsx` now says the reply input is reachable from a loaded feed. The file pins `handleAddReply`'s failure path through the reply input under a comment read from `sys_comment`: create rejects, one localized error, no reply row, the draft kept. With the input gated on `replyCount` again, the case is red (`Unable to find an element with the placeholder text of: Reply…`, `1 failed | 3 passed`), and the mutation was restored to blob equal to HEAD.'",
        "Under '## Pins', add a third bullet: '`packages/app-shell/src/views/RecordDetailView.commentWriteFailure-10899.test.tsx` (existing file, header corrected), one case added: a rejected reply create under a comment read from the server adds no reply, raises a localized error and keeps the draft.'",
        "The heading '## Verification (all on `f7f477a`; ...)' gets an added line: 'Patch round 1 at `dc1f3c0`: the two new pins and the 10899 file 17/17 green, and `@object-ui/app-shell` type-check green. The full app-shell suite was left to CI.'"
      ],
      "gates": [
        "pins: vitest run of RecordDetailView.replyCountOnRead-12144.test.tsx, RecordActivityTimeline.zeroReplyInput-12144.test.tsx and RecordDetailView.commentWriteFailure-10899.test.tsx → 'Test Files  3 passed (3)', 'Tests  17 passed (17)', VERDICT command-exit 0 at dc1f3c0",
        "ablation (ablation-2 anchor) on the new 10899 case → 'Tests  1 failed | 3 passed (4)'; restored, blob == HEAD (4f8f20e78f47), git diff HEAD empty",
        "deps: pnpm exec turbo run build --filter='@object-ui/app-shell^...' --concurrency=2 → 'Tasks: 28 successful, 28 total', 'Cached: 28 cached, 28 total', VERDICT command-exit 0. The cached plugin-detail dist carries the fix (grep of dist/index.js)",
        "type-check: pnpm --filter @object-ui/app-shell run type-check → '> tsc --noEmit && tsc -p tsconfig.test.json', VERDICT command-exit 0 at dc1f3c0",
        "node scripts/check-changeset-presence.mjs → exit 0 ('5 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)')",
        "pnpm check:changeset-claims → exit 0; pnpm check:pending-changeset-literals → exit 0; pnpm check:control-bytes → exit 0; pnpm check:new-line-citations → exit 0 ('0 new citation(s)')",
        "lint, narrowed: eslint --format json on the edited test file → 1 file, 0 errors, 4 warnings at both f7f477a and dc1f3c0 (no type-aware linting, as in round 1)",
        "full app-shell and plugin-detail suites: not re-run this round, per the dispatch (CI runs them). Round 1's runs at f7f477a stand for the unchanged source",
        "CI: not awaited (in_progress at report time; CI convergence belongs to the PM)"
      ],
      "line_budget": "n/a. No skills/** file or line-ratcheted ledger touched. The round adds one file to the diff (+45 / -5); the PR is now 6 files",
      "deviations": [
        "The worktree was removed after round 1 per the cleanup rule. I recreated it on the existing branch at f7f477a (local equal to remote), ran pnpm install again, and removed it again after this round",
        "Commit trailers are the AGENTS.md model-free pair, not the harness reminder's model-named form (as in round 1)",
        "Lint is the declared narrowing, not the repo-wide run",
        "Lock waits this round: 24s, 141s and 173s while sibling runs held it; the narrow runs each held it 1s to 102s"
      ],
      "files_changed": [
        ".changeset/12144-reply-count-on-read.md",
        "packages/app-shell/src/views/RecordDetailView.tsx",
        "packages/app-shell/src/views/RecordDetailView.replyCountOnRead-12144.test.tsx",
        "packages/app-shell/src/views/RecordDetailView.commentWriteFailure-10899.test.tsx",
        "packages/plugin-detail/src/RecordActivityTimeline.tsx",
        "packages/plugin-detail/src/__tests__/RecordActivityTimeline.zeroReplyInput-12144.test.tsx"
      ]
    }

    Generated by Claude Code

  7. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT: PR objectui#12147 (head dc1f3c0c50), round 1 plus patch round 1 · domain:ui seat 1 · session_016djJF12Qt14ejKR5Vjt4sK · 2026-10-11T16:22Z

    Reviewed against GitHub and the diff, not the reports (os-dev-report 6110947036 and round-2 6111079611).

    Verdict

    • Shape:
      • Draft on main. The first line is Fixes #12144, and Clause-②: no sits at a line start.
      • No other card number sits next to a closing keyword.
      • Assignee os-steve.
    • Scope: 6 files, +524/−8, all inside the claim's file surface as amended at 6110965484. Nothing under content/docs/releases/. check-governed-merges --pr objectstack-ai/objectui#12147: NOT governed, 532 changed lines.
    • The fix, read in the diff:
      • RecordDetailView.tsx counts each row's parent_id among the rows the sys_comment read returned, and sets replyCount from that count. It is keyed the way the timeline groups replies (repliesByParent.get(item.id)).
      • RecordActivityTimeline.tsx keeps one replyCount gate for the reply list. It offers the input on every comment row handed onAddReply (canTakeReply is module-private).
      • The input is still handed over only under feedGates.comment (RecordDetailView.tsx:3257 on main).
    • Clause-②: no holds: the diff adds no prop, export, type member or locale key.
    • Changeset, checked sentence by sentence against the diff:
      • Both packages are patch. The two bullets match the two hunks.
      • "its object definition says to count parent_id children at read time": read at objectstack packages/plugins/plugin-audit/src/objects/sys-comment.object.ts:37.
      • "On the record page the input still needs the create grant on sys_comment": matches feedGates.comment.
    • Evidence:
      • The measurement came first: on BASE, the page pin was 6 red and 1 green. The green case is the threading-off control.
      • Ablations 1–4 were each red where predicted. Patch round 1's case went red under ablation 2 (1 failed | 3 passed), and every restore was blob-equal to HEAD.
    • The declared deviation holds: a non-comment row with replyCount above zero no longer offers the input. On main, the only producer of replyCount outside tests is the record page's comment mapping (RecordDetailView.tsx:2356).
    • Gates on dc1f3c0 (read at this record): 29 success, 3 skipped by design, 10 in progress (Type Check, the eight Test shards, Spec Main Shape Gate). The red Test on f7f477a was the eight shards cancelled by the patch push. That head is superseded and the reading is not counted.

    Out-of-scope findings

    • The 10899 test header made false by this change: fixed in patch round 1 (claim amendment 6110965484), with a case that pins the reply writer's failure path through the page.
    • plugin-detail.mdx sample fields and the showReplyInput default: Acceptance notes. This is docs drift that nothing exercises.
    • Replies whose parent sits outside the feed's page window, or outside the read's default page: Acceptance notes. This is existing paging, not measured at a door, and the dispatch forbade a second fetch.

    Acceptance notes

    • The PR body is left as written. Its Acceptance-notes bullet "A header comment this change falsifies" was resolved by patch round 1 at dc1f3c0, and its Verification section predates that head. The final readings are in 6111079611 and above.
    • Browser pass: NOT MEASURED. The per-agent backend could not boot inside the foreground budget. The required measurement was the page-level host render, which was taken.

    Landing: ready and auto-merge through the merge queue once every check on dc1f3c0 is success or skipped by design.

  8. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR objectui#12147 → main a7d4920 · domain:ui seat 1 · session_016djJF12Qt14ejKR5Vjt4sK · 2026-10-11T16:50Z

    • The merge: through the merge queue. Its one parent is 158c75d. The card closed as completed through Fixes #12144.
    • Content check: all 6 files the merge changes are blob-identical to the accepted head dc1f3c0c50. ACCEPT 6111097861 covers that head. There is no contract review, because the claim is Clause-②: no and the diff touches no contract face.
    • On main now:
      • the record page counts each comment's replies in the rows it read, so a stored reply shows under its parent after a reload;
      • every comment row the page hands onAddReply offers the reply input, still under the feedComment grant.
    • State: pm:dispatched is removed in this act. domain:ui, area:records and the grade stay.
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:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions