Uh oh!
There was an error while loading. Please reload this page.
fix(plugin-detail): record:activity renders scheduled activities as event - #5886
Merged
Merged
Conversation
…event` (#5840) `sys_activity` rows with `type: "scheduled"` had no entry in ACTIVITY_TYPE_TO_FEED_TYPE, so they were discarded upstream of every filter a page can configure and could never appear on any timeline. HotCRM's `schedule_meeting` writes exactly that value for a not-yet-held meeting, on five objects; the held branch (`completed`) reached the feed and the scheduled branch did not. Map `scheduled` to `event`, which is the semantic pairing and also makes the declared `event` feed kind reachable for the first time. The unknown-type default is deliberately unchanged: a type nothing maps is still dropped, not bucketed into `system`, because rendering an unmeasured kind as something it is not is new wrong data rather than recovered data. The drop stops being silent instead — one `console.warn` per unknown type, and none for the types the table knows and deliberately excludes. The test's key-set pin used to assert set-equality with plugin-audit's declared options. That premise is false: every field on `sys_activity` is readonly and validateRecord skips readonly fields, so undeclared values are stored silently. The pin now states both vocabularies — the declaration, plus the values a shipped producer measurably writes, each naming its producer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EuPCi56cnGyykygi3z9w4m
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
os-zhuang
marked this pull request as ready for review
August 23, 2026 17:42
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Aug 23, 2026
os-zhuang pushed a commit
that referenced
this pull request
Aug 23, 2026
…s` filter (#5841) `normalizeFeedTypes` returned `undefined` for three different authored inputs — no `types` key, `types: []`, and a list whose every member was unrecognised — and `applyFeedConfig` reads `undefined` as "apply no filter". So a page that named the wrong feed kind was served EVERY activity on the record, with no diagnostic anywhere at runtime. Measured in a real app: a lead page authored `types: ['crm_task']` (an object name where a feed kind belongs) and its Activity tab rendered the audit stream for as long as it shipped. A sanitiser may narrow an author's request or refuse it; it must never silently widen it. Widening turns a typo into "show the user everything" — the one outcome no author asked for — and it hides behind a PLAUSIBLE result, since a populated timeline reads as working while an empty one gets investigated. `undefined` is now reserved for one meaning: no `types` key was authored. An authored filter that keeps nothing returns `[]`, and the call site tests `!== undefined` rather than truthiness, so `[]` filters to nothing. A non-array `types` is refused for the same reason rather than ignored. Unrecognised entries are named once, through the same warn-once plumbing #5886 added for unmapped `sys_activity.type` values, now factored into a shared `warnOnce` with a bucket per channel — the two vocabularies overlap, so one channel having spoken must not silence the other. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EuPCi56cnGyykygi3z9w4m
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#5840
sys_activityrows withtype: "scheduled"were discarded before any filter ran, so a scheduled meeting could never appear on any activity timeline whatever the page authored. This mapsscheduledto theeventfeed kind.Re-measured at live source
The card measured a shipped bundle. The live source is
packages/plugin-detail/src/renderers/recordActivityFeed.ts, and it says the same thing —ACTIVITY_TYPE_TO_FEED_TYPEhad noscheduledkey, andactivityRowToFeedItemreturnsnullfor anything that resolves falsy, upstream ofshowCompleted, upstream oftypes, upstream of everything a page can configure.Table as it stood, read from source, not from
dist:sys_activity.typecreatedupdateddeletedassignedsharedfield_changecompletedtasksystemsystemcommentedmentionedloginlogoutundefined(dropped)scheduledeventThe producer, measured
objectstack-ai/hotcrmat5eee1bd,src/actions/global.actions.ts:registered for
crm_lead,crm_contact,crm_account,crm_opportunity,crm_case. The held branch reached the timeline; the scheduled branch was written, stored, queryable and invisible.scheduled→eventalso makes the declaredeventfeed kind reachable for the first time, which answers the second half of the card's complaint at zero extra cost.commented/mentionedare NOT mapped — what was searchedThe PM's ruling was to map them only on a measured producer. There is none:
objectstack's own writer census (packages/plugins/plugin-audit/src/objects/sys-activity-type-vocabulary.test.ts) records both explicitly:commented— "no writer: sys_comment hooks write comment rows, never a sys_activity row";mentioned— "no writer: the @mention hook notifies; it emits no activity row".grep -rn "'commented'\|'mentioned'" srcacrosshotcrmreturns nothing.sys_commentto get reactions and threading, and its own comment says so.showCommentInput/enableMentionsare props of this component, not producers of those row types. Left unmapped.The unknown-type default is unchanged, deliberately
A type nothing maps is still dropped, not bucketed into
system— rendering an unmeasured kind as something it is not is new wrong data, not recovered data.What changed is that the drop is no longer silent: one
console.warnnaming the type, deduped per type (same shape as the evaluator's loud fail-open incore/src/evaluator/fieldRules.ts). Types the table knows and deliberately excludes stay silent, because a warning about a decision teaches authors to ignore the channel.What the card's premise got wrong, and one thing it did not know
The defect itself reproduces exactly. Two corrections:
sys_activitymap.commentdoes have a producer —sys_commentrows, via the host discussion context. After this PR the true count is 8:email,call,note,file,record_create,record_delete,approval,sharing. Filed as [finding] 8 of FeedItemType's 13 kinds still have no producer on any objectui surface #5877 per the PM ruling below.scheduledis not in the platform's declared vocabulary at all. plugin-audit declares eleven options andscheduledis not among them. It lands anyway because every field onsys_activityisreadonly: trueand objectql'svalidateRecordskips readonly fields, so an undeclared value is stored silently — measured upstream, in that same census file. The platform also forwards author-declared values into the column (ADR-0052 §5b.2activityMilestones[].type, applied verbatim byaudit-writers.ts), so this is a general door rather than one app misbehaving.That second point is why the test's key-set pin had to change rather than just gain a row. It asserted set-equality with plugin-audit's declared options; that premise is false, and keeping it would mean dropping stored rows to stay tidy. The pin now states both vocabularies — the declaration, plus values a shipped producer measurably writes, each naming its producer, as literals for the same reason the upstream census uses literals.
Whether the upstream enum should absorb
scheduledis a platform ruling, filed as objectstack-ai/objectstack#11424.PM ruling carried into this PR
From the
domain:uiPM seat: take the first direction only. Extend the map so the vocabularysys_activityactually stores reaches the timeline; do not narrowFeedItemType, since that enum is published by@objectstack/specin the other repo and narrowing it is a cross-repo spec retirement with ADR-0087 registry obligations. The ten-unreachable-kinds half is recorded separately as #5877 rather than acted on here.Verification
Every claim below quotes the verdict line the tool printed. Exit codes captured before any pipe.
Tests — path-filtered from the repo root, at final commit
1df1c03d3:Dependency closure built first:
pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-detail^...' build→VERDICT command-exit 0.Typecheck —
pnpm --filter @object-ui/plugin-detail type-check→VERDICT command-exit 0, and the run echoed the script, so it was not a zero-match no-op:Gates:
check-changeset-presence✅ 2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)check-changeset-no-major✅ No changeset declares a 'major' bump.check-control-bytes✅ check-control-bytes: OK (scanned 4890 tracked text file(s); skipped 85 binary).check-doc-linksLinks are valid across 13 scan roots.check-doc-component-types✅ Every documented component type is registered.check-i18n-call-site-keysEvery in-scope call-site key resolves against the en pack (2928 keys)check-i18n-en-driftNo en value changed in this range.check-package-self-import✅ No package names itself inside its own src/.check-spec-symbol-derivation✅ spec symbol derivation: 1300 files scanned against 4959 spec export namescheck-doc-snippet-typesdid not evaluate and is declared, not claimed: it exits 1 on a prerequisite, not on content —The snippet program was NOT run: the packages it resolves against are not built, naming nine unbuilt packages. Running it locally means a full workspace build behind the shared verify lock; CI builds and runs it. This change adds no code snippet to any doc.Lint — narrowing declared and measured. Ran
eslint --no-inline-configon the two changed TS files instead of the repo-wide sweep. Exit0, no output. The narrowing is a measurement, not a skip:--format jsonoutput with a verdict rather than being ignored.--format json: 2 files,errors=0 warnings=0each.eslint.config.jsdeclares noparserOptions.projectand noprojectService, so type-aware linting is off and no rule reads across file boundaries. No lint config and no shared type consumed by a lint rule changed, so this diff cannot move the verdict of any file it does not contain.Reverse verification — two ablations, each discriminating its own hunk
Both mutate a committed tree, confirm the mutation on disk by grepping the text meant to change (not an editor exit code, and not a bare
git diff --stat), and restore under atrap ... EXIT INT TERM. The subject resolves by relative import (from '../recordActivityFeed'), so nodistand no rebuild is involved — stated because an ablation that silently ran against stale build output would stay green and prove nothing.Ablation 1 — remove only
scheduled: 'event',. Disk proof: injected text count0, surrounding anchorcompleted: 'task',still1. Direction predicted before running, and matched exactly —Tests 4 failed | 24 passed (28):eventfeed itemNon-discriminating for this hunk, named rather than counted as evidence: the three
applyFeedConfiglegs build aneventitem by hand to pin the pipeline and never touch the map;leaves every previously-mapped type pointing where it didnormalisesscheduledaway on purpose, being the regression control for the other entries; the drop/warn legs use an unknown type, notscheduled.Ablation 2 — remove only the diagnostic, leaving the map entry in place. Disk proof: call count
0,scheduled: 'event',still1. ResultTests 2 failed | 26 passed (28)— the two legs asserting the warning, and no others, which is what shows the two probes are independent. Note the drop itself never regressed:still DROPS a type nothing mapswent red on itsconsole.warnassertion, not on itstoBeNull().stays SILENT for the types it deliberately dropsstays green here by construction — it discriminates the opposite mutation (a warning that fires on deliberate drops).Restore legs, both ablations:
git diff HEAD --statempty, marker text back at count1, full re-run green.Out of scope, filed unassigned
FeedItemType's 13 kinds still have no producer (the card's second half, per the PM ruling)RecordDetailViewhand-copies this map and nothing fails when the two disagree. This PR creates that divergence:schedulednow renders in therecord:activityblock and is still dropped on the console record page. Left deliberately — that copy is another package's surface, outside this card's fence — and called out in the docs page and in the source comment rather than left to be discovered.scheduled) — readonly fields make the enum unenforceable objectstack#11424 —sys_activity.typedeclares 11 options while a shipped producer writes a 12th, and readonly fields make the enum unenforceableGenerated by Claude Code