Skip to content

feat(console,app-shell): a settings action row draws its label once, on the button; the Installed Apps kernel note is the Uninstall tooltip (objectui#12610) - #12645

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-12610-settings-action-rows
Oct 16, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-12610-settings-action-rows

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #12610
Clause-②: yes

Items 1 and 4 of the card, as triage narrowed them. Items 2 and 3 are objectstack-ai/objectstack#23839 and objectstack-ai/objectstack#23841, not touched here.

What changes

1. A settings action row has one label, on the button. The console's settings renderer drew an action_button specifier's label twice: as the row label on the left and again inside the button. That is why Mail Delivery read "Send test email / Send test email" and AI & Embedder repeated "Test connection", "Reset to environment defaults" and "Test embedder". The manifests declare one label each, so the fix is in the renderer, once, for every action row (SettingsField, the action_button branch).

  • The button carries the label.
  • The row's left side holds the specifier's description when the manifest declares one, as one line of muted text. Otherwise it holds nothing and the button stands alone at the row's end.
  • No manifest declares a description on an action_button today, and the renderer invents none.
  • Settings keys, values and save behaviour do not change. SettingsView is untouched.

4. The Installed Apps kernel note is the Uninstall tooltip. The "kernel API is additive only" paragraph sat at the foot of the list, including under the empty state. It now opens as a tooltip, on hover or focus, on a loaded local row's Uninstall. It is that button's accessible description (aria-describedby to one sr-only copy), using the console's existing TooltipProvider / Tooltip / TooltipTrigger pattern.

  • A not-loaded row's Uninstall carries no note: that package left nothing in the running kernel (objectui#11645).
  • A cloud-bound list offers no Uninstall, so it carries no note either.
  • The note renders as text. The widget no longer passes it through dangerouslySetInnerHTML.

i18n. marketplace.installedAdditiveNote dropped its leading bold "Note:" label (a strong element, and each translation's equivalent) in all ten packs. The sentence after it is unchanged, and so is the key name. check:i18n-drift reads one en value changed, followed by all nine packs.

Tests

  • SettingsField.actionRowOneLabel-12610.test.tsx (console). It renders the real SettingsView over a stubbed fetch for every action_button the server's settings manifests declare: Mail, SMS, AI × 3, Knowledge, File Storage. It asserts that no string a button carries is drawn anywhere outside that button. Also covered:
    • a label translated through actions.ID.label;
    • a declared description, shown once on the left;
    • no description, so nothing beside the button.
  • InstalledListWidget.kernelNoteTooltip-12610.test.tsx (app-shell), in en and zh, over the real marketplaceApi:
    • the empty state carries no note;
    • a loaded Uninstall has the note as its accessible description, and hovering opens a tooltip with it and no markup;
    • a not-loaded Uninstall has no description;
    • all ten packs' values carry no markup.
  • Ablation, after committing the fix, each leg trap-restored and checked on disk:
    • restoring the old label div: 8 failed (8);
    • restoring the old paragraph and dropping the tooltip: 6 failed | 11 passed (17);
    • restoring the bold label in de: 1 failed | 16 passed (17).

Local gates (head 514ee9d)

  • Closure build of @object-ui/console^...: 34/34 tasks.
  • type-check for @object-ui/i18n, @object-ui/app-shell and @object-ui/console: 0.
  • pnpm exec vitest run apps/console/src/pages/settings/ packages/app-shell/src/console/marketplace/ packages/i18n/: 134 files passed, 1689 tests passed, 13 skipped.
  • check:i18n-keys, check:i18n-drift, check:i18n-dead-keys, check:changeset-claims, changeset:check, check:test-path-roots, check:spec-symbols and changeset presence: all 0.
  • eslint on the changed files: 0 errors.
  • The rest of the farm is CI's.

Screenshots

Branch claude/issue-12610-evidence, under evidence/12610/. These are Mail Delivery, AI & Embedder (three action rows), File Storage and Installed Apps (empty state). They were taken at 1280 × 900 with the console dev server, against a published @objectstack/* 18.0.0-next.1 showcase backend (e2e/live/ci/start-backend.sh). Each page has a -before shot (base 8ed7e5a) and an -after shot (this head).

NOT MEASURED: a screenshot of the Uninstall tooltip itself. Reason: the fresh backend had no installed package, so there was no Uninstall row to hover. The tooltip is pinned by the app-shell test above.

Changesets

  • 12610-settings-action-row-one-label.md: @object-ui/console patch.
  • 12610-installed-apps-kernel-note-tooltip.md: @object-ui/app-shell patch.
  • 12610-i18n-kernel-note-plain-text.md: @object-ui/i18n patch, carrying the Clause-② note.

Acceptance notes

  • Booting the live backend took two workarounds in a throwaway copy only:
    • npm 11 instead of npm 10, which crashed on edgesOut;
    • removing isDefault from the showcase capability-map page.
  • The settings pages also needed OS_PLATFORM_OWNER_EMAIL=admin@objectos.ai. Without it, the seeded admin is refused manage_platform_settings.
  • e2e/live/ci/stop-backend.sh left the objectstack dev child running on its port, so the next start came up on the next port. This is reported to the seat; it is not changed here.

Provenance: written by the os-dev cloud session session_01P1jMq7XJa8LG1or2Z2jgxH (claim 6211778237) and opened by the domain:ui seat 1 PM (session_011K9g23W67dy2zwnyzaoB6o) from that session's final message, because a cloud session cannot open a PR from its container. The seat changed only the two tag-shaped spellings of the bold label into words.


Generated by Claude Code

…nel note is the Uninstall tooltip (objectui#12610)

The settings renderer's action_button row printed the manifest label on the
left and again on the button. The button now carries it alone; the row's
left side holds the manifest's description when declared, nothing otherwise.

Installed Apps' "kernel API is additive only" paragraph leaves the page foot
(where it sat under the empty state too) and becomes the tooltip and
accessible description of a loaded row's Uninstall. The value is plain text
in all ten packs, so it no longer goes through dangerouslySetInnerHTML.

Claude-Session: https://claude.ai/code/session_01P1jMq7XJa8LG1or2Z2jgxH
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 70 chunks) 3094.7 KB 3119.4 KB
Main entry chunk (gzip) 73.8 KB 350 KB
Entry file index-DBbaG4VQ.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 21.19KB 7.76KB
app-shell (runtime-config.js) 22.59KB 7.89KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 20.39KB 6.02KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.62KB 0.83KB
auth (authStyles.js) 5.27KB 1.86KB
auth (createAuthClient.js) 41.19KB 11.12KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.26KB 8.03KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (commentAuthorship.js) 1.39KB 0.78KB
collaboration (index.js) 1.93KB 0.85KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 598.49KB 144.31KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 248.20KB 69.22KB
fields (index.js) 283.17KB 72.17KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.52KB 2.39KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 40.67KB 11.85KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 15.35KB 5.51KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.82KB 2.38KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 54.94KB 16.05KB
plugin-charts (index.js) 84.97KB 23.36KB
plugin-chatbot (index.js) 201.89KB 48.13KB
plugin-dashboard (index.js) 146.15KB 39.55KB
plugin-designer (index.js) 233.95KB 50.08KB
plugin-detail (index.js) 262.91KB 69.49KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 180.92KB 46.73KB
plugin-gantt (index.js) 178.36KB 45.08KB
plugin-grid (index.js) 265.13KB 73.29KB
plugin-kanban (index.js) 53.23KB 16.71KB
plugin-list (index.js) 129.90KB 33.12KB
plugin-map (index.js) 27.07KB 8.96KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 46.66KB 13.13KB
plugin-timeline (index.js) 39.21KB 11.85KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 93.43KB 23.94KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 12.07KB 3.68KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.81KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 1.13KB 0.65KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.78KB 2.70KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (node-slots.js) 7.18KB 2.34KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-approval-decision.js) 0.20KB 0.20KB
types (record-components.js) 0.20KB 0.19KB
types (record-panels.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.26KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.48KB 3.50KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: TIER_DEFAULT
Head-sha: 514ee9df8be583c229cd0e97f930bdb654ff5307
Local-runs: none

Face: the Clause-②: yes declaration on claim 6211778237 (objectui#12610). It was declared on «拿不准 ⇒ 按 yes», because the work might add @object-ui/i18n keys. The committed tree has no published schema and no governed path: check-governed-merges --pr reports 0 of 17 paths, NOT governed, +458/−33. Inputs: the diff, the card and its triage narrowing 6210969329, and this head's check-runs (33 success, 3 skipped by design).

① Derived judgments

  • @object-ui/i18n. No key is added, removed or renamed. marketplace.installedAdditiveNote changes value in all ten packs: the leading bold "Note:" label (a strong element) is dropped, and the sentence after it is unchanged. The exported en pack's type is its literal content, so this key's literal type changes value; the key set does not change. That is not a widening. A host that rendered the key as HTML loses the bold prefix, as the i18n changeset states. Correct; the declared yes was precautionary, and no widening occurred.
  • @object-ui/app-shell. InstalledListWidget.tsx is registered through a side-effect import (index.ts:338) and is not exported. The Uninstall button gains a tooltip and an aria-describedby, and the list foot's paragraph is gone. Removing dangerouslySetInnerHTML narrows nothing a host can author. Correct.
  • @object-ui/console. The action_button row draws its label once, on the button, and the specifier's description once, on the left, when the manifest declares one. Settings keys, values and save behaviour are unchanged. No manifest declares a description today; the dev measured this against objectstack service-settings/src/manifests. Correct.

② Semver level

@object-ui/console, @object-ui/app-shell and @object-ui/i18n are each a patch: the changes are visual and textual, nothing is added, and a key's value changes. Changeset Bump Policy and Changeset Fixed Group Check pass on this head. Consistent.

③ Boundary flags

The dev's three open questions are ruled as shipped:

  1. A: an action row with no description is right-aligned.
  2. A: keep the key name. A rename buys only a name and costs a published-key removal.
  3. A: a cloud-bound list, which offers no Uninstall, carries no note. The card's ruling puts the note where it applies.

Out of scope:

  • stop-backend.sh leaves an orphaned objectstack dev holding the port. This is an e2e live-lane tool, outside this card and lane.
  • The seeded admin needs OS_PLATFORM_OWNER_EMAIL before it can read the settings namespaces. This is backend behaviour.

The seat reports both; neither blocks.

Implemented-by: session_01P1jMq7XJa8LG1or2Z2jgxH
Reviewed-by: session_011K9g23W67dy2zwnyzaoB6o

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · domain:ui seat 1 · session_011K9g23W67dy2zwnyzaoB6o · 2026-10-16T09:45Z

What was reviewed:

  • Head 514ee9df8be583c229cd0e97f930bdb654ff5307, against objectui#12610 (items 1 and 4, as triage narrowed them at 6210969329).
  • The claim 6211778237 (Clause-②: yes, mode:cloud, cloud session session_01P1jMq7XJa8LG1or2Z2jgxH).
  • The contract review of record, 6212742062 (TIER_DEFAULT, PASS, same head).
  1. Shape.
    • The seat opened this draft PR to main from the cloud session's final message; its provenance line is in the body.
    • Fixes #12610 is the first line and the only closing keyword. Clause-②: yes starts a line, and the assignee is os-tesla.
    • All 17 files sit inside the claimed surface: SettingsField.tsx, InstalledListWidget.tsx, the ten locale packs, two tests and three changesets. SettingsView.tsx is untouched; objectui#12606 owns its header.
    • The changesets were checked sentence by sentence against the diff. No content/docs/releases/ change. The surface is NOT governed, at 491 changed lines.
  2. Premises measured by the seat.
    • action_button is one branch, and the fix is one renderer change with no per-manifest case.
    • The tooltip uses the existing TooltipProvider / Tooltip / TooltipTrigger and an aria-describedby to one sr-only copy. Only loaded local rows carry it.
    • dangerouslySetInnerHTML is removed.
    • No i18n key is added, and the key set is unchanged.
  3. Gates (check-runs pinned to this head).
    • 33 success and 3 skipped by design, including Lint, Repo Gates, Type Check, Test (shard 1–16/16), Build & E2E, Bundle Analysis (eager closure 3094.7 KB of 3119.4 KB) and Live E2E (informational).
    • The dev's ablations each go red: 8/8, 6/17 and 1/17.
  4. Accepted deviations.
    • The Uninstall tooltip has no live screenshot: the fresh backend had no installed package. The app-shell pin holds it.
    • Live before/after shots of Mail, AI & Embedder, File Storage and Installed Apps are at claude/issue-12610-evidence d1ce10a, under evidence/12610/.
  5. Out of scope.
  6. Landing.
    • Ready, then auto-merge into the merge queue.
    • After the merge: verify by content, close the card through Fixes, strip pm:dispatched, and archive the cloud session.

Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Settings pages: no duplicated action labels, warnings out of the subtitle, Cloud Connection says its sentence once

2 participants