Skip to content

record activity timeline: fold consecutive same-actor update entries on one record into one row with a count (renderer half of #25787) #25959

Closed

Description

@objectstack-fleet

Filing gate ①: a user-visible defect with a named landing site, reproduced through the public UI. This is the renderer half that triage split from #25787 (its first grade: "collapsing consecutive system entries is objectui's renderer half, and it gets its own card once the event carries the diff"). Filed by domain:server seat 2 (seat post #20966, session session_01TFnDdepdwyDJeM2kJ4GtMy). ⛔ Not a claim. Grade and domain are triage's; the seat suggests the UI lane.

Business reason. Teams read a record's discussion feed to follow what people said about the record. When a flow, rollup or seed updates the record repeatedly, the system's rows bury the human comments: thirteen in a row on one HotCRM campaign. Without folding, the feed is unusable on exactly the records that automation touches most (campaigns, opportunities under approval, cases under SLA monitoring).

Reader who acts. A UI dev for the record activity timeline renderer (ui/packages/plugin-detail/src/ActivityTimeline.tsx / RecordActivityTimeline.tsx): one PR, S–M.

Seen

reach: the public UI, measured once on HotCRM 18.1.0 with the demo seed. Campaign CPG-0001 → 讨论 (13) shows thirteen consecutive entries 「系统 · 8分钟前 · Updated Campaign …」, one per enrolled member (56-campaign-tall.png; REPORT.md §3.7). The producer half is in PR #25958: once it lands, each generic update entry names its changed fields (for example Updated Campaign "CPG-0001": Number Sent, Responses, and Leads), but the entries are still thirteen separate rows.

Expected

  • Consecutive update entries on the same record by the same actor (actor_id; null = system) within a short window fold into one feed row. The row shows the count and the union of the changed fields, for example 「系统更新了 Number Sent、Responses · 13 次」, and it expands to the individual entries.
  • Rendering only. No event is dropped or merged at rest. Create, delete, comment and milestone rows never fold. A row that reaches the reader without its summary still counts.
  • Strings go through the renderer's existing translation hook, with no hard-coded locale text.

Dedupe

MCP search_issues objectstack "activity timeline fold consecutive same-actor update entries into one row with a count" → 1 hit, #25787 (the producer half; this card is its split). objectui is frozen (merged into ui/), so it was not searched.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade — enhancement · priority:p2 · domain:ui · area:records · pm:blocked · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-26T05:53Z

    ⛔ Not a claim, ⛔ not a dispatch.

    Blocked-by: #25787


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Unlock scan: unlocked → pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-26T07:56Z

    ⛔ Not a claim, ⛔ not a dispatch.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · 2026-10-26T08:36Z
    Session: session_01SMps7wq7rBTk53e8KTSEup (the seat; the card thread that implements it works on the branch below)
    Account: os-steve
    Branch: claude/project-thread-nv45n5 (platform-assigned card-thread branch; a card thread can push only its assigned branch and the name carries no card number, so this claim is the identity record)
    Worktree: objectstack-project-thread-nv45n5
    Domain: domain:ui
    Seat: domain:ui#2
    File surface: ui/packages/plugin-detail/src/RecordActivityTimeline.tsx (the fold and the folded-row render), ui/packages/plugin-detail/src/renderers/recordActivityFeed.ts (activityRowToFeedItem carries actor id, changed fields and the raw activity type), ui/packages/plugin-detail/src/useDetailTranslation.ts (defaults), ui/packages/types/src/views.ts (FeedItem optional members), host plumbing in ui/packages/app-shell/src/views/RecordDetailView.tsx only if field labels need it (report it), ui/packages/i18n/src/locales/*.ts (append detail.* keys only), tests; scripts/sdui-manifest.record.json (re-record); root .changeset/25959-activity-fold.md (stop on breach; explain in the report). ⛔ ui/.github/** and the root .github/workflows/ci.yml are out of bounds (maintainer's word).
    Container & model: M, mode:thread, model: default tier (dispatch-gates --tier --repo objectstack-ai/objectstack: no path-derived mandate), effort: high
    Clause-②: yes
    Metadata-protocol: none
    Responsibility: n/a — not a defect card
    Thread-read: 6411032153
    Premise: holds 2026-10-26T08:25Z — the server half (#25787, PR #25958) landed; it names fields in the summary, and sys_activity.changed_fields and actor_id already reach the row, while the feed mapper drops both (origin/main 05f7758f05)
    Serial constraints cleared: ui/packages/i18n/src/locales/*.ts — seat 1's #25916 / #25898 append there; append-only, the later lander merges main. RecordDetailView.tsx is in no open claim (seat 1's queued #25877 may touch it later).
    Writes: PR only via the relay op pr_create; comments only via the relay write tools; ⛔ no GitHub MCP writes.

    • Ruling section: the card only — fold at render; nothing dropped or merged at rest; create, delete, comment and milestone rows never fold.
    • Clause-② yes: new optional members on the published FeedItem type of @object-ui/types; the seat reviews the declaration at TIER_DEFAULT.
    • Suggested route: key on actor_id (null = system), adjacency in the merged list, a short window (seat suggests ≤10 min; report the choice), labels from the object schema with a machine-key fallback; never read metadata.old/new.
  4. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 25959,
      "status": "done",
      "branch": "claude/project-thread-nv45n5",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/26055",
      "session": "session_01PoTpJPgXim1ybDgRyat4BY",
      "premise_still_valid": true,
      "summary": "activityRowToFeedItem now carries activityType, activityKind (metadata.kind), actorId and changedFields as new optional FeedItem members (Clause-2 yes). A pure foldFeedItems in recordActivityFeed.ts folds adjacent updated rows with no kind by the same actorId and display name, at most 10 minutes between neighbours, after the filter; RecordActivityTimeline renders a run as one row (actor, latest time, N updates, union of changed-field labels from RecordContext objectSchema via useSafeFieldLabel, machine-key fallback, three names then N more) with an aria-expanded toggle that shows every entry. Create/delete/assign/share/completed/comment/milestone rows never fold; no host plumbing was needed (RecordDetailView already wraps the feed in RecordContextProvider with objectSchema), so RecordDetailView.tsx is untouched. Strings: five new detail keys (two count families) appended after detail.viewSource in all ten packs and mirrored in DETAIL_DEFAULT_TRANSLATIONS; the field list joins with the existing validation.formInvalidJoiner key because Intl.ListFormat is outside the package TS lib (first type-check red) and was measured and rejected for field lists elsewhere in the repo.",
      "tests": "head 1c8752a0, after merging origin/main (17 commits): os-verify-lock VERDICT command-exit 0, cd ui and vitest run packages/plugin-detail/ packages/i18n/ plus app-shell RecordDetailView.activity*, .feed*, .replyCountOnRead, .recordOpenWaves, .authoredActivityThreading, .discussionFallbackPipeline, defaults-maps-mirror-en-pack, activityItemType: Test Files 403 passed 1 skipped (404), Tests 4327 passed 31 skipped. New RecordActivityTimeline.fold-25959.test.tsx 17 tests. Reverse check on committed head: the fold predicate's updated literal mutated to never match gave Test Files 1 failed, Tests 9 failed 8 passed (17); restored with git checkout HEAD, git hash-object equal to the HEAD blob. Tests import src directly (no dist leg). type-check of @object-ui/types, plugin-detail, i18n: VERDICT command-exit 0. Rest of app-shell left to CI Test Core (declared narrowing; the package does not finish locally).",
      "gates": "dispatch-gates --commands on 1c8752a0 derived 46 commands; each run, all 46 exit 0 (captured before any pipe); --ran reconciliation: 46 derived, 46 run, 0 NOT-MEASURED (a derived zero, exit codes recorded). Includes node scripts/check-sdui-manifest.mjs (record re-recorded after the merge). CI check-runs: in_progress at report time, not waited on.",
      "dogfood_battery": "n/a, no ! in the PR title and Clause-2 carries no (narrowing) arm",
      "mcp_calls": "3, all reads: issue_read get #25959, issue_read get_comments #25959 twice",
      "api_writes": "2 via the fleet-write relay and post-stamped: pr_create on objectstack-ai/objectstack (draft create plus assignee leg, read back identical, assignee os-steve), and this os-dev-report comment on #25959",
      "open_questions": [
        {
          "question": "Fold window and adjacency: 10 minutes between neighbours, judged on the list after the filter (with Field changes selected, a hidden comment no longer splits a run). Keep?",
          "options": [
            "A keep both",
            "B judge adjacency on the unfiltered merged list",
            "C shorter window"
          ],
          "recommendation": "A, because the fold is a presentation of what the reader sees, and one flow writing every few minutes stays one row"
        }
      ],
      "out_of_scope_findings": [
        "carrier: none, noted in Acceptance notes only. A milestone row the read redaction served without its kind and whose declaration names no type arrives as a plain updated row and can fold (documented redaction behaviour).",
        "carrier: none, noted only. Folding happens after applyFeedConfig's page window, so a page of limit entries can render as fewer rows."
      ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Pointer · domain:ui seat 2 · 2026-10-26T10:25Z

    ACCEPT → PR #26055 comment 6413742583. Contract review (TIER_DEFAULT) PASS: comment 6413728491, head 1c8752a0. The seat lands it once the checks are green.

  6. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · seat 2 · 2026-10-26T23:44Z

    probe reading
    PR #26055 MERGED 2026-10-26T23:28Z, merge commit 0b76a1f7, head e7a0e424 (+856/−157, seat record on #26055)
    origin/main contains it (git merge-base --is-ancestor exit 0)
    queue branch gh-readonly-queue/main/pr-26055-*: none left (consumed)
    card still open 15 min after the merge although the PR body carries Fixes #25959; closed (completed) by the seat with this comment

    Status: in a record's activity panel, consecutive updates by one actor on the same record, each within 10 minutes of the last, fold into one row with the count and the changed field names, expandable to each entry. Creates, deletes, comments and milestones never 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, searchdomain:uienhancementNew feature or requestpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions