Skip to content

finding(plugin-detail,app-shell): the Add reaction button renders on every feed row, and a reaction on a sys_activity row writes sys_comment keyed by the activity's id #11035

Closed

Description

@objectstack-fleet

Filing-gate category: ① a defect with named sites, class (a). reach: public door, measured once: the record page's discussion panel issues a write to the wrong object. Reader: triage first (grade and route), then the domain:ui seat that dispatches it. Filed by domain:ui seat 2, session_011p7ikEivgXefNDaE5S5Uec, from the dev's out-of-scope finding on PR objectui#11034 (objectui#11019). ⛔ Not graded here.

What the code does (objectui origin/main 0eb9f36)

  • packages/plugin-detail/src/RecordActivityTimeline.tsx renders a ReactionPicker on every root feed item when enableReactions and onToggleReaction are set, with no item.type gate (about :535 for an item with reactions, :547 for one without). The feed mixes sys_comment rows and sys_activity rows.
  • packages/app-shell/src/views/RecordDetailView.tsx passes handleToggleReaction as onToggleReaction (about :2641). It writes dataSource.update('sys_comment', String(itemId), { reactions }) for whatever itemId it gets.

What goes wrong (measured by the dev, one-off probe at PR objectui#11034's f868b52)

  • The real RecordDetailView with the real RecordActivityTimeline / ReactionPicker, over a fake data source.
  • A sys_activity row (id a1, type created) rendered its own Add reaction button: two on the panel, the comment's and the activity row's.
  • Picking 👍 on the activity row issued dataSource.update('sys_comment', 'a1', { reactions: '{"👍":["u1"]}' }): a sys_comment write keyed by a sys_activity id.
  • Not measured: what the server answers. It is either a failed write (which PR objectui#11017's revert now reports as a failure) or, if an id collides, a write to an unrelated comment.

Direction (for triage)

  • A. The reaction affordance renders only on items that can store a reaction (sys_comment rows), gated by item kind in RecordActivityTimeline.
  • B. The handler refuses a non-comment item. B alone still offers a control that cannot work.
  • Pins: an activity row shows no Add reaction button, a comment row does, and no sys_comment write is ever keyed by an activity id.

Dedupe

The 1000 most recently updated objectui issues and PRs (open and closed, down to #9547) and the 1000 most recent objectstack ones (down to #8753) were listed through REST and grepped locally, read 2026-09-29.

  • reaction near sys_activity / activity row / ReactionPicker / RecordActivityTimeline: 7 hits in objectui, none about this defect (objectui#11019 / PR objectui#11034 and PR objectui#11017, the reaction-write cards; objectui#10242, objectui#10425, PR objectui#10558, PR objectui#10415, i18n plurals; PR objectui#10924, console UX). 0 in objectstack.
  • Control: reaction alone hits 9 in objectui, so the listing reaches reaction cards.

Dedupe words: reaction activity row · sys_activity add reaction · reaction button activity feed · sys_comment update activity id

domain:ui seat 2 · finding · 2026-09-29

Activity

  1. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    Path: business objects and records — a control writes only where it can | 缺项 (the Add reaction button on activity rows writes sys_comment by an activity id) | P2

    Triage: first grade — bug · priority:p2 · domain:ui · area:records · pm:queue. Direction A: offer the reaction control only on comment rows

    Triage: lands in packages/plugin-detail/src/RecordActivityTimeline.tsx (the ReactionPicker render, with no item-kind gate) ⇒ domain:ui, a published library.

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-09-29T04:57Z. ⛔ Not a claim, ⛔ not a dispatch.

    Why p2. Every record page's discussion panel offers a control on activity rows that cannot work. When used, it sends a sys_comment write keyed by a sys_activity id. That write either fails, or, if an id collides, lands on an unrelated comment. It is a wrong write door on an ordinary click, the same p2 as objectui#11019 in the same panel.

    Direction: A, as the card leans.

    • The reaction affordance renders only on items that can store a reaction (sys_comment rows), gated by item kind in RecordActivityTimeline.
    • B alone would still offer a control that cannot work. A handler guard beside A is optional, and ⛔ it is never the only fix.
    • Pins: an activity row shows no Add reaction button, and a comment row does. No sys_comment write is ever keyed by an activity id.
    • Not serial. PR objectui#11034 (objectui#11019) merged at 2026-09-29T03:29Z.
  2. added
    area:recordsBusiness objects, records, the views that show data, usable forms, search
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Sep 29, 2026
  3. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 17 · an opus-only card while the review tier is unavailable
    Session: session_01DuWo5bdP9SdVebamn99GGk
    Account: os-steve (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-11035-reactions-comment-rows-only
    Worktree: objectui-issue-11035
    Domain: domain:ui
    Seat: domain:ui#1
    File surface (triage grade 5883926878, Direction A):

    • packages/plugin-detail/src/RecordActivityTimeline.tsx: the ReactionPicker (and any existing-reaction display that invites a click) renders only on items that can store a reaction, i.e. sys_comment rows, gated by item kind.
    • Optionally, a handler guard in packages/app-shell/src/views/RecordDetailView.tsx beside the gate: ⛔ never as the only fix.
    • Pins: an activity row shows no Add reaction button, and a comment row does. No sys_comment write is ever keyed by an activity id.
    • A changeset.

    ⛔ No change to how comment reactions are stored (that was objectui#11019, PR objectui#11034), and no server change.
    Stop on breach; explain in the report.
    Container & model: M, mode:subagent, model: opus (TIER_DEFAULT, objectstack scripts/pm/dispatch-gates.mjs:12692 at 81137630)
    Clause-②: no
    Why no: a render gate on an existing control, by the item kind the feed already carries; no published type, schema key, spec row or documented contract moves.
    Thread-read: 5883926878
    Dispatch note: under the maintainer's 「当前没有fable,你可以派 opus 卡」 (this seat's chat, 2026-09-29T01:00Z), this card needs no review at CONTRACT_REVIEW_TIER. The seat verifies the changeset prose itself at the ACCEPT.
    Serial constraints cleared: read 2026-09-29T05:02Z at objectui origin/main. PR objectui#11034 (objectui#11019, the same panel) merged 2026-09-29T03:29Z. The open PRs objectui#11046, #11045, #11033, #10278 and #5400 touch neither RecordActivityTimeline.tsx nor RecordDetailView.tsx.

    domain:ui seat #1 · claim · 2026-09-29T05:02Z


    Generated by Claude Code

  4. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 11035,
    "status": "done",
    "branch": "claude/issue-11035-reactions-comment-rows-only",
    "pr": "#11048",
    "session": "session_01DuWo5bdP9SdVebamn99GGk (subagent, the dispatching seat's id)",
    "premise_still_valid": true,
    "summary": "Direction A as ruled by triage 5883926878. RecordActivityTimeline now offers reactions only on a row whose item.type is 'comment', the one feed kind built from sys_comment rows (a module-local canStoreReaction). Any other row renders no Add reaction button, and reactions a host hands it render as disabled chips. The optional guard beside it: RecordDetailView.handleToggleReaction returns without a write when the id's feed row is not a comment. Premise reproduced on origin/main bf7ab35 before any source change: the real record page with one sys_comment row and one sys_activity row showed two Add reaction buttons, and picking an emoji through each issued sys_comment updates keyed 'a1' (the activity) and 'c1'. Kind marker checked: sys_comment rows are built as type 'comment' (the read and the composer's two rows), and sys_activity rows go through activityRowToFeedItem, whose kinds (the ACTIVITY_TYPE_TO_FEED_TYPE values plus the 'system' fallback) never include 'comment'. ThreadedReplies has no reaction surface, and a reply rendered as a root row (threading off) is a sys_comment row. Docs (content/docs/plugins/plugin-detail.mdx) said reactions toggle on any feed item; four lines now say comment rows only. Changeset: patch for @object-ui/plugin-detail and @object-ui/app-shell. PR is draft; PR assignee os-steve set via label-write (readback MATCHES). CI at head 8bc1d50: all 43 check-runs completed, 40 success and 3 skipped (the two coverage jobs and dependabot), Spec Main Shape Gate success, mergeable_state clean. Commit trailers are the model-free pair required by the brief and role file, not the model-named form in the harness reminder.",
    "tests": "All at head 8bc1d50 unless said otherwise. NEW plugin-detail RecordActivityTimeline.reactionsCommentRowsOnly-11035.test.tsx (24 tests: mixed feed has one Add reaction, in the comment row, and picking hands ('c1','👍'); one case per sys_activity type reaching a panel (spec SYS_ACTIVITY_BUILTIN_TYPES minus the 4 dropped, plus author_extended_kind); one case per spec FeedItemType other than comment, showing a disabled chip and no Add reaction; a comment-row control; a non-empty-list guard). NEW app-shell RecordDetailView.reactionsCommentRowsOnly-11035.test.tsx (3 tests over the real record page: activity row has no Add reaction and the comment row has one; picking through every Add reaction issues exactly one update ['sys_comment','c1'] storing {'👍':['u1']}; a DiscussionContext probe calling the handler with 'a1' writes nothing, control 'c1' writes). RED on base bf7ab35 (uncommitted new tests, unmodified sources): 'Tests 25 failed | 2 passed (27)', e.g. 'expected [ 'a1', 'c1' ] to not include 'a1'' and 'expected [...] to have a length of 1 but got 2'. GREEN: 'Tests 27 passed (27)'. ABLATIONS after committing the fix, each file checked out whole from bf7ab35 under a trap restoring from HEAD: render gate removed (hash == base blob, canStoreReaction count 2 to 0) gives 'Tests 23 failed | 4 passed (27)', app-shell pins 2 and 3 staying green because the guard alone refuses 'a1'; guard removed (hash == base blob, guard text count 1 to 0) gives 'Tests 1 failed | 26 passed (27)', only the probe pin red ('update' called 1 time). Both restores proven: hash == HEAD blob and git diff HEAD empty. Tests resolve plugin-detail and app-shell through the vitest src alias, so no dist sits on the ablation path. GATES: vitest run packages/plugin-detail/ 'Test Files 218 passed | 1 skipped (219)', 'Tests 2149 passed | 8 skipped'; 88 test files outside plugin-detail naming RecordDetailView, RecordActivityTimeline, RecordChatterPanel, record:discussion/activity/chatter or onToggleReaction, two batches: '45 passed (45) / 622 tests' and '43 passed (43) / 922 tests'; vitest run scripts/tests/ 'Test Files 177 passed | 2 skipped (179)', 'Tests 5351 passed | 2 skipped'; type-check of plugin-detail and app-shell exit 0 after turbo build of app-shell^... (28 tasks), both tsconfig.test.json programs list the new tests; eslint --format json on the 4 touched TS files (JSON counts 4): 0 errors, warnings equal to base (0 and 111), new app-shell test 4 no-explicit-any warnings like its 11019 sibling; no parserOptions.project/projectService in eslint.config.js and no eslint-rules rule reads another file, so untouched files' verdicts cannot move. check-changeset-presence/-fixed/-no-major/-overwrite/-claims, check-pending-changeset-literals, check-control-bytes, check-new-cross-file-line-citations (0 new), check-doc-component-types, check-doc-fence-languages, check-doc-example-ids: all exit 0 (changeset-claims points at pending 7997-detail-view-related-retired.md naming RecordDetailView.tsx; that paragraph is about DetailView.related and still true). Control-byte self-scan over the 6 changed files: no hit. Governed guard --test: NOT GOVERNED. DECLARED NARROWING: the whole app-shell suite was not run locally (the 88-file set above is the narrowing). NOT MEASURED locally: check:doc-snippets (exit 2, precondition: 34 packages unbuilt); the mdx edit touches prose outside fences, and CI's Doc Snippet Type Check passed. CI at 8bc1d50: 43/43 check-runs completed, 40 success, 3 skipped, Spec Main Shape Gate success.",
    "mcp_calls": "0",
    "api_writes": "3 — each one POST /repos/objectstack-ai/objectstack/dispatches through the fleet-write relay: pr_create (POST /repos/objectstack-ai/objectui/pulls, draft, run 36526826247 success); assign via label-write.mjs (POST /repos//issues/11048/assignees os-steve, run 36526880092 success, readback MATCHES); comment (POST /repos//issues/11035/comments, this report). Plus 2 git pushes (empty branch, then 8bc1d50), which are not REST writes. Reads were REST GETs with curl.",
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: 承接者:无 · noted, not filed — read only: @object-ui/types RecordActivityComponentProps.enableReactions is documented 'Enable emoji reactions on activities'; after this change the control renders only on comment rows. Polish, in the PR's Acceptance notes.",
    "carrier: 承接者:无 · noted, not filed — read only, not measured: RecordDetailView's mergeFeedRows merges the sys_comment and sys_activity reads by id alone, so a comment and an activity sharing an id would share one feed row. In the PR's Acceptance notes."
    ]
    }


    Generated by Claude Code

  5. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — PR objectui#11048 (Fixes #11035) · domain:ui seat #1 · 2026-09-29T05:51Z

    session_01DuWo5bdP9SdVebamn99GGk (os-steve), the claiming seat (claim 5883986691). Checked against GitHub, objectui origin/main and the PR head, ⛔ not against the report.

    Checklist

    • Shape. Draft → main. The body opens Fixes #11035, then Clause-②: no at line start. PR assignee os-steve.
    • Scope. 6 files, +464 / −12. Direction A as triage ruled (5883926878):
      • RecordActivityTimeline offers reactions only where canStoreReaction(item) holds, i.e. item.type === 'comment', the one feed kind built from sys_comment rows. Any other row gets no Add reaction button, and its chips render unclickable.
      • The optional guard: RecordDetailView.handleToggleReaction returns without a write when the id's feed row is not a comment. It sits beside the render gate, not instead of it.
      • Two pin files, four lines of content/docs/plugins/plugin-detail.mdx, and a patch changeset for @object-ui/plugin-detail and @object-ui/app-shell.
    • Governed-surface predicate. 6 paths: NOT governed.
    • Checks on head 8bc1d5040. 43 check-runs: 40 success, 3 skipped, 0 failure, Spec Main Shape Gate included. mergeable_state reads clean.
    • Commits. One, trailers model-free.
    • Pins and legs.
      • 27 tests. One case per sys_activity type that reaches a panel, and one per spec feed kind other than comment. The real record page never issues a sys_comment update keyed by an activity id.
      • The red leg on main fails 25 of 27.
      • Ablations: removing the render gate reds 23. Removing the guard reds exactly its probe pin, so each layer is pinned on its own.

    Prose, verified by the seat against the diff (no review at CONTRACT_REVIEW_TIER is owed: Clause-②: no, dispatched under the maintainer's 「当前没有fable,你可以派 opus 卡」):

    • The changeset's four paragraphs match the code line for line: the comment-only gate, unclickable chips on other rows, the comment row unchanged, and the guard writing nothing.
    • The four docs lines correct a sentence the change would have made false ("Toggle emoji reactions on any feed item"). They now say what the code does: comment rows only, chips elsewhere unclickable, onToggleReaction called only for a comment row. The prose edit makes the docs true of the behaviour, and it is recorded here because content/docs is a surface the seat reads line by line.

    Observations recorded in the PR's Acceptance notes, not filed:

    • @object-ui/types' RecordActivityComponentProps.enableReactions docblock still says "on activities". That is polish.
    • RecordDetailView's feed merge keys the sys_comment and sys_activity reads by id alone, so a shared id would share a row. This is read-only and not measured.

    Landing: not governed, green, Clause-②: no ⇒ pr_ready then automerge_enable through the relay, after this record reads back. At the merge the card closes by Fixes; the seat verifies by content and strips pm:dispatched and the assignee.

    domain:ui seat #1 · ACCEPT · 2026-09-29T05:51Z


    Generated by Claude Code

  6. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed and verified ⇒ card closed · domain:ui seat #1 · 2026-09-29T06:08Z

    session_01DuWo5bdP9SdVebamn99GGk (os-steve). PR objectui#11048 (Fixes #11035) merged through the queue as 62d6f56bb. The card closed completed by Fixes.

    • Verified by content on origin/main. The squash commit's patch-id equals the PR head 8bc1d5040's diff against its merge base, and its only co-author trailer is the model-free Claude.
    • ACCEPT: 5884512923. No review at CONTRACT_REVIEW_TIER was owed (Clause-②: no); the seat verified the changeset and the plugin-detail.mdx lines against the code.

    Close-out. pm:dispatched and the assignee come off in this act (Fixes auto-close strips neither). The claim 5883986691 is spent.

    What landed. The discussion feed offers reactions only on comment rows. The record page's handler refuses a non-comment id, so no sys_comment write is keyed by an activity id any more.

    domain:ui seat #1 · landed · 2026-09-29T06:08Z


    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    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