fix(spec): record:activity's props row names items / loading as the host feed slot - #21422
Conversation
… host feed slot `RecordActivityProps` gains a `guidance` table in the shape `RecordHistoryProps` already uses for `entries` / `loading`: the renderer takes both keys as a host-supplied feed, so an authored one is a static snapshot (or a loading state pinned on). Both keys stay refused; only the unrecognized-key message now names them and the omit remedy. Claude-Session: https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d Co-authored-by: Claude <noreply@anthropic.com>
…essage Claude-Session: https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 138 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 2feb3d8acd4c7482975006a69c78b3c0e9063311 && git checkout 2feb3d8acd4c7482975006a69c78b3c0e9063311
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 39a912ea73ddff7fc85ebb3379a8ce74ff1343f5 ab581575851de1a92859746a04c1721f27b878e1 && git checkout -B drift-repro 39a912ea73ddff7fc85ebb3379a8ce74ff1343f5 && git merge --no-ff ab581575851de1a92859746a04c1721f27b878e1
node scripts/docs-audit/affected-docs.mjs --json 39a912ea73ddff7fc85ebb3379a8ce74ff1343f5 |
Contract reviewServed-tier: Inputs: card #21279 (body; comments ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: FAIL |
…ey on both mounts `RecordChatterProps.feed` IS `RecordActivityProps`, so the `items` / `loading` guidance also answers `record:chatter` / `record:discussion` `feed.items` / `feed.loading`, where the chatter renderer reads neither key and never self-fetches. Each bullet now states the standalone `record:activity` facts (host data channel / host fetch state) and that nothing reads the key on a chatter or discussion `feed`; the remedy stays "Omit it", with the `sys_activity` self-fetch scoped to the standalone block. The chatter pin asserts that mount-true clause on both names. Shape and accept set unchanged; the changeset sentence is corrected. Claude-Session: https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Inputs: card #21279 (body; comments ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
Fixes #21279
Clause-②: no
What changes
RecordActivityProps, theComponentPropsMap['record:activity']row, gains aguidancetable that namesitemsandloading. It uses the shapeRecordHistoryPropsalready uses forentries/loading. Both keys stay undeclared, so the accept set does not change. Only the unrecognized-key refusal changes: it now names them as the host's feed slot and gives the remedy, which is to omit them. No new vocabulary.packages/spec/src/ui/component.zod.ts: the twoguidanceentries, plus a docblock that records the renderer read points and therecord:chatterfeedmount.packages/spec/src/ui/component-record-blocks.test.ts: pins through the row (below)..changeset/21279-record-activity-host-feed-guidance.md: an@objectstack/specpatch, message text only.Premise check (base
4b20c84748)RecordActivityPropswasstrictObject({ surface, history, guidanceSets }, …)with noguidancemap. Measured through the row:{ items: [ … ] }gave oneunrecognized_keysissue at path[]with keys["items"], and the message was only the generic line: "Unrecognized key(s) on thisrecord:activity:items. Until this shape was closed, …".loadinggot the same, and so didfeed.itemsonrecord:chatter..objectui-shapin89cad75d5570,packages/plugin-detail/src/renderers/record-activity.tsx::161-166readsitemsas an array, off the node or out of thepropertiesbag, andloadingasschema.loading ?? bag.loading.:265-266the hostloadingwins over the discussion context's flag and over the self-fetch state.check:docs(insidecheck:generated) is green with nothing regenerated. The reference page does not liftguidancetext, so no generated file is in this diff.After
Through
ComponentPropsMap['record:activity'], the same singleunrecognized_keysissue (keys["items"]) now carries:loading: "loadingis not authorable surface. On a standalonerecord:activityit is the host's fetch state for itsitemsfeed and wins over the block's other loading sources, so authoredtruepins the loading state on forever. On arecord:chatter/record:discussionfeednothing reads it. Omit it withitems: each block takes its loading state from its own source."(Round 2, head
ab58157585: both bullets now state who reads the key on each mount, because the table is one flat map shared by the standalone row and therecord:chatter/record:discussionfeed. See Review round 1.)Control: the
record:historyentries/loadingmessages are byte-identical before and after. I captured both probe outputs and the diff of the history section exits 0.Pins (
component-record-blocks.test.ts, through the row)items(a feed and[]) andloading(trueandfalse) are each refused with exactly one issue:codeunrecognized_keys,path[],keysthe one key. The refusal is about the key, not a value domain.itemsmessage carries the named first sentence, the omit remedy andsys_activity. Theloadingmessage carries its named first sentence and "Omit it withitems".record:chatterandrecord:discussionfeed, which is the same object, still refuses both keys at path["feed"], and (round 2) the message carries the mount-true clause "On arecord:chatter/record:discussionfeednothing reads it." with "Omit it".record:historypins are the unchanged control.Reverse verification (ablation)
The ablation ran from the committed fix, with
node scripts/ablation-replace.mjswrapping the run. It changed the literal anchorguidance: {+items: 'itemsis the HOSTtoguidanceAblated: {…, which disables the whole table.a301b1717b3atoa2e40a0d5943.vitest run src/ui/component-record-blocks.test.ts: 2 failed / 30 passed. The two named-message pins went red. The other three pins (refusal, control, chatterfeed) stayed green, as they should: the ablation removes the message and leaves the accept set alone.a301b1717b3a),git diff HEADis empty, andgit statusis clean.Verification (head
b6224772eb)pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2: 600 files, 17611 passed, 1 todo, exit 0. Before the merge, on29f3682ea5: 599 files, 17578 passed.pnpm --filter @objectstack/spec typecheck: exit 0. That coverstsc --noEmit,check:scripts-typecheckandcheck:test-typecheck. The edited test file is intsconfig.test.json's program (--listFilesOnly: 1 hit) and has no debt-ledger entry.pnpm --filter @objectstack/spec build && … check:generated: all 15 generated artifacts up to date. Nothing was regenerated.node scripts/pm/check-widening-tells.mjs --declaration no --diff: exit 0.component.zod.tswas judged against a declared surface and no widening tell fired. The changeset and the test file are NOT MEASURED by construction.Clause-②: noholds.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands(85 commands), each run on this head with its exit code captured before any pipe.--ranreconciliation: 83 exit 0, 1 NOT MEASURED, 1 unrun.check:dual-build-cjs-loads. It exited 3, PREREQUISITE NOT MET: it needs a whole-workspace build, and 63 of 77 build tasks are turbo cache misses here.check:type-check-debt. Its--re-measurebuilds the whole workspace closure and then type-checks every package. On29f3682ea5it passed its self-test and coverage leg, then hit my 300 s per-command cap during the closure build. That run left partialdist/trees in this worktree, which I removed before the rerun. It does not fit the foreground cap on this shared box, so it is declared to CI.tsconfig.eslint --no-inline-config --format jsonon the two edited TS files gives 2 files, 0 errors, 0 warnings.ESLint.isPathIgnoredis false for both).eslint.config.mjsnever enables type-aware linting (0projectService, noparserOptions.project; its own comment at:327-328says so). So this diff cannot move the verdict on any untouched file. Repo-widepnpm lintis CI's.Acceptance notes
record:chatter/record:discussionfeedmount (superseded in round 2). Round 1 left the host-channel sentence describing the standalone block only, and declined chatter-mount wording as splitting one shape. The at-tier record5954789199(FAIL) overruled that: the guidance table is one flat map that cannot be scoped per mount, so the fix is wording true on both. Round 2 (ab58157585) states each mount.PROPS_HISTORY's shared tail ("…was not read there…") is pre-existing-false for host-channel keys the renderer does read, onrecord:historyand here. Not owed by this PR (record5954789199③); the seat carries it.mainmerged once. Mergedfa7b565212(mergeb6224772eb). No conflict, none of this diff's files, noos-regendeferral. After the merge I ran a frozen install, a spec rebuild andcheck:generated, then the full spec suite and the gate union on the merge head.mainhas moved since, to41a3c8df15: a comment-only edit inview.zod.tswith no overlap, so I did not merge again.Review round 1
b6224772eb(5954789199), on one point: the new bullets were false on therecord:chatter/record:discussionfeedmount. Everything else (accept set unchanged,record:historybyte-identical,patch/Clause-②: no) was judged right.ab58157585, +47 / -29 in the same 3 files): the bullets are reworded for both mounts, the chatter pin asserts the mount-true clause, and the changeset sentence is corrected. Two ablations each turn the expected pins red, and the restore is proven (dev report5955605217).Generated by Claude Code