Skip to content
This repository was archived by the owner on Oct 26, 2026. It is now read-only.

fix(app-shell): toasts sit top-centre, clear of the page header's actions (objectui#13340) - #13363

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-13340-one-toast-per-refusal
Oct 23, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-13340-one-toast-per-refusal

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Closes #13340
Clause-②: no

The two emitters: measured, and the second one does not reproduce on origin/main

The card asks for the second emitter behind each refusal on the Setup forms. I traced both Setup form paths on origin/main (2c4e5f5) and counted toasts in a real browser, at 1440×900, against a live @objectstack 18.0.0 backend (objectstack dev --fresh, Setup app). I ran the same check on the console that 18.0.0 ships at /_console.

Form (Setup) Path Toast emitter Error toasts per refusal
Business Units → New (A record with this value already exists) ModalForm → handleSubmit → dataSource.create rejects → rethrown components form.tsx, the submit catch: toast.error(errorMessage, { id: outcomeToastId }) 1 (main), 1 (18.0.0)
Users → Create User (Either email or phoneNumber is required) ActionParamDialog → runner run → console apiHandler returns the refusal core ActionRunner.handlePostExecution → console toastHandler → toast.error(message) 1 (main), 1 (18.0.0)
Teams → Add Member same as Create User (add_team_member, type: 'api') same same path (the live run succeeded, so no refusal was counted)

