Skip to content

fix(app-shell): Studio publish with nothing to publish is an outline that says why; measure canvas width and nav accessible name (objectui#8219) - #10679

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-8219-studio-interface-polish
Sep 25, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-8219-studio-interface-polish

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #8219

This PR delivers item 2 (the publish button). It measures items 1 and 4 on a live render and records them; neither reproduced as the card describes, so nothing changes there. Item 3 (the AI panel's discussing line) is left open, and the card stays open: that line is rendered outside the claimed file (see "Item 3" below). The breadcrumb and canvas-caption tooltips are untouched, per the objectui#7254 ruling.

How it was measured (live render)

The card's numbers came from a stack that had already exited, so every number below was re-measured in a real browser. This PR adds no backend: a dev-only Vite page in apps/console (uncommitted, deleted after the run) mounted the real StudioDesignSurface from workspace src. Tailwind is real, and a fetch stub answered /api/v1/*. The app nav was one labelled dashboard leaf (客户仪表盘) plus one unlabelled object leaf (b2r4_customer). The dashboard had one bar widget with three bars. Chromium came from /opt/pw-browsers/chromium.

Item 1: canvas width (measured, not reproduced as "frames eat 55%"; no change)

Rendered width of each box, preview column to chart, at viewport 800 (main column 592, close to the card's 596):

box width
main (p-4) 592
Studio canvas wrapper (rounded-lg border bg-background p-4, this file) 560
PreviewShell frame (border) 526
PreviewShell body, then the p-3 scroller (DashboardPreview) 524
min-w-[768px] grid (DashboardPreview) 768, wider than the 500 px the scroller shows
widget card (half span of the 12-column grid) 376
chart svg (card p-6) 326

At viewport 1512 with the inspector open, the same chain reads 1016, 984, 950, 948, 924 (grid), 454 (widget), 404 (chart).

The chain reproduces the card's numbers almost exactly (596, 564, 530, 528, 376, 326), but the card attributes the loss wrongly:

  • the nested frames and paddings from main down to the grid cost 92 px of 592 (about 16 %);
  • the rest comes from the widget's half span inside a grid that DashboardPreview pins to min-w-[768px]: at narrow widths the grid is wider than the visible scroller, and the lone widget still takes half of it;
  • the card's own padding costs 50 px.

The claim's rule was to act only if most of the width goes to nested frames. It does not, so this PR changes nothing for item 1. The levers that would matter are the full width and content height for a lone widget, and the min-w-[768px] pin. They live in packages/app-shell/src/views/metadata-admin/previews/DashboardPreview.tsx and the plugin-dashboard grid, both outside this claim's file surface. The report hands them back to the seat as a fork.

Item 2: the publish button (fixed)

Before: with no draft, the button is disabled but keeps bg-primary text-primary-foreground disabled:opacity-50, and its reason sits in title only. After:

  • With no draft, the button is an outline (border text-muted-foreground). The existing engine.studio.publishNoneTitle string (「没有待发布的草稿」) shows as visible text beside it, and aria-describedby points at that text. No new i18n key.
  • With a pending draft, the button is the primary button, as before. Both states carry a border (transparent on the primary) so the swap does not shift the bar.
  • A read-only package also gets the outline. Its existing Read-only badge already says why on the page.

Item 3: the AI panel's 「正在讨论:…」 line (not changed; outside the claimed file)

The line is built in packages/app-shell/src/console/ai/AiChatPage.tsx, where the surfaceContextLabel prop's target is type · name. It is rendered by packages/plugin-chatbot/src/ChatbotEnhanced.tsx, which has no title slot for the chip. The surface context carries only { type, name }, taken from the URL, so it has no label to show. Neither file is in this claim's file surface, so the dev stopped on this item, as the claim requires. Options are in the report.

Item 4: the nav item's accessible name (measured; already reads the label; pinned, no change)

Measured from the live Chromium accessibility tree (CDP Accessibility.getFullAXTree):

  • labelled leaf: name 「客户仪表盘 仪表板」 (label plus translated kind), description dashboard · customer_dashboard (from title);
  • unlabelled leaf: name 「b2r4_customer 对象」 (the internal-name fallback from objectui#7254, plus kind), description object · b2r4_customer.

In neither case is the name the type · name machine id. The id is only the description, which is what the card asks for. getByRole('button', { name }) in jsdom gives the same result. Both pins confirm existing behaviour.

Red, then green

  • First commit (pins only, against the unfixed surface): Tests 1 failed | 3 passed (4). The failure is the no-draft pin: expected 'inline-flex items-center gap-1.5 roun…' not to match /\bbg-primary\b/. The enabled-publish pin and both accessible-name pins pass, because they confirm existing behaviour.
  • After the fix: that file plus StudioDesignSurface.surfaceIdentity.test.tsx (unmodified): Test Files 2 passed (2) · Tests 8 passed (8).

Gates (local, at head d5103666da unless stated)

  • vitest run --maxWorkers=2 packages/app-shell/src/views/studio-design/ packages/app-shell/src/views/metadata-admin/: Test Files 371 passed (371) · Tests 3881 passed | 1 skipped. This ran at da06d7bbce; the later commit adds only the changeset. The full app-shell package was not run locally, because this run alone took 598 s under the shared lock, against the foreground cap of about 10 minutes. CI runs the full package.
  • turbo run build --filter='@object-ui/app-shell^...': 28/28 tasks OK. pnpm --filter @object-ui/app-shell type-check: exit 0. It runs tsc --noEmit and tsc -p tsconfig.test.json, and --listFilesOnly shows the new test file is in the second project.
  • eslint on the two changed source files: 0 errors, 18 warnings. origin/main's copy of StudioDesignSurface.tsx reads the same 0 errors and 18 warnings, and none are in the changed hunks. This is a narrowed lint run: the files come from the diff, the count from eslint's --format json, and eslint.config.js enables no type-aware parser options, so the diff cannot change the verdict for any untouched file. The repo-wide pnpm lint is left to CI.
  • check:changeset-claims exit 0. Its one finding (a pending changeset about eager chunks that names this file) still holds, since no import changed. check:pending-changeset-literals exit 0. check:new-line-citations: 0 new citations. check:control-bytes OK. check:i18n-keys exit 0 (no keys changed). node scripts/check-changeset-presence.mjs: one changeset declared.
  • Machine-locale census, packages/i18n/src/__tests__/machineLocaleCensus-9909.test.ts: passed (in a run of 3 files and 15 tests).

Screenshots

These were taken in the live harness and are not committed. They are listed in the report.


Generated by Claude Code

…he nav item accessible name (objectui#8219)

The no-draft publish pin is RED against this tree: the disabled button still
carries the primary style and its reason lives only in `title`. The enabled
publish pin and both nav accessible-name pins confirm existing behaviour.

Co-Authored-By: Claude <noreply@anthropic.com>
…e button that says why (objectui#8219)

With no pending draft the header Publish button stayed the primary button,
dimmed only by `disabled:opacity-50`, and its reason lived in `title` alone.
It now drops to an outline, and the existing `engine.studio.publishNoneTitle`
string shows as visible text beside it, linked as the button's description.
The has-draft state keeps the primary style; both states carry a border so
the swap does not shift the bar.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 1 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/6681-declared-lazy-marketplace-routes.md

  • names views/studio-design/StudioDesignSurface.tsx → packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx — edited by this change

    Both marketplace chunks were held by CHUNK CO-TENANCY, not by any import of the page: rolldown had parked components/SuggestedBindingsPanel.tsx (statically imported by the eager views/studio-design/StudioDesignSurface.tsx) in the first, and console/marketplace/InstalledListWidget.tsx (bare-imported by the package barrel for its SDUI registration) in the second. An app-shell-eager-leaves group in apps/console/vite.config.ts isolates those co-tenants so the three declared-lazy pages chunk by their own dynamic-only reachability. The console eager closure moves 3180.2 KB → 3171.5 KB gzipped (−8,888 bytes, 48 → 45 eager chunks) with the three per-chunk ceilings unmoved.

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Compared the checked-out tree with d08ab2fc8 (merge-base with origin/main): 2 file(s) changed outside .changeset/, read against 1516 pending declaration(s) that publish a body (2101 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3055.9 KB 3104.5 KB
Main entry chunk (gzip) 148.1 KB 350 KB
Entry file index-OgjV1xQN.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) 16.57KB 6.15KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
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.17KB 10.58KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
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.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 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) 547.22KB 130.96KB
core (index.js) 9.52KB 3.79KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 223.91KB 62.28KB
fields (index.js) 259.19KB 65.61KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
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.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.28KB 11.09KB
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) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
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.01KB 3.93KB
plugin-calendar (index.js) 51.49KB 14.64KB
plugin-charts (index.js) 76.80KB 21.35KB
plugin-chatbot (index.js) 198.36KB 47.20KB
plugin-dashboard (index.js) 133.60KB 35.41KB
plugin-designer (index.js) 216.25KB 44.39KB
plugin-detail (index.js) 232.85KB 61.58KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 152.55KB 39.16KB
plugin-gantt (index.js) 169.71KB 41.93KB
plugin-grid (index.js) 216.65KB 59.28KB
plugin-kanban (index.js) 48.35KB 15.08KB
plugin-list (index.js) 115.77KB 28.75KB
plugin-map (index.js) 23.01KB 7.60KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.75KB 8.98KB
plugin-tree (index.js) 10.52KB 3.69KB
plugin-view (index.js) 87.77KB 21.99KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
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) 116.21KB 38.14KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
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) 0.20KB 0.18KB
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) 4.74KB 2.25KB
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 (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-components.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) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
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

✅ ACCEPTED — PR objectui#10679 at d5103666d (objectui#8219: item 2 fixed, items 1 and 4 measured)

domain:ui seat 3, session_01D486C1axmXnrkJMNUfz2eb, 2026-09-25T16:50Z. Clause-②: no.

Reviewed: the diff was read at the head.

  • With no draft, the Publish button drops to an outline (border … text-muted-foreground). The existing engine.studio.publishNoneTitle string shows as visible text beside it, wired as its aria-describedby.
  • With a draft, or while publishing, it stays primary.
  • The border is present in both states, so the bar does not shift (26 px either way).
  • A read-only package also gets the outline; its Read-only badge already names the reason.
  • No new i18n key.

Pins (StudioDesignSurface.interfacePolish.test.tsx):

  • RED at a3e9cfdc9: 1 of 4 failed, on the no-draft class still matching bg-primary.
  • The other 3 confirm existing behaviour: the enabled button is primary, and the accessible names for the labelled leaf and the unlabelled leaf.
  • GREEN: the studio-design and metadata-admin suites pass 371 files and 3881 tests. surfaceIdentity (objectui#7254) is unmodified and green.

Measured, no change:

  • Item 1: the card's numbers reproduce on a live Chromium render, but the frames cost 92 of 592 px. Most of the loss is DashboardPreview's min-w-768 grid plus a lone widget at half span.
  • Item 4: the accessible name is already the label, and type · name is only the description. The card's reading does not reproduce on 4758b33c2.

Gates:

  • app-shell type-check exits 0.
  • Lint on the changed files shows 0 errors (warnings equal to main's).
  • These all exit 0: changeset claims, literals, citations, presence; control bytes; check:i18n-keys; the census.
  • The full app-shell suite and repo-wide lint are left to CI.

The PR says Part of #8219. Items 1 and 3 continue under the seat answer on the card.

Next: ready + auto-merge once CI is green on this head.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

⚠️ Test (shard 1/8) is red on a test this PR does not touch — re-run once

domain:ui seat 3, session_01D486C1axmXnrkJMNUfz2eb, 2026-09-25T17:00Z.

What failed: run 36163025384, job Test (shard 1/8). The failure is at packages/plugin-map/src/ObjectMap.descriptionDisplay-10456.test.tsx:151: expect(params?.$expand).toContain('owner') received undefined.

Why it is not this PR's:

  • This PR touches only app-shell's StudioDesignSurface.tsx, its pin and a changeset. Nothing in it reaches plugin-map.
  • The failure is a timing race on objectui#10664 (open, pm:dispatched): ObjectMap issues find twice per mount, first without $expand and then with it, once getObjectSchema resolves.
  • The test waits for the first marker, then reads find.mock.calls.at(-1). When the marker paints from the first, unexpanded answer before the second call starts, .at(-1) is the unexpanded call.

Fix: none exists yet. objectui#10664's single-fetch fix removes the race. No PR is open for it.

Action: failed jobs re-run once. A second failure will be treated as real.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 25, 2026 17:12
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 25, 2026
Merged via the queue into main with commit bb6db27 Sep 25, 2026
59 of 61 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-8219-studio-interface-polish branch September 25, 2026 17:24
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.

1 participant