Skip to content

feat(app-shell): packaged actions on/off beside the packaged flows (ADR-0126 §8 item 2) - #6415

Merged
os-support-ai merged 1 commit into
mainfrom
claude/issue-6412-action-activation-ui
Aug 25, 2026
Merged

feat(app-shell): packaged actions on/off beside the packaged flows (ADR-0126 §8 item 2)#6415
os-support-ai merged 1 commit into
mainfrom
claude/issue-6412-action-activation-ui

Conversation

@claude

@claudeclaudeBot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Closes#6412

Part of the objectstack-ai/objectstack#12150 program (ADR-0126, v17 line). This is the
surface half of ADR-0126 §8 item 2 / amendment ruling 3 — the engine, the ledger rows
and both dispatch doors landed in objectstack-ai/objectstack#12348 (merged e5ce2ed0),
which deliberately left objectui out of its scope. The maintainer's pull, verbatim and
untranslated:

「动作 可能是需要开关的,因为有的 action 我不想启用。」

What landed

Setup › Packaged automation (the §7.4 page, #6382) gains a
packaged actions section beside its flows one. Per packaged action it does exactly
one thing — on/off for this scope — because that is all the
sys_metadata_activation ledger knows about an action.

flows section (unchanged)actions section (new)
on/offPOST /automation/:name/togglePOST /actions/_activation/:object/:action
clone✅ §7.1not chartered (§8 item 2)
drift / ancestry⛔ §9⛔ §9

Where each fact on a row comes from

There is no _status-style read door for actions to mirror the flows half with — objectstack-ai/objectstack#12348
shipped the flip only — and this PR does not invent one. It reads the two surfaces the
platform already sanctions, and joins them in a pure module (packagedActions.ts), exactly
the shape packagedFlows.ts has:

  • Discovery mirrors the runtime's own collectActionDeclarations: object-embedded
    actions[] from GET /meta/objectplus standalone items from GET /meta/action,
    with the object-embedded declaration winning an <object>:<action> key clash. Listing
    only the first source would leave an administrator with no off-switch for a packaged
    standalone action — the defect packagedFlows.ts names for a filtered-out flow.
  • State comes from the ledger's metadata_type: 'action' rows via the generic data API
    list, which the object's own declaration sanctions for precisely this use:
    apiMethods: ['get', 'list']"Reads stay open so operability surfaces can answer
    'what is disabled here?'"
    . Absence of a row means active, so a stock boot shows
    everything armed.
  • Provenance reuses isPackagedFlowItem rather than transcribing it a second time — its
    clauses read the two keys the registry stamps on every metadata item and say nothing about
    flows. What is new is which item is asked: an embedded action carries no provenance marks
    of its own, so its owning object answers; a standalone item answers for itself. That is the
    read-side twin of how the door derives the package it writes
    (action._packageId ?? obj._packageId).

global is the object segment for an object-less action — the spelling both dispatch doors
take, and the literal string the flip puts in the URL, so the object column shows what the
flip will actually address.

Refusals reach the operator verbatim

No client-side softening, no retry loop. All three shapes are reachable in tests and each is
transcribed character-for-character from the runtime's own message builders:

  • 403 PERMISSION_DENIED — the §5 posture gate. Its remedy clause is the action one
    and deliberately does not say "clone"; a client that reworded it would put back the
    recommendation the gate carries a per-door sentence to avoid.
  • 409 RESOURCE_CONFLICT — two objects declare this action name. Both rows stay listed
    and neither switch is pre-emptively disabled: the ledger addresses an action by name (§4),
    and only the server can name the objects the flip would reach.
  • 503 SERVICE_UNAVAILABLE — no ledger attached. An outage is not a verdict.

One refusal is the section's own and points the same way: a hasMore on the ledger read is
treated as a load failure rather than rendered. A dropped row reads as "active", so a
partial ledger would show a switched-off action as armed — the one direction this
section must not fail in.

Deliberate absences (ADR-0126 §9), pinned

⛔ No clone control. ⛔ No "customized" badge, diff-vs-base or base-moved notice. The
absence is asserted against a response that smuggles clonedFrom / baseVersion in, so it
is enforced at the renderer and not merely by the wire — a response field is the cheapest
place for ancestry to reappear.

Flows section

Behaviour unchanged. It gains a heading beside the new one, and the page subtitle now reads
"Flows and actions …" — that string moved in all ten packs together. The existing page test's
fake server answers the three new reads with empty lists so its assertions keep measuring the
flows half; leaving them unanswered would not have been neutral (its stub throws).

i18n

Nine new packagedAutomation.* keys in enand real translations in all nine other
packs
— no inline defaultValue standing in for a pack entry (the objectui#3517 lesson).
The two toggle-failure keys are artifact-neutral by wording and are reused rather than
duplicated into ten packs.

Verification

Everything below was run at 924d105, the branch head, with the gate's own verdict quoted
rather than a shell $?.

gateverdict
vitest run packages/app-shell/src/views/setup/ packages/i18n/Test Files 60 passed (60) · Tests 968 passed (968)
type-check (app-shell, i18n)Scope: 2 of 47 workspace projects · both Done
lint (app-shell, i18n)✖ 2680 problems (0 errors, 2680 warnings)0 errors; none attributable to the changed files
check:i18n-keys"Every in-scope call-site key resolves against the en pack … every literal inline defaultValue matches the value the pack serves"
check:i18n-drift"1 en value(s) changed … 9 pack value(s) followed" · "Every changed en value was followed by all nine translation packs."
check:control-bytes✅ OK (scanned 5308 tracked text file(s))
check:eager-closure3/3 ✅ chunk ceilings, weighed against a fresh turbo run build --filter=@object-ui/console
changeset:check + check-changeset-presence✅ 16 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)

Ablation — the verbatim-refusal relay was replaced with a client-side fallback
(actionErrorDetail(json, fallback) → a function returning only fallback). The mutation
was confirmed on disk by grepping for both the removed and the injected text (removed: 0
hits, injected: 1 hit) before the run, not by a diffstat. Predicted direction: red. Observed:
Tests 4 failed | 10 passed — the three refusal shapes plus the row-isolation case. The
restore leg was verified cmp-identical to the pre-ablation file. No rebuild was needed
and none is claimed
: the test imports ./PackagedActionsSection by relative path, so the
module under test resolves from source, never through a package exportsdist/.

Declared narrowing. The full packages/app-shell/ suite (536 test files) was started and
hit the container's ~10-minute foreground cap (exit 143). What ran instead is every
app-shell test file that this diff can reach, derived by enumerating importers of the changed
modules and consumers of the one changed en value: only src/index.ts and
services/builtinComponents.tsx reference the page outside views/setup/, neither has a
test file, and packagedAutomation.subtitle has exactly one call site. The lint narrowing is
repo-level only — both changed packages were linted in full, and the population is
invariant because the flat config enables no type-aware linting (no project /
projectService in languageOptions), so this diff cannot move a verdict in an untouched
file. The remaining farm is CI's run.


Generated by Claude Code

…DR-0126 §8 item 2)
Setup › Packaged automation gains a packaged ACTIONS section beside its flows
one — the surface half of ADR-0126 §8 item 2 (amendment ruling 3). The engine,
the ledger rows and both dispatch doors landed in objectstack PR 12348; objectui
was deliberately out of that PR's scope, so this is the other half of one card.
Per packaged action the section does exactly ONE thing: on/off for this scope.
That is all the sys_metadata_activation ledger knows about an action.
- State comes from the ledger's `metadata_type: 'action'` rows, read through
the generic data API list the object itself sanctions for exactly this
(`apiMethods: ['get', 'list']`). Absence of a row means active.
- Discovery mirrors the runtime's own collectActionDeclarations: embedded
`actions[]` from GET /meta/object plus standalone items from
GET /meta/action, object-embedded winning an <object>:<action> key clash.
- Flips invoke POST /actions/_activation/:object/:action with exactly the one
key its body declares; `global` is the object segment for an object-less
action, the spelling both dispatch doors take.
NO clone for actions — ruling 3 charters the switch and nothing else, and the
clone half stays pre-chartered. NO drift or ancestry surface (§9); the absence
is pinned against a response that smuggles clonedFrom/baseVersion in, so it is
enforced at the renderer and not merely by the wire.
Refusals reach the operator verbatim, no softening and no retry loop: the §5
posture gate (403 PERMISSION_DENIED), the ambiguous-name refusal (409
RESOURCE_CONFLICT, naming the objects) and the no-ledger outage (503
SERVICE_UNAVAILABLE). A `hasMore` on the ledger read is a load failure rather
than a rendered list — a dropped row reads as "active", so a partial ledger
would show a switched-off action as armed.
The flows section is unchanged in behaviour. Nine new packagedAutomation.* keys
land in `en` and in all nine other packs as real translations; the page subtitle
moved in all ten together.
Claude-Session: https://claude.ai/code/session_01KWRU3s15AJz7PGW7a7wdCh
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 52 chunks)3233.7 KB3266.6 KB
Main entry chunk (gzip)157.4 KB350 KB
Entry fileindex-dhTBbQkt.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)11.30KB4.28KB
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.86KB114.58KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)173.18KB47.97KB
fields (index.js)238.89KB60.02KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
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.66KB12.84KB
plugin-charts (index.js)64.66KB18.32KB
plugin-chatbot (index.js)188.60KB44.82KB
plugin-dashboard (index.js)133.46KB34.48KB
plugin-designer (index.js)211.95KB42.75KB
plugin-detail (index.js)245.10KB62.31KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)131.78KB32.19KB
plugin-gantt (index.js)164.14KB39.87KB
plugin-grid (index.js)201.79KB54.60KB
plugin-kanban (index.js)52.87KB14.57KB
plugin-list (index.js)112.63KB27.45KB
plugin-map (index.js)20.09KB6.62KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.49KB11.93KB
plugin-timeline (index.js)26.70KB7.69KB
plugin-tree (index.js)9.26KB3.13KB
plugin-view (index.js)84.55KB20.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)54.84KB18.43KB
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)3.75KB1.85KB
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.72KB2.24KB
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

@os-support-ai
os-support-ai marked this pull request as ready for review August 25, 2026 22:43
@os-support-ai
os-support-ai added this pull request to the merge queueAug 25, 2026
Merged via the queue into main with commit 9602dc8Aug 25, 2026
29 checks passed
@os-support-ai
os-support-ai deleted the claude/issue-6412-action-activation-ui branch August 25, 2026 22:55
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.

L6-UI: packaged actions on/off on the Setup packaged-automation page (ADR-0126 §8.2, ruling 3 — surface half)

2 participants

@os-support-ai@claude