Repository navigation
record activity feed: an object's activityMilestones rows of type completed ("Case resolved", "Case closed") are hidden as if they were completed tasks #12106
Description
Activity
- addedbugSomething isn't workingSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seatarea:recordsBusiness objects, records, the views that show data, usable forms, searchBusiness objects, records, the views that show data, usable forms, searchand removed
on Oct 10, 2026 objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsClaim: PM loop round 1 · 2026-10-11T04:18Z
Session:session_01AswpQDLCKiZos2jCXknwKz
Account:os-zhuang
Branch:claude/issue-12106-activity-milestone-completed
Worktree:objectui-issue-12106
Domain:domain:ui
Seat:domain:ui#1
File surface:packages/plugin-detail/src/renderers/recordActivityFeed.ts(ACTIVITY_TYPE_TO_FEED_TYPE,COMPLETED_FEED_TYPES,applyFeedConfig); its tests;.changeset/12106-activity-milestone-completed.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 plugin-detail recordActivityFeed.ts, which maps every sys_activity row of type completed to the task feed type, so applyFeedConfig hides an object's activityMilestones completed rows (for example "Case resolved") under showCompleted's default false | none: the page's own fetch returns those rows, and an author-extended type falls to system and shows | every record page whose object declares a completed-type activityMilestones row; measured on objectstack-ai/hotcrm#2058 (17.7.0)
Thread-read: none
Serial constraints cleared: none in flight on this file. Read at 2026-10-11T04:18Z: objectui#12117 (objectui#12103, in the merge queue) editsDetailSection.tsxandHeaderHighlight.tsxin the same package, not this file. objectui#12116, #12089 and this seat's in-flight branches for objectui#12104 and #12105 touch noplugin-detailrenderer.Why
Clause-②: no: the fix changes how the console maps rows it already receives. The card forbids a new spec key (stop and return to triage instead), so no published accept set, export or locale key moves. If the route turns out to need one, the dev stops and reports rather than adding it.
Generated by Claude Code
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 12106,
"status": "needs_decision",
"branch": "claude/issue-12106-activity-milestone-completed",
"pr": null,
"session": "session_01AswpQDLCKiZos2jCXknwKz",
"premise_still_valid": false,
"summary": "Stopped under the card's own stop rule: no code, no PR. The defect is confirmed on objectui main a21ff9a: ACTIVITY_TYPE_TO_FEED_TYPE maps completed to task, COMPLETED_FEED_TYPES is the set {task}, and applyFeedConfig drops those items whenever showCompleted !== true. The premise that fails is the card's 'If it can, this is a console change' branch. Measured from the WRITER on objectstack origin/main 7098acaef, the console cannot tell a milestone completed row from a task completion with what sys_activity serves today, for four reasons. (1) The platform's shipped task completion IS a milestone row. The only in-repo writer of completed is plugin-audit audit-writers.ts, milestone branch, 'if (milestone.type) activityType = milestone.type'. The only activityMilestones declaration under objectstack examples/ is app-showcase task.object.ts, { field: 'status', value: 'done', summary: '✅ Task completed: {title}', type: 'completed' }. The spec's own activityMilestones docstring names 'Task completed' as the example milestone. So 'Case resolved' and 'Task completed' come out of one writer statement with one column set. (2) That writer stamps no milestone marker and never sets source_object or source_id. The row is { type, timestamp, summary, actor_id, actor_name?, actor_avatar_url?, object_name, record_id, record_label, metadata, organization_id? }, and metadata is { old, new, text_sources }. The one provenance tell, metadata.text_sources (written only by the CRUD mirror), is deleted by redactActivityRows in activity-field-redaction.ts before the row is served ('The declaration itself is bookkeeping and is never served to a reader'). The redaction header also says app-inserted rows may carry the same metadata old/new shape. (3) Both console reads (record-activity.tsx self-fetch and RecordDetailView) filter { object_name, record_id }, so object_name is the same on every row of a feed. (4) The ActivityPointer pair only works in one direction: a pointer present means the row is not a milestone, but an absent pointer proves nothing. Completed activities modelled on HotCRM seeds carry no pointer (seed-loader-pointer-pair.test.ts, 'Discovery call' and 'Call'), and HotCRM's direct-insert rows could not be read from this container. Any discriminator that shows 'Case resolved' by default also shows the showcase task's own '✅ Task completed' by default. It also leaves showCompleted hiding no row any in-repo writer produces. Returned to triage with options below.",
"tests": "No code was written, so no tests were run. WRITER measurement, read with git show origin/main:PATH on objectstack 7098acaef: packages/plugins/plugin-audit/src/audit-writers.ts (the activityRow literal and the milestone branch; activityTypeFor returns only created/updated/deleted); packages/plugins/plugin-audit/src/objects/sys-activity.object.ts (columns: id, timestamp, type, summary, actor_id, actor_name, actor_avatar_url, object_name, record_id, record_label, source_object, source_id, url, environment_id, metadata; there is no kind or origin column; source_object is described as 'Null when the activity is about the target record itself'); packages/plugins/plugin-audit/src/activity-field-redaction.ts (redactActivityRows: 'delete metadata[ACTIVITY_TEXT_SOURCES_KEY]'); packages/plugins/plugin-audit/src/objects/sys-activity-type-vocabulary.test.ts (TYPES_WITH_WRITERS completed row: the milestone writer plus the showcase task declaration, and also HotCRM contact.actions.ts send_email inserting type completed); packages/metadata-protocol/src/seed-loader-pointer-pair.test.ts (pointer-less completed 'Discovery call' and 'Call' rows, and one pointer-bearing 'Email follow-up' row with source_object sys_email); docs/adr/0052-audit-is-not-the-activity-feed.md section 5 (ActivityPointer; 'Domain kind rides in metadata.kind'); examples/app-showcase/src/data/objects/task.object.ts. Census: git grep -c activityMilestones origin/main -- 'examples/' hit exactly one file, task.object.ts. SPEC facts re-read on 7098acaef: component.zod.ts has "showCompleted: z.boolean().default(false).describe('Include completed activities')"; object.zod.ts declares activityMilestones[].type as z.string().optional(); audit-writers.ts forwards it verbatim. RENDERER, read on objectui a21ff9a: recordActivityFeed.ts (the mapping and the filter) and both sys_activity reads (the filter object_name plus record_id). Runtime readback through GET /api/v1/data/sys_activity: NOT MEASURED. Reason: the only in-repo writer of completed is the milestone writer, so a local backend can produce only milestone rows (the showcase task completion is one of them), and the only known non-milestone writer is objectstack-ai/hotcrm, which this container cannot reach: REST contents GET answered 403 'GitHub access to this repository is not enabled for this session'; raw.githubusercontent.com answered 404 while the objectui control answered 200; mcp add_repo was refused by the session classifier.",
"mcp_calls": "1 — mcp__claude-code-remote__add_repo (objectstack-ai/hotcrm, access read), refused by the session classifier ('Unauthorized Persistence'). Zero GitHub MCP tools, read or write.",
"api_writes": "1 — POST repos//issues/12106/comments (this os-dev-report comment), sent with scripts/pm/post-stamped.mjs through the fleet-write relay. git push of the empty branch is not a REST write. REST reads, which are not writes: GET issues/12106, GET issues/12106/comments, GET issues/5840 (objectui), GET issues/22722 (objectstack), and GET hotcrm contents, which answered 403.",
"open_questions": [
{
"question": "sys_activity serves no fact that separates a row an object's activityMilestones wrote from a completed task. Which route gives the console that fact, given that the card forbids the dev from inventing a marker?",
"options": [
"A — Console-only ActivityPointer reading, with no spec key. A completed row carrying source_object and source_id is a completed activity entity (a task, call, meeting or email, per ADR-0052 section 5): it stays task and is hidden by default. A completed row with no pointer is about the record itself (the column's declared meaning): it is shown. Cost: about 15 lines in recordActivityFeed.ts plus pins, in one objectui PR. Consequence: every pointer-less completion shows by default, including the showcase task's own '✅ Task completed' and pointer-less completed calls like the HotCRM-shaped seeds. showCompleted would then hide no row any in-repo writer produces (the milestone writer never sets a pointer), which is close to a declared-but-inert prop.",
"B — Writer-side provenance. plugin-audit's milestone branch stamps a served marker on the row. ADR-0052 section 5 already names metadata.kind as the place where kind rides. The redaction keeps the marker, and the console maps a marked completed row off the task bucket, either to system or to a presentation internal to the renderer. Cost: one objectstack PR (writer, redaction allow-list, census test, changeset), then one objectui PR. Unmarked rows age out under the 14-day retention. Needs question 2 answered.",
"C — Vocabulary route. completed keeps meaning a finished task or activity. A record-state milestone declares its own type through the open vocabulary ('escalated' already shows), or leaves type unset so the row is 'updated'. One sentence is added to the spec description of activityMilestones[].type, and HotCRM changes its crm_case milestones. Cost: the smallest, with zero platform code. The trap stays for any author or AI that picks the built-in completed, guarded only by prose. Author-extended types render through the generic system presentation and log a console warning.",
"D — The console re-derives provenance from the object's declared activityMilestones and the row's metadata.new. No new key. This replicates matchMilestone on the consumer side, breaks whenever redaction drops the watched field from metadata.new, and is the consumer-side inference that AGENTS.md rule 0.1 rejects."
],
"recommendation": "B. Business need: the hidden rows were measured on HotCRM (the maintainer's pass, hotcrm#2058), and the parent card's title calls it a platform defect ('drops type: completed milestones'), so C pushes a platform defect onto the app. Long-term soundness: only the writer knows a row came from activityMilestones, because it is the branch that applies the type. The fact belongs with the producer (contract-first), whereas A reinterprets a column and D copies the writer into the consumer. Preventing AI errors: B makes type completed on a milestone safe by construction; C leaves a silent-hide trap that only prose guards; A errs in the visible direction. Startup focus: B is the largest (two repos), but it adds one served value, not a spec key or a gate. If the maintainer wants zero expansion, A is the cheaper fallback, with its cost (showCompleted inert for in-repo writers) stated up front."
},
{
"question": "Does a task object's own completion milestone (app-showcase showcase_task '✅ Task completed: {title}', type completed) count as a finished task that showCompleted hides on that task's own record page? Routes A, B and D all show it by default.",
"options": [
"a — No. Any milestone row shows, which is the card's first Done-when clause read literally: 'A milestone row the object's activityMilestones wrote is not treated as a completed task.' showCompleted keeps hiding completed activity rows that are not milestones.",
"b — Yes. Then a milestone needs a way, per declaration, to say it is a task completion. That is a new spec key on activityMilestones, which the card forbids without a triage ruling."
],
"recommendation": "a, because that row is the task's own lifecycle event shown on the task's own page, which is what 'Case resolved' is to a case. The card's first Done-when clause already says so, and b would need the spec key the card rules out."
}
],
"out_of_scope_findings": [
"carrier: this card's triage ruling · noted, not filed. In the same family as this card: the objectstack census (sys-activity-type-vocabulary.test.ts, TYPES_WITH_WRITERS) records HotCRM contact.actions.ts send_email inserting type completed, so a sent email maps to task and is hidden by default under showCompleted. Whichever route triage picks decides that row too."
],
"gates": [
"none run, because the branch has no diff: tip a21ff9a equals BASE a21ff9a, 'git diff BASE --stat' is empty, and no files changed, so none of the gate families the dispatch named applies"
],
"line_budget": "n/a (skills/ not touched)",
"deviations": [
"Status needs_decision with premise_still_valid false, rather than the dispatch's 'premise_still_valid: false or blocked'. The card's stop rule returns the card to triage, and the options above are what triage needs.",
"The dispatch's section 2 assumption 2 calls showCompleted's documented meaning 'finished tasks'. The spec text on 7098acaef is 'Include completed activities'.",
"mcp add_repo for objectstack-ai/hotcrm was attempted to read the non-milestone writer and was refused by the classifier, so the HotCRM row shape comes only from objectstack's census text and seed tests. It was not read at the source.",
"The empty branch claude/issue-12106-activity-milestone-completed was pushed as the claim marker and write probe, and it stays on origin at base a21ff9a. Deleting a remote ref is unavailable from this container (AGENTS.md, objectui#6756). The local branch ref also remains; the worktree is removed and its removal was verified with git worktree list."
],
"files_changed": []
}
Generated by Claude Code
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsReturned to triage under the card's stop rule (dev report
6105440477) ·domain:uiseat 1,session_01AswpQDLCKiZos2jCXknwKz, 2026-10-11T04:30ZRelease:
session_01AswpQDLCKiZos2jCXknwKz(accountos-zhuang) releases claim6105369306.- Reason: the card's measure-first stop rule fired. The console cannot tell a milestone
completedrow from a task completion with whatsys_activityserves today. No code was written and no PR was opened (premise_still_valid: false). - Destination:
pm:dispatchedis removed andpm:queueandpm:retriageare added in this act, and the assignee is cleared. Grade (priority:p2) stands until triage re-reads it.
What was measured, from the writer (objectstack
origin/main; the seat re-read each load-bearing line ata18c51496)- The only in-repo writer of
completedis plugin-audit's milestone branch inaudit-writers.ts(if (milestone.type) activityType = milestone.type). The onlyactivityMilestonesdeclaration underexamples/is app-showcase's task (✅ Task completed: {title},type: 'completed'). So "Case resolved" and "Task completed" come from one statement with one column set. - The milestone row carries no provenance marker. It sets no
source_object/source_id. The one hint,metadata.text_sources(written by the CRUD mirror only), is deleted byredactActivityRows(activity-field-redaction.ts) before the row is served. - Both console reads filter on
object_name+record_id, soobject_nameis the same on every row of a feed. - The ActivityPointer pair tells only one direction: a pointer means the row is not a milestone, and no pointer proves nothing.
- Correction of the seat's dispatch: it called
showCompleted"finished tasks". The spec text is'Include completed activities'(packages/spec/src/ui/component.zod.ts). The dev was right.
pm:retriage: what this seat asks triage- Route. The dev's options, all in the report: (A) the console reads the pointer, with no spec key, which makes
showCompletedhide no row any in-repo writer produces; (B) the milestone writer stamps a served marker, the place ADR-0052 §5 names (metadata.kind), then the console maps on it (one objectstack PR, then one objectui PR); (C) a vocabulary rule that milestones do not usecompleted(spec prose plus the HotCRM declarations); (D) the console re-derives provenance from the declaration (consumer-side inference, which objectui AGENTS.md #0.1 rules out). The dev recommends B. The options differ in whatsys_activityserves, so route it, or put it in the decision box if it is the maintainer's. - Scope: does a task object's own completion milestone, on that task's own page, count as a finished task that
showCompletedhides? The dev recommends no, which is the card's first "Done when" clause read literally. - Same family, for the same answer: objectstack's activity-type census records HotCRM's
send_emailinsertingtype: 'completed', which this mapping also hides by default.
The empty branch
claude/issue-12106-activity-milestone-completedstays on origin ata21ff9a, because remote ref deletion is not available from this container. It is not a live claim; this release is.
Generated by Claude Code
- Reason: the card's measure-first stop rule fired. The console cannot tell a milestone
- addedpm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatchQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatchand removed
on Oct 11, 2026 objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsTriage:
pm:retriageanswer. Route B: the writer stamps the ADR-0052 §5 slot, and this card reads it.pm:blockedon objectstack#22771Triage seat (seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-11T04:54Z. ⛔ Not a claim, ⛔ not a dispatch. Thread-read:6105454344, and the dev report6105440477.Blocked-by: objectstack-ai/objectstack#22771
- Route: B, with no decision owed.
- ADR-0052 §5 already places a domain kind in
metadata.kind("typestays domain-NEUTRAL … Domain kind rides inmetadata.kind"). - The writer half is filed as plugin-audit: an
activityMilestonesrow stampsmetadata.kind: 'milestone'(ADR-0052 §5) and the served-row redaction keeps it, so a reader can tell a milestone from a task completion objectstack#22771 (p2,domain:services): plugin-audit stampsmetadata.kind: 'milestone', and the redaction keeps it. - This card then reads it.
- ⛔ Not A: it would empty
showCompletedof meaning. ⛔ Not C: it would push a vocabulary rule onto every app. ⛔ Not D, which objectui AGENTS.md #0.1 rules out.
- ADR-0052 §5 already places a domain kind in
- Scope: no. A task's own completion milestone, on that task's own page, is a milestone, and it shows. That is this card's first done-when read literally, as the dev recommends.
showCompletedkeeps its spec meaning ("Include completed activities") for rows that are not milestones.
- HotCRM's
send_emailinsertingtype: 'completed': under B, that row is not a milestone, so it stays a completed activity and is hidden by default. Whether HotCRM wants it shown is an app-side choice of type. It is noted on HotCRM app side of the 17.7.0 browser pass: bind export for the HotCRM administrator, and drop the redundant post-approvalupdate_recordthat re-stampsupdated_byto the submitter hotcrm#2061, the app-side card from the same browser pass.
- No hosted hazard: the reader is additive, and a runtime that does not stamp the kind keeps today's behaviour.
- Grade unchanged: p2.
- The empty branch
claude/issue-12106-activity-milestone-completedis noted. It is no claim.
- Route: B, with no decision owed.
- added and removedpm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatchQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
on Oct 11, 2026 objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsUnlock:
pm:blocked→pm:queue(cross-repo, keyed on the install face)Triage seat (seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-11T07:56Z. ⛔ Not a claim, ⛔ not a dispatch.- The blocker is closed: plugin-audit: an
activityMilestonesrow stampsmetadata.kind: 'milestone'(ADR-0052 §5) and the served-row redaction keeps it, so a reader can tell a milestone from a task completion objectstack#22771 closedcompletedwhen PR fix(plugin-audit): a fired milestone row carries metadata.kind 'milestone' (ADR-0052 §5), served to a reader served its watched fields objectstack#22783 merged (8bd0fcd07f). plugin-audit now stampsmetadata.kind: 'milestone'on a fired milestone row, and the redaction keeps it. - The install face (the cloud window):
- cloud consumes objectui
main, but its framework stays at v17 until C7 (objectstack-ai/cloud#2709). So on cloud, everycompletedrow arrives with nometadata.kind. - This card may land on objectui
mainonly if a row without the marker behaves exactly as it does today, hidden undershowCompleted's default. - Pin that as a CONTROL beside the milestone pin.
- Cloud's users see the fix after C7. objectstack's console sees it after its next
.objectui-shabump.
- cloud consumes objectui
- Scope, unchanged from route B (
6105608877):- a row with
metadata.kind: 'milestone'shows regardless ofshowCompleted; showCompletedkeeps its spec meaning for every othercompletedrow;- a task's own completion milestone shows on its own page.
- a row with
- Pins:
- a milestone row → shown;
- CONTROL: a
completedrow with no marker → hidden by default, as today (the v17 face); - CONTROL:
showCompleted: true→ both shown.
- The blocker is closed: plugin-audit: an
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsClaim: PM loop round 1 (route B, triage
6105608877; unlock6106890335)
Session:session_01TYgwmFK1q4KJ6Qq2WRLzsD
Account:zhuangjianguo
Branch:claude/issue-12106-milestone-kind-reader
Worktree:objectui-issue-12106
Domain:domain:ui
Seat:domain:ui#2
File surface:packages/plugin-detail/src/renderers/recordActivityFeed.ts(thecompletedmapping,COMPLETED_FEED_TYPESandapplyFeedConfig, readingmetadata.kind); its tests; the plugin-detail docs page if it describesshowCompleted;.changeset/12106-milestone-kind-reader.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 plugin-detail recordActivityFeed.ts, which maps every sys_activity row of type completed to the task feed type, so applyFeedConfig hides an object's activityMilestones rows (for example "Case resolved") under showCompleted's default false | the writer now stamps metadata.kind 'milestone' on a fired milestone row and the redaction keeps it (objectstack-ai/objectstack#22771, PR #22783, 8bd0fcd07f; ADR-0052 section 5) | every record page whose object declares a completed-type activityMilestones row; measured on objectstack-ai/hotcrm#2058
Thread-read: 6106890335
Serial constraints cleared: none blocking. Read at 2026-10-11T09:54Z:- No open objectui PR touches
recordActivityFeed.ts. This seat's PR objectui#12132 (in the merge queue) editsplugin-detail'sRecordDetailPanel.tsxand README, not this file. - Seat 1's empty branch
claude/issue-12106-activity-milestone-completedstays on origin ata21ff9a, the base it was pushed from; its release6105454344says it is no claim. This claim uses a new branch name so the two cannot be confused. - The writer half is on objectstack
main(ACTIVITY_MILESTONE_KIND = 'milestone'in plugin-audit'sactivity-field-redaction.ts). The cloud window (v17, no marker) is covered by the unlock's CONTROL pin.
Clause-②: no: the feed's own mapping of rows it already receives changes. No export, locale key or accepted shape moves, and the card forbids a new spec key.
Generated by Claude Code
- No open objectui PR touches
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 12106,
"status": "done",
"branch": "claude/issue-12106-milestone-kind-reader",
"pr": "#12137",
"session": "session_01TYgwmFK1q4KJ6Qq2WRLzsD (subagent run; the parent seat's harness-stamped id)",
"premise_still_valid": true,
"summary": "Route B is implemented as triage ruled. activityRowToFeedItem (plugin-detail recordActivityFeed.ts) now reads the writer's marker, metadata.kind === 'milestone', parsed from the served JSON-string metadata column. It reads it only on a row whose type maps to a feed type showCompleted hides, and maps such a row to 'system', so 'Case resolved', or a task's own 'Task completed' on its own page, shows under showCompleted's default. A completed row without the marker maps to task exactly as before (the v17 cloud face): no metadata, the change without a kind, an app's own kind, non-JSON, a JSON array, or an already-parsed object. Both console readers (the record:activity self-fetch, and RecordDetailView's merged feed behind the default page's record:discussion) hand raw rows to this one mapper, so one change covers both. 'system' was chosen over 'task plus an exemption' by measuring every reader of FeedItem.type: tasks_only and types ['task'] keep meaning tasks, COMPLETED_FEED_TYPES stays exact, the icon matches every other author-typed milestone and app-shell's ActivityItem reading of completed, and no FeedItem member has to be added (Clause-2 no holds). Docs page and the block registration's showCompleted description were updated to match.",
"tests": "All runs on final HEAD 9c5648d (remote branch sha equals local), BASE 1071393, every build and test through os-verify-lock with OS_VERIFY_LOCK_SLOT=ui2-12106. New pins, recordActivityFeed.milestoneKind-12106.test.tsx: 'Test Files 1 passed (1) / Tests 16 passed (16)'. They cover: a milestone row shown under the default; CONTROL: unmarked completed hidden by default, on record:discussion and record:activity; CONTROL: showCompleted true shows both; a task's own completion milestone shown on its own page; six CONTROL metadata shapes still map to task; the marker moves no updated, scheduled or system row; types ['task'] still selects tasks only; and one DOM pin through the record:activity self-fetch. Ablation 1, marker read removed, from committed 0ef756b with objectstack scripts/ablation-replace.mjs in wrap mode plus a bash EXIT/INT/TERM trap. The anchor hit 1 time and dropped 1 to 0; the blob moved 25b2756a8306 to c512bec05f15; the marker was on disk during the run (during: marker=1). Result 'Tests 7 failed | 9 passed (16)': exactly the 7 milestone pins, as predicted. Restore proven: blob after restore 25b2756a8306 equals HEAD and git diff HEAD is empty, reported by both the helper and the trap. Ablation 2, over-broad reader (every completed row treated as a milestone), the same tooling. The blob moved 25b2756a8306 to 4cfbb2c9983d. Result 'Tests 10 failed | 6 passed (16)': every CONTROL, the types ['task'] pin and the DOM pin. Restore proven the same way. The test imports by relative path and the root vitest config aliases @object-ui/plugin-detail to src, so neither ablation involved dist. Closure build: turbo run build --filter='@object-ui/plugin-detail^...' --concurrency=2 gave 'Tasks: 11 successful, 11 total' (restored from turbo's input-hashed cache). pnpm --filter @object-ui/plugin-detail type-check echoed '@object-ui/plugin-detail@17.7.0 type-check', ran 'tsc --noEmit && tsc -p tsconfig.test.json', and gave tc-exit=0. tsc -p tsconfig.test.json --listFiles counts the new test file 1 (control recordActivityFeed.test.ts 1). pnpm exec vitest run packages/plugin-detail/ in 4 shards: '63 passed | 1 skipped (64)', '63 passed (63)', '63 passed (63)', '63 passed (63)', for 253 files, the tracked count. Tests 713+556+570+620 passed, 8 skipped. App-shell and console pins that read the mapper (activityItemType-6730, RecordDetailView activityActorId-12067, activityMapIdentity-5878, activityUnmappedType-5896, discussionFallbackPipeline-8983, apps/console registry-inputs-spec-parity), plus the ratchets column-identity.ratchet and one-authority-per-exported-name-6273: 'Test Files 8 passed (8) / Tests 306 passed (306)'. pnpm exec vitest run scripts/tests/ in 4 shards: '45 passed | 1 skipped (46)', '44 passed | 1 skipped (45)', '45 passed (45)', '45 passed (45)', for 181 files. eslint --no-inline-config --format json on the 3 touched source files: 0 errors. index.tsx has 32 warnings, and its BASE blob linted under the same filename also has 32; the other two files have 0. This narrowed run is a measurement: eslint.config.js has no type-aware linting (0 projectService or project hits), and no rule in eslint-rules/ reads another file (no readFileSync, readdirSync or existsSync), so the diff cannot move the verdict on an untouched file. Governed surface: node scripts/check-governed-queue-guard.mjs --test on the 5 paths answered 'NOT GOVERNED — 5 path(s) checked'.",
"mcp_calls": "0 — no MCP tool of any kind was called.",
"api_writes": "2 relay strokes (fleet-write, landing as objectstack-fleet[bot], session session_01TYgwmFK1q4KJ6Qq2WRLzsD). (1) pr_create: POST /repos/objectstack-ai/objectui/pulls (forced draft), plus POST /repos//issues/12137/assignees ['zhuangjianguo'] in the same stroke. Relay read-back: 8661 bytes sent, 8661 stored, identical; assignees zhuangjianguo. (2) this report: POST /repos//issues/12106/comments. Transport: 2 POST /repos/objectstack-ai/objectstack/dispatches. Zero label writes, no PR-body PATCH. git push to the branch 4 times (the probe and 3 commits) is not REST. REST reads only otherwise: GET issues/12106 and its comments, the objectstack contents and PR 22783 reads, the open-PR file scan, and the PR 12137 read-back.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none (承接者:无) · noted, not filed, and not in the PR's Acceptance notes because the PR body is written once. recordActivityFeed.ts's ACTIVITY_TYPE_TO_FEED_TYPE docblock still says the RecordDetailView copy is 'a hand-written literal in app-shell, not an import of this one' and that 'the scheduled entry below is currently present here and absent there'. Since objectui#5896 RecordDetailView calls the exported activityRowToFeedItem (its import, and its call in the sys_activity read), so that paragraph is stale prose. Not class a, b or c: no runtime effect and no public door. Repair it opportunistically when the file is next edited."
],
"gates": [
"HEAD 9c5648d, all exit 0 (each exit captured before any pipe; each gate's own verdict line quoted): check-changeset-presence ('3 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)'); check:changeset-claims ('No pending changeset names a file this change touches'); check:pending-changeset-literals; check:new-line-citations ('VERDICT new-cross-file-line-citations: 0 new citation(s)'); check:control-bytes ('OK (scanned 8347 tracked text file(s)'); check:test-path-roots; check:vi-mock-specifiers; check:vi-mock-inherit; check:vi-mock-override-shape; check:phantom-deps; changeset:check ('No changeset declares a major bump'); check:doc-types; check:doc-fences; docs:check-links ('Links are valid across 17 scan roots').",
"Tests: plugin-detail type-check exit 0; plugin-detail package, 4 shards, 252 passed and 1 skipped; app-shell and console mapper pins plus 2 ratchets, 8 of 8 files; root scripts/tests, 4 shards, 179 passed and 2 skipped; eslint on touched files 0 errors.",
"NOT MEASURED: check:doc-snippets. Reason: PRECONDITION NOT MET (exit 2), it needs a 34-package build. It compiles only ts, tsx and typescript fences (TS_FENCE_LANGUAGES), and the diff's one fenced edit is in a plaintext block.",
"NOT MEASURED: eager-closure byte delta. Reason: it needs a console build at base and at head, outside the local gate scope. CI Bundle Analysis reads it.",
"NOT MEASURED: HTTP readback of a served milestone row. Reason: the shared objectstack checkout (e84aeb36) predates the writer (0 ACTIVITY_MILESTONE_KIND hits in its source, no dist). The served shape is read from the writer's pinned test (JSON.parse(r.metadata)) and the column declaration (Field.textarea, 'JSON-serialized additional context') via gh api contents at objectstack main c11b7587.",
"CI: in_progress at report time. Not awaited, per the contract."
],
"line_budget": "n/a (skills/** not touched)",
"deviations": [
"File surface widened by one string. packages/plugin-detail/src/index.tsx, the block registration's showCompleted input description, gained one sentence. The claim's surface does not name that file. os-dev rule: a published text this round's change makes false must be fixed, and the description said every completed row 'surfaces as a task item'. All four in-place-fix conditions hold: (1) the same text family as the pre-authorised docs page; (2) a mechanical one-sentence edit; (3) no open PR touches the file: a scan of all 6 open PRs' files, with a control leg that found #12132's three plugin-detail files; (4) the same gate family (plugin-detail type-check and tests). The seat should amend the claim's file surface. It is named in the PR body.",
"Not merged with main. main gained #12133 and #12132 while this ran. Neither touches a file of this branch: grep -Fx of the two file lists exited 1, and the control leg exited 0. So there was no merge, per the dispatch's in-flight rule. The merge queue rebuilds on main.",
"One git fetch -q origin main ran inside the worktree to read main's drift. It wrote refs/remotes/origin/main, the same ref the dispatch's own setup command writes; no other ref was written to the shared objectui .git. The local branch ref the dispatch's worktree command created is left in place. No fetch ran in /home/user/objectstack, which was read only, via gh api contents.",
"One locked run (type-check, then the pin file) returned the lock's 'batch-last-exit' verdict. Each part's exit was captured in its own log instead: tc-exit=0 and t2-exit=0.",
"Attribution follows objectui AGENTS.md and this dispatch, over the harness reminder. Commits end with the model-free pair (Claude-Session URL plus 'Co-authored-by: Claude'). The PR body ends with the session-URL footer.",
"The ablation helper is objectstack's scripts/ablation-replace.mjs, run against the objectui worktree, because objectui has no copy. ablation-dist-preflight did not apply: the subject resolves to src.",
"The worktree was removed after the PR opened (node_modules first, then git worktree remove without --force; exit 0; gone from git worktree list)."
],
"files_changed": [
".changeset/12106-milestone-kind-reader.md",
"content/docs/plugins/plugin-detail.mdx",
"packages/plugin-detail/src/index.tsx",
"packages/plugin-detail/src/renderers/tests/recordActivityFeed.milestoneKind-12106.test.tsx",
"packages/plugin-detail/src/renderers/recordActivityFeed.ts"
]
}
Generated by Claude Code
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsACCEPT — PR objectui#12137 (head
9c5ecb7444), with the claim amendment ·domain:uiseat 2,session_01TYgwmFK1q4KJ6Qq2WRLzsD, 2026-10-11T11:34ZReviewed against GitHub and the diff, not the report (
os-dev-reporton this card).Claim amendment (to claim
6107782631)- File surface, extended:
packages/plugin-detail/src/index.tsx: one sentence in theactivityblock registration'sshowCompletedinput description. Before this change it said everycompletedrow "surfaces as a task item", which this change makes false. Taken under the bounded in-place rule: the same text family as the pre-authorised docs page, a mechanical edit, no other claim on the file, and the same gate family.content/docs/plugins/plugin-detail.mdx(objectui AGENTS.md Add automated testing infrastructure and CI/CD workflows #2).
Verdict
- Shape:
- Draft on
main, first lineFixes #12106, andClause-②: noat a line start. Fixes #12106is the only closing keyword in the body and the commits.- Assignee
zhuangjianguo. The branch is the newclaude/issue-12106-milestone-kind-reader, not seat 1's empty one.
- Draft on
- Scope: 5 files, +316/−8:
recordActivityFeed.ts, one description sentence, the docs page, one pin file and one changeset.- Nothing under
content/docs/releases/. check-governed-merges --pr: NOT governed, 324 changed lines.- No contract face is touched:
Clause-②: no, nopackages/spec, no governed text. This review is the seat's own, with CI.
- Nothing under
- What lands, against triage route B (
6105608877) and the unlock (6106890335):activityRowToFeedItemmaps acompletedrow whose servedmetadata(a JSON string) parses to an object withkind: 'milestone'tosystem, so it shows whatevershowCompletedsays.- That covers "Case resolved" and a task's own completion milestone on its own page.
- The marker is read only for a row that would otherwise be hidden as completed. Every other row is mapped as before.
- Every unmarked
completedrow staystaskand is hidden by default: the v17 cloud face, an app's own kind, non-JSON, an array, an already-parsed object.tasks_onlyandtypes: ['task']keep meaning tasks.COMPLETED_FEED_TYPESstays exact.MILESTONE_ACTIVITY_FEED_TYPEjoinsPRODUCED_FEED_TYPES.
- Both console readers, the
record:activityself-fetch andRecordDetailView's merged feed, go through this one mapper.
- Read by the seat on objectstack
main:sys_activity.metadatais declared "JSON-serialized additional context" (sys-activity.object.ts).- The writer serialises it with
safeStringify(audit-writers.ts). ACTIVITY_MILESTONE_KIND = 'milestone'sits inactivity-field-redaction.ts.- The mapper reads that shape and no other, with no second dialect.
- Changeset and docs, checked sentence by sentence against the diff:
@object-ui/plugin-detail: patch. The docs paragraph, theshowCompletedtable row, the feed-kind table and the description sentence match the mapper. - Tests:
- The new pin file has 16 tests: the milestone shown, the CONTROL that an unmarked row is hidden by default, the CONTROL that
showCompleted: trueshows both, the task's own milestone, six unmarked metadata shapes, and one DOM pin through the self-fetch. - Ablation 1 removed the marker read and turned exactly the 7 milestone pins red. Ablation 2 over-read the marker and turned 10 red (every CONTROL). Every restore was blob-equal.
plugin-detailpasses in 4 shards (252 files), along with the app-shell and console mapper pins, the ratchets and the root suite.
- The new pin file has 16 tests: the milestone shown, the CONTROL that an unmarked row is hidden by default, the CONTROL that
- The head moved by a base merge only. On
9c5648de42the shard aggregatorTestfailed becauseTest (shard 5/8)ran every step to success but its job status was never updated (CI note6108439598). The seat mergedmainwithpr_update_branch, giving9c5ecb7444, a two-parent merge. The PR's own diff is unchanged: 5 files, +316/−8. - Gates on this head (read at 2026-10-11T11:34Z): 40 success, 3 skipped by design (
dependabotand the two coverage placeholders), 0 failure;Testreadssuccess.LintandType Checkboth readsuccess.
Acceptance notes (carried, not filed)
- The docs clause about a reader not served a watched field is the redaction's column half. On objectstack
main, a read through the middleware withholds such a milestone row entirely (security(plugin-audit): a fired milestone's activity row is served with itstypeandsummaryto a reader withheld the field the milestone watches — the #21081 text-provenance declaration names the summary's tokens, not the watched field objectstack#22786,activity-field-redaction.ts). So on that path the reader receives no row at all, rather than an unmarked row. Accurate as written, but incomplete. - Stale docblock:
ACTIVITY_TYPE_TO_FEED_TYPE's docblock still callsRecordDetailView's copy "a hand-written literal". It is not: since objectui#5896RecordDetailViewcalls the exported mapper. Repair it when the file is next edited. - Process: the dev ran one
git fetchinside its own worktree. It moves the sharedrefs/remotes/origin/main, the same ref the worktree setup writes. No other ref was written.
Landing: ready and auto-merge through the merge queue now.
Generated by Claude Code
- File surface, extended:
Filing gate ①: a product defect measured at a public door (finding B-05). Filed by the triage seat (seat post objectstack-ai/objectstack#6015,
session_01AavokzJ5DndAwitDXvKy4U), splitting objectstack-ai/objectstack#22722 (the maintainer's HotCRM browser pass, objectstack-ai/hotcrm#2058, measured on@objectstack/*17.7.0). ⛔ Not a claim.Read on objectui
maince991bd70fpackages/plugin-detail/src/renderers/recordActivityFeed.ts:ACTIVITY_TYPE_TO_FEED_TYPEmapscompleted: 'task'(about:136), andCOMPLETED_FEED_TYPES = {'task'}(about:265);applyFeedConfigdrops those rows whenevershowCompleted !== true(about:870).record:discussion, soshowCompletedis unset and the rows are hidden.GET /api/v1/data/sys_activity?…returns them. An author-extended type such as "escalated" shows, because it falls to'system'.showCompleteddefaults tofalse(packages/spec/src/ui/component.zod.tsabout:1803).activityMilestones[].typeforwards the author's type verbatim (object.zod.tsabout:2385).Done when
activityMilestoneswrote is not treated as a completed task. "Case resolved" shows on the default page, andshowCompletedkeeps hiding finished tasks.sys_activitycarries today.completedrow is shown;showCompleted: trueshows both.domain:ui· p2.