Each refusal is also written inside the form: the form's banner above the fields (setSubmitError), or the parameter dialog's refusal line (action-param-refusal, objectui#13161). It is the same sentence as the toast, so the screen shows it twice: once in the form and once as a toast. That matches the report's "×2". It is not a second toast. Under the card's own rule (find the second emitter, never de-duplicate by text) there is no second toast emitter to remove. Dropping the toast while the banner shows would be a design change: the toast exists because the banner can be scrolled out of view in long forms. That decision belongs to the maintainer and is not made here.

So this PR pins the measured behaviour: one error toast per refusal on both paths. It also fixes the placement, which did reproduce.

Placement: did reproduce, fixed

At 1440×900 the toaster sat top-right at sonner's 24px offset. On Setup → Business Units the toast box measured [1060,24,1416,78]. The header buttons measured New [1198,63,1275,99] and Import [1283,63,1376,99], so the toast covered both for its 4s life. After the change the toast measures [542,56,898,110], clear of both buttons. With the record dialog open it sits in the empty middle of the dialog's title row, clear of the title, the close button and the error banner.

ConsoleToaster change, through the one existing Toaster config, so it applies to every page:

  • No drawer open: position="top-center", with offset={{ top: 56 }} (the AppShell top bar is h-12, 48px, plus an 8px gap). The top-right corner holds every page header's actions (a list's New and Import, a record's Edit and header actions, dashboard controls). Top-centre is the strip a page header leaves free: title on the left, actions on the right. The offset keeps the toast off the top bar's centred global search.
  • A right-side drawer open: unchanged from objectui#11685. The toaster goes back to top-right and steps into the strip beside the drawer, or below the drawer's header.
  • Bottom-right stays unused (objectui#7482: the assistant's FAB and composer). Phone offsets are unchanged.

What changed

  • packages/app-shell/src/chrome/ConsoleToaster.tsx: the default anchor and offset (DEFAULT_OFFSET), and its comments.
  • packages/app-shell/src/chrome/ConsoleToaster.autoDismiss-7482.test.tsx: the default anchor is now ['top', 'center'].
  • packages/app-shell/src/chrome/ConsoleToaster.drawerClearance-11685.test.tsx: with no drawer, top-centre at --offset-top: 56px. With a drawer, top-right as before. The test now re-reads the toaster region, because sonner mounts a new element when the position changes.
  • New packages/app-shell/src/hooks/__tests__/useConsoleActionRuntime.oneToastPerRefusal-13340.test.tsx: the console's own toastHandler, apiHandler and paramCollectionHandler are wired into a real ActionRunner. Only the fetch is faked, answering the create-user door's 400. Checks: one refused Confirm gives exactly one toast.error, each further refusal adds one, and Cancel adds none. Reverse-checked: adding a toast.error in apiHandler's refusal branch turns both tests red.
  • New packages/components/src/renderers/form/__tests__/form-one-toast-per-refusal-13340.test.tsx: one refused submit gives exactly one toast.error, carrying the server's sentence. A refused retry gives one more under the same outcome id, so it replaces the first instead of stacking.
  • .changeset/13340-toaster-top-center.md (@object-ui/app-shell patch).

Tests run

  • pnpm exec vitest run packages/app-shell/src/chrome/ packages/app-shell/src/hooks/__tests__/useConsoleActionRuntime.oneToastPerRefusal-13340.test.tsx packages/app-shell/src/hooks/__tests__/useConsoleActionRuntime.paramRefusal-13161.test.tsx packages/app-shell/src/views/ActionParamDialog.refusalKeepsInput-13161.test.tsx packages/components/src/renderers/form/__tests__/: 99 files passed, 680 tests passed, 17 skipped.
  • The other ConsoleToaster consumers' tests (ObjectView.inviteFeedback-11817, ObjectView.toolbarScriptSelection-12331, plugin-form WizardForm.outcomeToastSupersede): 3 files, 18 tests passed.
  • turbo run type-check --filter=@object-ui/app-shell --filter=@object-ui/components: 31 of 31 tasks successful.
  • node scripts/check-changeset-presence.mjs: pass. pnpm check:new-line-citations: 0 new citations.
  • NOT MEASURED live: the drawer-open position switch in a real browser. The unit tests above cover the mechanism.

Acceptance notes

  • The double-toast half of the card did not reproduce, on origin/main or on the 18.0.0 console. What reads as "×2" is the toast plus the in-form banner or refusal line. Whether a form whose own banner is visible should also toast is a maintainer decision, not made here.

  • I could not read the hotcrm QA report (no access from this session). The forms above were measured on the platform's own Setup objects, not on a HotCRM build.

  • Adds no product-specific widget, dialog, hook or string (see AGENTS.md › Product-neutral renderer)

🤖 Generated with Claude Code

https://claude.ai/code/session_01WJozybDmRH8z2c5SD7vJkj

…ions (objectui#13340)

With no drawer open the console toaster now anchors top-centre, 8px below
the 48px top bar. The top-right corner holds every page header's actions:
at 1440x900 a toast covered a list's New and Import buttons for its 4s life.
A right-side drawer still moves the toaster to top-right and offsets it
clear of the drawer (objectui#11685); bottom-right stays the assistant's
(objectui#7482).

Pins one error toast per server refusal on the two Setup form paths: the
form renderer's submit catch (record forms) and the runner's post-execution
through the console toastHandler (action param dialogs such as Create User
and Add Member). Measured on origin/main against a live 18.0.0 backend:
both paths raise one toast per refusal; no second emitter reproduced.

Refs objectui#13340

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 77 chunks) 3238.5 KB 3744.1 KB
Main entry chunk (gzip) 81.7 KB 350 KB
Entry file index-BE9c8gfb.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 (buildIdentity.js) 0.80KB 0.49KB
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 21.41KB 7.90KB
app-shell (runtime-config.js) 24.52KB 8.48KB
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) 45.59KB 12.03KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 20.67KB 6.12KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.74KB 3.94KB
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) 42.84KB 11.66KB
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) 617.54KB 150.58KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 253.04KB 70.68KB
fields (index.js) 294.97KB 75.45KB
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) 41.40KB 12.01KB
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 (ServerReadScope.js) 4.03KB 1.82KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.99KB 0.43KB
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) 55.56KB 16.21KB
plugin-charts (index.js) 86.08KB 23.67KB
plugin-chatbot (index.js) 238.47KB 56.30KB
plugin-dashboard (index.js) 147.76KB 40.03KB
plugin-designer (index.js) 233.95KB 50.08KB
plugin-detail (index.js) 267.56KB 71.11KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 186.64KB 48.54KB
plugin-gantt (index.js) 178.46KB 45.13KB
plugin-grid (index.js) 277.24KB 77.04KB
plugin-kanban (index.js) 64.49KB 19.83KB
plugin-list (index.js) 149.81KB 39.28KB
plugin-map (index.js) 27.17KB 9.00KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 46.66KB 13.13KB
plugin-timeline (index.js) 39.31KB 11.88KB
plugin-tree (index.js) 15.27KB 5.40KB
plugin-view (index.js) 94.54KB 24.33KB
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) 129.13KB 42.23KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.71KB 2.11KB
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 (product-blocks.js) 0.20KB 0.19KB
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

席位核对

ACCEPT — objectui#13340 (p3, S), head 1b81c0327d7108a9b944d8c9f02ba982b4952cfa, reviewed 2026-10-23T12:42Z.

Seat: domain:ui#1 · Clause-②: no

项 读数
形态 6 个文件,+340/−32,都在 packages/app-shell 和 packages/components 的测试内,加 .changeset/,在认领申报的文件面内。NOT GOVERNED。changeset:@object-ui/app-shell patch。
前提实测 · 重复 toast 没有复现。dev 在真实浏览器里(1440×900,后端为已发布的 @objectstack 18.0.0 Setup 应用)分别用 origin/main 的 console 和 18.0.0 自带的 console 各数一次,每次拒绝都只有一条 toast。业务单元表单只由表单渲染器 toast,新建用户的参数对话框只由 ActionRunner 经 console 的 toastHandler toast,第二个发出点不存在。报告里的「×2」很可能是 toast 加上表单里同一句的错误横幅,横幅不是 toast。去掉其中之一属于设计取舍,不在本卡范围。现在两条路径都钉住了「一次拒绝一条 toast」:往 apiHandler 的拒绝分支加一条 toast,两个测试都会变红。
前提实测 · 位置 属实,已修。没有打开抽屉时,ConsoleToaster 从 top-right 改为 top-center,距顶 56px(顶栏 48px 加 8px 间隙)。1440×900 下 toast 的框原来压住 New 和 Import,现在不再相交。打开右侧抽屉时仍回到 top-right 并让开抽屉(objectui#11685);右下角仍让给助手(objectui#7482);手机端偏移不变。
门禁 dev 本地 vitest 99 个文件 680 项通过,另有 3 个用到 toaster 的文件 18 项通过;@object-ui/app-shell、@object-ui/components type-check 31/31。CI 全绿后由席位转 ready、进合并队列。
接受的偏差 toast 位置的改动是全应用的,不只 Setup。卡面要求「toast 不挡页头操作」,而页头操作都在右上角,所以改锚点是按卡面做的;所有页面共用一个锚点。
落地条款 Closes #13340:位置那一半已修;重复 toast 那一半没有复现,已用钉测锁住单条行为,合并后关卡。如果维护者认为横幅与 toast 重复也算「两次」,那是另一个设计决定,届时另立卡。

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

ACCEPT 6365140544 is suspended · domain:ui seat 1 · 2026-10-23T12:44Z. It names head 1b81c0327d7108a9b944d8c9f02ba982b4952cfa, which is red on Build & E2E: e2e/toast-under-modal.spec.ts (objectui#11723), three tests of the "1440 centred dialog" configuration, all at pathOf (expected corner, received below header). The spec reads the toaster's offset to name its path, and the new no-drawer default (top-centre, 56px) is not a path it knows. The failure is this PR's.

Seat: domain:ui#1
Clause-②: no
File surface: the claim's surface plus e2e/toast-under-modal.spec.ts (teach the path classifier the new no-drawer anchor; every hit-test assertion stays).

The seat re-reviews on the new head and writes a fresh ACCEPT there; nothing is queued on this head.

…bjectui#13340)

`pathOf` classified every `--offset-top` other than 24px as "below header",
so the centred-dialog configuration read the new no-drawer anchor
(top-centre, 56px below the top bar) as the drawer path and failed before
its hit tests ran. It now reads the toaster's anchor (`data-y-position` /
`data-x-position`) first: top-centre at 56px is the no-drawer path
("top centre", formerly "corner"); top-right is the objectui#11685 drawer
path, beside the drawer or below its header. Any other anchor or offset is
named rather than folded into a known path.

Every hit-test assertion is unchanged. In the centred-dialog configuration
the toast now sits over the dialog's title row, and the spec still shows it
is the click target there and that a click on it reaches nothing beneath.

Refs objectui#13340

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJozybDmRH8z2c5SD7vJkj
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT(取代挂起的 6365140544)— objectui#13340 (p3, S), head 091cc2e1dde58ae58f5d39eeff41ed1124d1c0a8, reviewed 2026-10-23T12:46Z.

Seat: domain:ui#1 · Clause-②: no

项 读数
形态 7 个文件(第二个提交补了 e2e/toast-under-modal.spec.ts,按卡上的文件面修订 6365181622),都在 packages/app-shell 和 packages/components 的测试内,加 .changeset/,在认领申报的文件面内。NOT GOVERNED。changeset:@object-ui/app-shell patch。
前提实测 · 重复 toast 没有复现。dev 在真实浏览器里(1440×900,后端为已发布的 @objectstack 18.0.0 Setup 应用)分别用 origin/main 的 console 和 18.0.0 自带的 console 各数一次,每次拒绝都只有一条 toast。业务单元表单只由表单渲染器 toast,新建用户的参数对话框只由 ActionRunner 经 console 的 toastHandler toast,第二个发出点不存在。报告里的「×2」很可能是 toast 加上表单里同一句的错误横幅,横幅不是 toast。去掉其中之一属于设计取舍,不在本卡范围。现在两条路径都钉住了「一次拒绝一条 toast」:往 apiHandler 的拒绝分支加一条 toast,两个测试都会变红。
前提实测 · 位置 属实,已修。没有打开抽屉时,ConsoleToaster 从 top-right 改为 top-center,距顶 56px(顶栏 48px 加 8px 间隙)。1440×900 下 toast 的框原来压住 New 和 Import,现在不再相交。打开右侧抽屉时仍回到 top-right 并让开抽屉(objectui#11685);右下角仍让给助手(objectui#7482);手机端偏移不变。
e2e 上一个 head 的 Build & E2E 红在 e2e/toast-under-modal.spec.ts(objectui#11723):它的路径分类只看偏移,把新的默认位置误判成抽屉路径。现在先读 toaster 的锚点再读偏移,居中对话框这一组改为期望 top centre。所有命中测试断言都没动:toast 压在居中模态框的标题行上时,Undo 仍是点击目标,关闭按钮只关 toast,点 toast 不会穿透到下面。dev 本地 Chromium 跑这个 spec,13 项全过。
门禁 dev 本地 vitest 99 个文件 680 项通过,另有 3 个用到 toaster 的文件 18 项通过;@object-ui/app-shell、@object-ui/components type-check 31/31。CI 全绿后由席位转 ready、进合并队列。
接受的偏差 toast 位置的改动是全应用的,不只 Setup。卡面要求「toast 不挡页头操作」,而页头操作都在右上角,所以改锚点是按卡面做的;所有页面共用一个锚点。
落地条款 Closes #13340:位置那一半已修;重复 toast 那一半没有复现,已用钉测锁住单条行为,合并后关卡。如果维护者认为横幅与 toast 重复也算「两次」,那是另一个设计决定,届时另立卡。

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 77 chunks) 3238.5 KB 3744.1 KB
Main entry chunk (gzip) 81.7 KB 350 KB
Entry file index-CwObu-89.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 (buildIdentity.js) 0.80KB 0.49KB
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 21.41KB 7.90KB
app-shell (runtime-config.js) 24.52KB 8.48KB
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) 45.59KB 12.03KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 20.67KB 6.12KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.74KB 3.94KB
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) 42.84KB 11.66KB
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) 617.54KB 150.58KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 253.04KB 70.68KB
fields (index.js) 294.97KB 75.45KB
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) 41.40KB 12.01KB
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 (ServerReadScope.js) 4.03KB 1.82KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.99KB 0.43KB
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) 55.56KB 16.21KB
plugin-charts (index.js) 86.08KB 23.67KB
plugin-chatbot (index.js) 238.47KB 56.30KB
plugin-dashboard (index.js) 147.76KB 40.03KB
plugin-designer (index.js) 233.95KB 50.08KB
plugin-detail (index.js) 267.56KB 71.11KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 186.64KB 48.54KB
plugin-gantt (index.js) 178.46KB 45.13KB
plugin-grid (index.js) 277.24KB 77.04KB
plugin-kanban (index.js) 64.49KB 19.83KB
plugin-list (index.js) 149.81KB 39.28KB
plugin-map (index.js) 27.17KB 9.00KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 46.66KB 13.13KB
plugin-timeline (index.js) 39.31KB 11.88KB
plugin-tree (index.js) 15.27KB 5.40KB
plugin-view (index.js) 94.54KB 24.33KB
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) 129.13KB 42.23KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.71KB 2.11KB
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 (product-blocks.js) 0.20KB 0.19KB
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
objectstack-fleet Bot marked this pull request as ready for review October 23, 2026 13:01
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 23, 2026 13:01
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 23, 2026
Merged via the queue into main with commit 4603b8c Oct 23, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-13340-one-toast-per-refusal branch October 23, 2026 13:16
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

2 participants