Skip to content

[finding] The sys_activity row -> FeedItem construction is still written twice, and the console surface drops unmapped types silently #5896

Description

@claude

Observed while implementing #5878 (PR #5894). Not fixed there: that card's fence was the type TABLE, and closing this one changes behaviour rather than removing a duplicate, so it needs its own decision and its own tests.

The duplicate

#5878 converged the sys_activity.type -> FeedItem.type table onto record:activity's exported ACTIVITY_TYPE_TO_FEED_TYPE. The row -> FeedItem construction around it is still written twice:

  • packages/plugin-detail/src/renderers/recordActivityFeed.ts -- activityRowToFeedItem(row, systemActorLabel), plus its helper activityTimestamp(row).
  • packages/app-shell/src/views/RecordDetailView.tsx -- an inline for loop in the sys_activity merge that builds the same object from the same columns.

activityTimestamp's own docblock records the copy explicitly: "Copied from RecordDetailView's merge for the same reason the type map is: same rows, same quirk." That is the same unguarded-mirror shape #5878 was filed for, one level up, and nothing compares the two.

Three ways they already differ

  1. Unknown types are silent here, loud there. The block distinguishes a type the table maps to undefined (a decision, returns quietly) from a type the table does not contain at all (an unmapped producer, warnUnknownActivityType fires once per value). The console merge tests only if (!feedType) continue;, so both collapse into a silent drop. The diagnostic record:activity drops every sys_activity.type: "scheduled" row, and 10 of FeedItemType's 13 kinds have no producer at all #5840 added -- for precisely the failure mode where a producer writes a value nothing maps, and the row vanishes with no signal anywhere -- does not reach the console record page.
  2. Missing-timestamp fallback.activityTimestamp returns String(row.created_at ?? ''); the inline copy assigns when = row.created_at, which can be undefined. Different FeedItem.createdAt for a row with neither column.
  3. The system-actor label. Two independently authored i18n lookups (detail.systemActor here, the block's systemActorLabel argument there) for the same fallback.

Why it was not folded into the #5878 fix

Converging the constructor is not a mechanical deletion. Adopting activityRowToFeedItem changes observable behaviour on the console record page -- it starts emitting a console.warn for unmapped types, and it changes the timestamp fallback -- so it needs its own regression tests and its own read on whether the warning is wanted on that surface. Doing it under #5878's fence would have widened the verification surface of a card whose thesis was "one table, one reading".

Suggested shape

Export activityRowToFeedItem from @object-ui/plugin-detail's entry point (the table is already exported there as of PR #5894, and app-shell already imports from that barrel -- no new dependency edge), have RecordDetailView call it, and pin the convergence with an identity spy rather than a value comparison, as #5878 did. Decide deliberately whether the unknown-type warning should fire on the console surface; it probably should, since that surface is where a shipped producer's rows are most likely to be seen going missing.

Filed unassigned for triage.


Generated by Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpm:queue

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions