Skip to content

fix(plugin-detail): pin recordActivityFeed's OPEN-vocabulary contract to sys_activity.type - #6112

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-5969-activity-feed-open-vocabulary-pin
Aug 24, 2026
Merged

fix(plugin-detail): pin recordActivityFeed's OPEN-vocabulary contract to sys_activity.type#6112
yinlianghui merged 1 commit into
mainfrom
claude/issue-5969-activity-feed-open-vocabulary-pin

Conversation

@yinlianghui

@yinlianghuiyinlianghui commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Fixes#5969

Implements the maintainer ruling of 2026-08-24 on objectstack#11507, direction 4:
sys_activity.type is author-extensible. All gates below ran on 8740317bc, the
final commit on this branch.

Item 4 answer: this IS a behaviour change, not only a pin

What I found was a drop, not a defined fallback. activityRowToFeedItem returned
null for any sys_activity.type outside ACTIVITY_TYPE_TO_FEED_TYPE — the row was
stored, queryable and invisible, with only a console warning. Under the ruling that value
is legitimate author-extended data, so the drop reproduces the #5840 failure by a different
route. It now renders through UNMAPPED_ACTIVITY_FEED_TYPE (the generic system feed
type), still announced once per distinct type. A real patch changeset names it.

Kept minimal: ACTIVITY_TYPE_TO_FEED_TYPE itself is unchanged, so the copy
RecordDetailView reads is untouched. The four deliberate exclusions (commented /
mentioned / login / logoutundefined) still return null, silently — those are
decisions, not gaps.

The two halves of the pin

Superset, in words:map ⊇ built-ins — every value the platform declares has an
entry here, a feed type or an explicit undefined. A new built-in with no entry turns the
pin red. The converse (built-ins ⊇ map) is not asserted and neither is set equality
in any spelling: under direction 4 an author-extended key legitimately lives outside the
declaration, and the pin #5840 removed is the one that made a closed declaration drop
stored rows. A second test asserts the non-containment explicitly, so "restore the equality
check" has to delete a passing test to happen.

Fallback: a positive observable — the row is present, carries its own summary, actor and
timestamp, and its feed type is the declared fallback — with the counter-probe in the same
run: created still renders field_change and scheduled still renders event, so a map
pointed wholesale at the bucket cannot read as green.

The docblock names the ruling (objectstack#11507, direction 4, 2026-08-24) in both the map
and the fallback constant, so the next sweep does not "restore" set-equality.

Where the built-in set was read from, and its limit

Source: objectstack packages/plugins/plugin-audit/src/objects/sys-activity.object.ts,
the type: Field.select([...]) declaration, read at objectstack commit 91b1342
(objectstack#11522) on 2026-08-24. Proved to be the only declaration on the platform by
grepping every spelling of the vocabulary across packages/ and apps/ — one non-test hit,
that file.

⚠️ It is not reachable live from this repo: @objectstack/plugin-audit is a server
plugin, and none of @objectstack/spec / client / formula / lint carry the
declaration (spec publishes FeedItemType, the map's values, never its keys). So the
census in the test is a documented hand copy, and it had already drifted — scheduled was
declared upstream on 2026-08-24 while the previous census still filed it as "undeclared but
written". Filed as objectstack#11807 (finding, unassigned): publish the built-in set where a
UI package can read it, which is what would let that literal be deleted. The fallback is
what keeps the drift costing an icon rather than a row.

Which assertions survive a revert

Measured, not asserted, by ablation B (reverting the fallback line):

  • Go red: the fallback leg, the missing-type leg, the non-containment leg, and
    warns once per unknown type — 4 tests.
  • Stay green: the superset leg and the whole-table regression control. That is expected
    and stated: the superset leg pins a relation between the map and the platform that already
    holds today; it is not a test of this behaviour change, and its discriminating power is
    measured by ablating the map (ablation A), not by ablating the fix.

Verification

Direction predicted before each run; both predictions held. Each probe asserted anchor
uniqueness, printed its landing site, grepped injected and removed text separately, kept
a control string present in both versions (export const ACTIVITY_TYPE_TO_FEED_TYPE), and
restored under trap … EXIT INT TERM with a cwd-independent git -C … checkout --.
git diff HEAD --stat was empty after each. The edit was committed before ablating. No
rebuild step is involved: the tests import ../recordActivityFeed by relative source path,
so nothing resolves through dist/.

probepredictedobserved
A — delete assigned: 'field_change' from the mapsuperset red, fallback greensuperset red (message names assigned), whole-table control red (predicted side effect), fallback green — 2 failed / 39 passed
B — point the unknown branch back at undefinedfallback red, superset green4 fallback-dependent tests red, superset + table control green — 4 failed / 37 passed
gateexita red would have meant
pnpm --filter '@object-ui/plugin-detail^...' build0stale closure — every reading after it suspect
pnpm --filter @object-ui/plugin-detail type-check (tsc --noEmit && tsc -p tsconfig.test.json)0the fallback constant or the mapper does not type against FeedItemType
pnpm exec vitest run packages/plugin-detail/src (root form)0104 files / 979 tests — a sibling renderer depended on the drop
pnpm exec vitest run …/recordActivityFeed.test.ts --reporter=verbose041 tests, all five new/changed ones named in the log
downstream prefix sweep, direction demonstrated--filter '...@object-ui/plugin-detail' matches 11 packages (plugin-detail + app-shell, plugin-calendar/gantt/kanban, runner, apps/console, apps/site, 3 examples); the suffix form matches 13 (its dependencies) — the prefix list contains the consumers, which is the direction contract tightenings need
pnpm --filter @object-ui/app-shell type-check0the only downstream package referencing the changed exports (2 files; the other 9 match 0) breaking on the new export surface
pnpm exec vitest run packages/app-shell/src/views0310 files / 2989 passed, 1 skipped — the shared map's consumer regressing
pnpm exec eslint over the merge-base delta, plain config0style/correctness on the two changed files
node scripts/check-changeset-presence.mjs0"2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)" — a behaviour change shipping undeclared
node scripts/check-control-bytes.mjs0a raw control byte in the new prose (5053 files scanned)
pnpm check:spec-symbols0the hand-written built-in census colliding with a spec export name
grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' on changed files1 (no hits)same, outside the gate

Environmental red, resolved, not a finding: app-shell type-check first exited 2 with
Cannot find module '@object-ui/auth' / plugin-chatbot / plugin-view / plugin-list / plugin-form — the unbuilt-closure signature (no @object-ui/plugin-detail among them). It
went green after pnpm --filter '@object-ui/app-shell^...' build.

Gate set derivation (standing rule from #5874): enumerated from each CI job's own step
list — ci.yml Type Check + Test, lint.yml Lint, changeset-presence.yml,
control-bytes.yml — not from top-level script names. check:spec-symbols was picked up
that way. Skipped as not-applicable to a two-file TS diff with no new imports, deps, locale
strings or packages: check:phantom-deps, check:self-import, check:esm-specifiers,
check:i18n-keys, check:i18n-drift, check:icon-record-names,
check:action-forward-parity, check:designer-field-key-parity, the coverage meta-checks.

Declared narrowing: the downstream test sweep ran packages/app-shell/src/views rather
than all 11 packages' suites, because grep showed app-shell is the only downstream package
referencing the changed module's exports (2 files; the other 9 match zero). CI runs the full
farm regardless.

Deviation:#6088 (packages/plugin-detail/src/synth/) had not landed on main at branch
time, so this branches from b00fcba45 instead of a base containing it. Different file, no
overlap.


Generated by Claude Code

… a defined fallback
`activityRowToFeedItem` returned `null` for any `sys_activity.type` outside
`ACTIVITY_TYPE_TO_FEED_TYPE`, so a stored row with an author-extended type was
queryable and invisible. Under the 2026-08-24 direction-4 ruling on
objectstack#11507 that column is author-extensible, so such a value is
legitimate data and dropping it reproduces the objectui#5840 failure by a
different route. It now renders through `UNMAPPED_ACTIVITY_FEED_TYPE`, still
announced once per distinct type.
The pin that replaces the set-equality check objectui#5840 removed is
two-directional and says so in its docblock: the map must cover every BUILT-IN
type (superset), and an unknown type must reach the feed through the fallback.
Set equality is not asserted in either spelling.
Part of #5969
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSoz9uGhaaSgiq3hshtN7L
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 52 chunks)3221.1 KB3990.2 KB
Main entry chunk (gzip)153.7 KB350 KB
Entry fileindex-C5NtuYYW.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)10.38KB3.90KB
app-shell (runtime-config.js)18.10KB6.51KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)505.15KB114.53KB
core (index.js)4.92KB1.97KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)166.86KB46.08KB
fields (index.js)238.40KB59.89KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)23.13KB7.63KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)7.77KB3.13KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)9.53KB3.38KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.64KB1.50KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)1.93KB0.88KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.62KB12.83KB
plugin-charts (index.js)64.66KB18.32KB
plugin-chatbot (index.js)188.21KB44.67KB
plugin-dashboard (index.js)133.35KB34.45KB
plugin-designer (index.js)212.30KB42.80KB
plugin-detail (index.js)244.14KB61.94KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)125.63KB30.64KB
plugin-gantt (index.js)164.15KB39.88KB
plugin-grid (index.js)200.79KB54.26KB
plugin-kanban (index.js)52.89KB14.59KB
plugin-list (index.js)111.86KB27.22KB
plugin-map (index.js)20.11KB6.64KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.49KB11.93KB
plugin-timeline (index.js)26.49KB7.59KB
plugin-tree (index.js)9.26KB3.13KB
plugin-view (index.js)84.57KB20.74KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)52.40KB17.45KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.35KB0.70KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)12.13KB3.65KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)7.54KB2.63KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.87KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.49KB2.14KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@yinlianghui
yinlianghui marked this pull request as ready for review August 24, 2026 18:02
@yinlianghui
yinlianghui added this pull request to the merge queueAug 24, 2026
Merged via the queue into main with commit 17fbbafAug 24, 2026
23 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-5969-activity-feed-open-vocabulary-pin branch August 24, 2026 18:13
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

recordActivityFeed: pin the feed-kind map's OPEN-vocabulary relationship to sys_activity.type (ruled direction 4 on objectstack#11507)

2 participants

@yinlianghui@claude