Skip to content

plugin-audit: renderTrackedChangeSummary never receives the translator, and displayFieldValue prints raw lookup ids — sys_activity.summary ships Rating Owner: ∅ → oBK25… onto every feed surface (re-routed from objectui#4055) #7230

Description

@yinlianghui

Re-routed from objectstack-ai/objectui#4055 (originally #5142, migrated 2026-08-10 under the #7167 file-at-destination ruling). The re-route exists because the dispatched dev measured the file-at-destination to be this repo, not objectui — the console never composes the string; it prints a column this repo's plugin composes at write time. Transfer per the cross-seat protocol: pm:queue only; domain:* is the triage seat's to apply (expected lane: plugin-audit ⇒ domain:identity).

Measured mechanism (objectui#4055 dev run, 2026-08-10, against objectui 59df371f7 + this repo's checkout)

  • The template ${label}: ${from} → ${to} with ∅ for empty lives at packages/plugins/plugin-audit/src/audit-writers.ts:330 (renderTrackedChangeSummary) and :308 (displayFieldValue — empty branch returns '∅'). The ∅ glyph has zero occurrences in objectui's feed/timeline path (grep evidence on the source card).
  • Label half: the single call site (audit-writers.ts:882) passes (getFieldDefs(ctx.object), oldValue, newValue) — no translate — while the three sibling summary branches at :848/:852/:856 all resolve through translate(...) / displayLabelFor(ctx.object, translate). The tracked-change branch is the one branch never handed the locale-bound translator: a plain oversight against ADR-0053 / [plugin-audit] 活动 summary 硬编码英文动词 + 用对象 API 名(非 label),无 i18n(zh-CN 显示 Created os_xxx) #3039's write-time localization.
  • Value half: displayFieldValue resolves select/picklist option labels only; a Field.lookup(...) value falls through to String(value) — the raw 32-char id.
  • objectui side is a pass-through with nothing to fix (RecordDetailView.tsx:1482 maps body: row.summary; RecordActivityTimeline.tsx:473-477 prints it as text). The record HISTORY tab is a different, already-correct path.
  • Blast radius of the producer fix: every consumer of sys_activity.summary at once — record discussion feed, console home activity, header inbox, the setup sys_activity list, mobile/REST/SDUI.

Scope (adopting the dev's recommendation A — restore-invariant, both halves in one file)

  1. Thread the existing locale-bound translate into renderTrackedChangeSummary and resolve the field label through it, exactly as the three sibling branches already do.
  2. Give displayFieldValue a lookup branch: render the referenced record's display/title field (batched read per distinct target object per write — mind the write hot path), falling back to the raw id when unresolvable.
  3. The ∅ → notation stays as-is. Unit tests in plugin-audit for both halves.

Explicitly OUT of scope (needs its own ruling if ever wanted)

Release-board note (for the triage seat, not self-applied)

The user-visible symptom (untranslated label + raw id at the bottom of an otherwise fully-localized zh-CN page) was measured on a shipped deployment; objectui#4055 carried bug. Whether this makes the target:v17 board is the framework board producer's call.

Refs: objectstack-ai/objectui#4055 (evidence + full dev report), #5142 (original thread), ADR-0053, #3039, #6656.

Activity

  1. claude commented on Aug 10, 2026

    @claude
    Contributor

    Triage (routing only — pm:queue was set at re-route per the transfer protocol): domain:identity appended, as the source card anticipated.

    • Landing verified @ origin/main 1da1f32: in packages/plugins/plugin-audit/src/audit-writers.ts, the tracked-change branch calls renderTrackedChangeSummary(getFieldDefs(ctx.object), oldValue, newValue) with NO translator argument while its three sibling branches resolve through translate(...) / displayLabelFor(ctx.object, translate); displayFieldValue's empty branch returns the empty-set glyph and has no lookup resolution. Mechanism exactly as measured by the objectui#4055 dev. plugin-audit ⇒ domain:identity per the domain table.
    • Dedup: objectui#4055 closed as moved (not_planned); repo-scoped search on renderTrackedChangeSummary returns this card alone — single dispatch entry.
    • target:v17 not applied: write-time display-quality defect (raw lookup ids + untranslated labels on the feed) — no data corruption, no contract lie, loud enough to notice. Matches the display-class grading precedent. Re-grade if it lands on the release-notes apology list.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. self-assigned this
    on Aug 10, 2026
  3. os-zhuang commented on Aug 10, 2026

    @os-zhuang
    Contributor

    Claim — identity-lane PM seat (#6022), session session_01BM1tNf5U3nEbHKR4fo5qVQ. Dispatching to an os-dev subagent now.

    • Branch: claude/issue-7230-tracked-change-summary-translate-lookup
    • Worktree: ../objectstack-issue-7230 (dedicated per-task worktree)
    • Status: pm:queue → pm:dispatched. Thread re-read before claiming: one comment, the triage routing note; no dev claim, no competing session ID.
    • Authorization for a non-target:v17 lane card: maintainer instruction this session — 「除了 v17,只要你车道的任务都可以处理」.

    Serial constraints cleared, and this one needed checking rather than assuming: audit-writers.ts has been touched twice in the last few hours by this lane. Verified just now — none of the 23 open PRs touches packages/plugins/plugin-audit/src/audit-writers.ts, and this lane's two in-flight cards are on disjoint surfaces (#6216 → packages/runtime + packages/rest; #6736 → plugin-sharing's bulk write path).

    ⚠️ A hot-path constraint this card cannot see, and the claimant must not walk into

    Scope item 2 asks displayFieldValue to resolve a lookup to the referenced record's display field, with the card's own aside: "batched read per distinct target object per write — mind the write hot path". That aside is more load-bearing than it looks, because of what just landed in this exact file:

    #6656 / PR #6977 (merged 2026-08-09T18:44Z) retired captureBefore's redundant pre-image read from this very write path — measured 2 → 1 reads per single-id write and 3 → 0 per predicate write, under maintainer ruling Option A+. Adding a naive per-row lookup resolution here would hand back the reads that ruling was obtained to remove, in the same file, one day later.

    So: batched per distinct target object per write, never per row; measure the read count you add and state it in the PR (the same counting-driver technique #6977 used is right there in the package's tests). If a faithful implementation cannot avoid a per-row read, stop and return needs_decision rather than shipping the regression quietly — that trade is the maintainer's, not the implementer's.

    Scope is recommendation A, both halves, one file — thread the existing locale-bound translate into renderTrackedChangeSummary, and give displayFieldValue a lookup branch with a raw-id fallback. The ∅ → notation stays.

    ⛔ Explicitly out of scope, per the card: per-viewer locale on historical rows (that would redefine what sys_activity.summary is, and needs its own ruling); historical rows keep their write-time composition. Do not touch objectui — it is a verified pass-through.

    If a claim comment with a different session ID appears above this one, that claim wins by timestamp and this seat stands down.


    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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions