Skip to content

feat(app-shell): Studio's app, permission and view previews show the authored area descriptions, RLS policy labels and view label (objectui#11027) - #11098

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-11027-preview-display-keys
Sep 29, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-11027-preview-display-keys

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #11027
Clause-②: no

Four display keys that @objectstack/spec parses and publishes reached no human anywhere in Studio. Each designer preview now renders its key where it was authored, and renders nothing where it was not.

What changed

All under packages/app-shell/src/views/metadata-admin/.

key preview now
app.areas[].description AppPreview An areas list under the app header, in read and design mode: each area's label and id, with its description beneath it. label and description are the spec's I18nLabel, so both go through the spec's resolveI18nLabel in the designer locale (the resolver DashboardPreview already uses).
permission.rowLevelSecurity[].label PermissionPreview A policy list under the existing perm.rls.title heading ("Row-Level Security" / "行级安全"): each policy's name, with its label beside it. The header pill still counts them.
permission.rowLevelSecurity[].description PermissionPreview Beneath its policy's row.
view container label ViewPreview The preview heading (an h3 above the rendered view) on the list route, the form route and the no-object state. No one-entry tab strip. The label is I18nLabel and is resolved the same way.
  • An unauthored key renders nothing: no stand-in text. The area id and the policy name are required identifiers and always show, the way the headers already show the app and permission-set names.
  • A permission set whose only grants are policies used to show the "grant at least one permission" empty state, which would have hidden the policies. The empty-state test now also asks whether rowLevelSecurity is empty.
  • New chrome: one designer row, engine.appPreview.areas ("Areas" / "区域", matching the runtime sidebar's sidebar.area), in the designer table's en and zh halves. The policy list reuses perm.rls.title.
  • Changeset: .changeset/11027-preview-display-keys.md, @object-ui/app-shell patch.

Premises, re-measured at origin/main a5841be35

  • AppPreview.tsx: grep -c areas = 0, with a control of grep -c navigation = 8 on the same file.
  • PermissionPreview.tsx: rowLevelSecurity was read into rls and used only for the count pill.
  • ViewPreview.tsx: the label was relayed only into the one injected named listView. ObjectView's renderNamedViewTabs returns null when entries.length is at most 1. The reverse run below measured it through the real object-view stack: with the fix reverted, the authored label is not a heading anywhere on the page.
  • Key types: area label and description and the view item label are I18nLabel. The RLS policy label and description are plain z.string() in the installed spec 17.4.0 and in objectstack origin/main rls.zod.ts. So the permission pin has no locale-map case, and a non-string value renders nothing instead of being stringified.

Evidence

The final head is 335a6c38c. Every reading below is at that sha unless it says otherwise. Heavy runs went through the shared verify lock.

  • Build. pnpm exec turbo run build --filter='@object-ui/app-shell^...' --concurrency=2: exit 0, 28 tasks, 12 of them cached.
  • Type-check. pnpm --filter @object-ui/app-shell type-check (tsc --noEmit && tsc -p tsconfig.test.json): exit 0. tsc -p tsconfig.test.json --listFilesOnly lists all three new pins.
  • Tests. pnpm exec vitest run --maxWorkers=2 packages/app-shell/src/views/metadata-admin/previews/ plus the three packages/i18n tests that read the designer table (appDesigner-fieldDesigner-formula-retired-6310, dashboard-emptyState-keys-retired-7125, perm-home-namespace-3546): Test Files 82 passed (82), Tests 1108 passed (1108). The run also included one uncommitted scratch probe (1 file, 1 test; see Acceptance notes), so the committed tree accounts for 81 files and 1107 tests.
  • The pins, one per preview:
    • previews/AppPreview.areas-11027.test.tsx: authored, absent (per area and per app), a locale map in zh-CN and en-US, the zh heading row, and design mode.
    • previews/PermissionPreview.rlsPolicies-11027.test.tsx: authored, absent (per policy and per set), a policies-only set, and the zh heading row.
    • previews/ViewPreview.containerLabel-11027.test.tsx: through the real object-view registration. Authored, absent (the heading set equals the authored set minus the label, so nothing stands in for it), a locale map in zh-CN and en-US, and the form route.
  • Reverse verification at 0edb411f1: the three preview sources were checked out at BASE a5841be35 and the pins kept at head. The mutation was proven on disk before the run: each file hashed to its BASE blob, and the markers readAreas / readPolicies / ViewLabelHeading counted 0. Result: 11 failed, 2 passed of 13. The 2 green cases are the two per-app and per-set absent cases, which is the expected direction. Every authored, locale-map and heading case went red. Restoration used git checkout HEAD --, was proven by HEAD blob hashes and an empty git diff HEAD, and ran from an EXIT/INT/TERM trap. The one commit after 0edb411f1 (335a6c38c) changes types only: it reads draft.areas off the already-typed draft, and it types the view pin's stub adapter. The reverse run was not repeated for it.
  • Lint, narrowed to the diff. pnpm exec eslint --format json over the 7 changed .ts/.tsx files: 7 files, 0 errors, 29 warnings.
    • Every warning is already on main. Per-file warning counts, BASE to head: AppPreview.tsx 8 to 8, PermissionPreview.tsx 3 to 3, ViewPreview.tsx 18 to 18, i18n.ts 0 to 0. The three pins have 0.
    • Population: eslint.config.js lints **/*.{ts,tsx} minus its ignores (build output). The changeset .md is outside that population.
    • Invariance: the config sets no parserOptions.project or projectService, so linting is not type-aware and this diff cannot change the verdict on any file it does not touch.
    • The repo-wide pnpm lint run is CI's.
  • Gates (node scripts/…), each exit 0, each quoted by its own verdict line:
    • check-i18n-call-site-keys, check-i18n-designer-table-parity ("Every en row has a zh row, and every shared row carries the same placeholders."), check-i18n-en-drift (the designer table: 1 key added, 0 en values changed);
    • check-changeset-presence (7 source files of 1 released package, 1 changeset), check-changeset-no-major, check-changeset-claims (report-only; the three pending changesets it names for i18n.ts were read, and all three paragraphs still hold), check-pending-changeset-literals;
    • check-control-bytes, check-new-cross-file-line-citations (0 new), check-designer-field-key-parity, check-test-path-roots.
  • Not measured locally: the full pnpm test and pnpm lint runs (they belong to CI), and a real browser. The previews were read through React Testing Library DOM only.

Acceptance notes

  • Finding, class a, not in this diff. AppPreview mishandles two other I18nLabel keys it already reads:
    • The app header prints [object Object] for a locale-map label, because it uses String(label).
    • normalizeNav keeps a nav item's label only when it is a string. A childless nav item with a map label is dropped from the list, together with its target. A group with a map label reads "(unnamed)".
    • Measured by an uncommitted render probe of the Studio app preview at 335a6c38c. A plain-string nav label on the same mount rendered, as the control.
    • Left out of this diff: the card's scope is the four unrendered keys. The next change queued on that file is objectui#10862.
    • Dedupe words: AppPreview app label object Object · AppPreview navigation label locale map unnamed · app preview I18nLabel resolveI18nLabel nav label.
  • Claim wording vs this repo. The claim says a new chrome key would land in the packages/i18n locale packs. The designer's engine.* rows live in its own table (metadata-admin/i18n.ts, en and zh), which is the only table the designer t() reads (packages/i18n/README.md, "Scope — the engine.* carve-out"). The new row went there, and no pack changed.
  • The relay of the label into the injected listView is unchanged. It still renders nowhere on this route. Removing it is not this card's business.

Implemented by domain:ui seat 2 in session https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec, on branch claude/issue-11027-preview-display-keys.


Generated by Claude Code

…authored area descriptions, RLS policy labels and view label (objectui#11027)

- AppPreview lists AppSchema.areas: each area's label, id and, when authored,
  its description, resolved in the designer locale through the spec's
  resolveI18nLabel. New designer row engine.appPreview.areas (en, zh).
- PermissionPreview lists each rowLevelSecurity policy by name, with its
  label and description when authored, under the existing perm.rls.title
  heading. A set whose only grants are policies no longer shows the empty
  state.
- ViewPreview draws the view's own label as the preview heading on the
  no-object, form and list routes; an unauthored label draws nothing.
- One pin per preview: authored, absent and, where the key is I18nLabel,
  a locale-map case.

Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec
Co-authored-by: Claude <noreply@anthropic.com>
…view pin's stub adapter (objectui#11027)

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

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 3 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/6310-designer-formula-key-retired.md

  • names packages/app-shell/src/views/metadata-admin/i18n.ts → packages/app-shell/src/views/metadata-admin/i18n.ts — edited by this change

    Not touched: designer.field.formula ('Formula (CEL)') in packages/app-shell/src/views/metadata-admin/i18n.ts, a different and live key belonging to metadata-admin's ObjectFieldInspector — the surface that still authors formula expressions.

.changeset/7125-dashboard-empty-state-keys-retired.md

  • names packages/app-shell/src/views/metadata-admin/i18n.ts → packages/app-shell/src/views/metadata-admin/i18n.ts — edited by this change

    Not touched: table.noRows ('No rows to display') and engine.form.noRows (packages/app-shell/src/views/metadata-admin/i18n.ts, read at widgets.tsx) — two different, same-named keys in different namespaces. Nor the comments in WidgetEmptyState.tsx, DatasetWidget.tsx, ObjectDataTable.tsx and PivotTable.tsx that record WHY three widgets with three strings became one shared empty state; the packs' own comment keeps that rationale and now names the retirement instead of a row that is gone.

.changeset/8632-malformed-picklist-option-loud.md

  • names views/metadata-admin/i18n.ts → packages/app-shell/src/views/metadata-admin/i18n.ts — edited by this change

    Two new strings land in the designer's own en / zh tables — the metadata-admin console owns its strings in views/metadata-admin/i18n.ts and is deliberately outside the ten locale packs (packages/i18n/README.md, "Scope — the engine.* carve-out").

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 17fc68873 (merge-base with origin/main): 7 file(s) changed outside .changeset/, read against 1748 pending declaration(s) that publish a body (2352 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3109.3 KB 3149.4 KB
Main entry chunk (gzip) 149.3 KB 350 KB
Entry file index-DWFTIa0M.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.88KB 6.25KB
app-shell (runtime-config.js) 20.68KB 7.36KB
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) 18.17KB 5.40KB
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.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
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.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) 558.81KB 133.93KB
core (index.js) 9.94KB 3.94KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 227.99KB 63.22KB
fields (index.js) 261.11KB 66.33KB
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 (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.35KB 9.18KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.32KB 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.58KB 4.90KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.25KB 2.17KB
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) 52.17KB 15.06KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 137.68KB 36.71KB
plugin-designer (index.js) 215.78KB 44.42KB
plugin-detail (index.js) 233.64KB 61.85KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 169.69KB 43.37KB
plugin-gantt (index.js) 171.88KB 42.61KB
plugin-grid (index.js) 228.51KB 62.64KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 115.92KB 28.65KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.04KB 12.21KB
plugin-timeline (index.js) 31.07KB 9.15KB
plugin-tree (index.js) 11.21KB 3.89KB
plugin-view (index.js) 90.43KB 22.76KB
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.81KB 3.58KB
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) 119.55KB 39.23KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
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) 8.61KB 3.45KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.17KB 2.73KB
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.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 22.61KB 7.40KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 3.83KB 1.49KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 3.19KB 1.62KB
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.26KB
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) 19.30KB 6.99KB
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

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 335a6c38cb81d90d2700e3f3e3ecabf143466149
Local-runs: none

Inputs: card #11027 (body, triage 5881560418, claim 5889511891, os-dev-report 5890531848), PR #11098 (body, 8-file list, net diff against main, +495/−2), and the check-runs on the head with filter=latest. Context reads at the head and at the PR base 17fc68873 through contents/…?ref=: the three preview sources, preview-registry.ts, metadata-admin/i18n.ts, packages/i18n/README.md, .github/workflows/ci.yml and lint.yml, packages/app-shell/package.json, and at objectstack main the spec's app.zod.ts, rls.zod.ts, view.zod.ts and ui/i18n-label-resolver.ts. Nothing built, run or re-run.

Binding direction: triage 5881560418 over the card body. Judged against it below.

① Derived judgments

Every accept-set and public-surface change the diff implies, each named right or wrong:

  1. AppPreview reads draft.areas and lists each area (label, id, description). draft is Record of string to unknown in MetadataPreviewProps, so readAreas(raw: unknown, locale) is a typed read with no cast; a non-array, a non-object entry, an empty string and an off-contract non-map value all render nothing. label and description are I18nLabelSchema on the spec's NavigationAreaSchema (label required, description optional), and both go through the spec's resolveI18nLabel(label, locale), which returns undefined for an absent value and refuses a non-map instead of stringifying it; the preview then folds '' to undefined. The list draws in read and design mode, under the new engine.appPreview.areas heading, and is omitted entirely when the app declares no areas. No prop, export, type or accepted authoring key moves. Right. (Nit, not a finding: key={i} index keys on a static authored list are fine here.)
  2. PermissionPreview lists each rowLevelSecurity policy under the existing perm.rls.title row (en Row-Level Security / zh 行级安全). authoredText keeps a value only when it is a non-empty string; the spec's RowLevelSecurityPolicySchema declares label: z.string().optional() and description: z.string().optional() (name required, snake_case), so the string-only guard is the key type, not a narrowing, and the pin's lack of a locale-map case is correct. The count pill is unchanged. Right. (Observation only: the pill counts rls.length while the list counts object entries, so a malformed non-object entry, which the spec rejects anyway, would count without listing. No change asked.)
  3. ViewPreview draws the container label as an h3 heading (ViewLabelHeading) above the rendered body on the no-object, form and list routes; resolveI18nLabel(draft.label, locale) (the spec's ViewSchema has label: I18nLabelSchema.optional()), trimmed, and null when absent or blank. listViews still carries one injected entry, plugin-view is untouched, and renderNamedViewTabs therefore still returns nothing for one entry: no one-entry tab strip came back. Right. The raw single-schema route (a bare { type } draft with no ViewItem wrapper) draws no heading; that draft is not a view container, so no container label is owed there. Right. The pre-existing relay of the label into the injected listView is left as is (see ③).
  4. Designer chrome. One new row engine.appPreview.areas (Areas / 区域) in ENGINE_STRINGS_EN and ENGINE_STRINGS_ZH; the policy list reuses perm.rls.title, present in both halves. Both previews read chrome through the designer t(). Right (placement judged in ③a).
  5. Imports. resolveI18nLabel from @objectstack/spec/ui in AppPreview and ViewPreview: the same subpath DashboardPreview.tsx already imports at the head, and @objectstack/spec ^17.4.0 is already an app-shell dependency. No new dependency, no new public surface. Right.
  6. Out-of-scope kept out. The file list touches no packages/i18n pack, no flow.* / hook.* reader, adds no HookPreview, and changes nothing under objectstack's liveness ledger. Right.
  7. Pins, one per preview, authored and absent both. AppPreview.areas-11027.test.tsx: authored, absent per area (Servicearea_service, nothing more) and per app (no heading, no li), locale map in zh-CN and en-US with a [object Object] refusal, the zh heading row, design mode. PermissionPreview.rlsPolicies-11027.test.tsx: authored, absent per policy (tenant_wall alone) and per set, policies-only set, zh heading row. ViewPreview.containerLabel-11027.test.tsx: through the real registered object-view renderer over a stub adapter; authored heading, absent (the heading set equals the authored set minus the label, so nothing stands in), locale map both ways, form route. Every "absent" case asserts the absence of invented text, which is what the direction's control demands. Right. The dev's reverse run (11 of 13 red on the base sources, the two per-app/per-set absent cases green) is the expected direction; I did not repeat it.

② Semver level

.changeset/11027-preview-display-keys.md declares '@object-ui/app-shell': patch. @object-ui/app-shell is a published package (no private, 17.6.0). The diff adds rendering inside three Studio preview components and one designer-table row; it changes no export, prop type, schema, accept set or i18n pack, so patch matches what the diff publishes. No @object-ui/i18n changeset is due: the claim's "plus @object-ui/i18n if a key is added" was conditional on a pack key, and no pack changed (③a). The PR body carries Clause-②: no, matching the claim's Clause-②: no with its "Why no" (no type, schema, export or accept set moves), and the diff bears that out. Head check-runs on the changeset family: Changeset Declaration, Changeset Bump Policy, Changeset Fixed Group Check, Changeset Claim Re-read and Changeset Overwrite Report, all success.

Clause-②: no

③ Boundary flags

open_questions: none declared, none found. The dev's flags:

  • (a) Chrome key placement, judged: the deviation is required, and the claim's wording was wrong for this repo. The claim and the order say a new pack key goes to packages/i18n. The designer t() is pickTable(locale).strings[key] ?? key, and pickTable returns only the module-local ENGINE_STRINGS_EN / ENGINE_STRINGS_ZH tables; it reads no i18next namespace and no pack. packages/i18n/README.md, "Scope — the engine.* carve-out", states the same from the pack side: no engine.* key exists in any of the ten packs, all-locales-key-parity compares pack against pack, and check-i18n-call-site-keys skips this module by declaration. A key placed in a pack would have been unreachable from the preview. The row therefore went where every sibling engine.appPreview.* row lives, in en and zh, which is what "through the designer locale, like the other preview strings" asks. The gates that read this table are check:i18n-designer-parity (en is a subset of zh, shared placeholders match) and check:i18n-drift (its designer-table population): both are steps of ci.yml's type-check job, so their CI verdict is the head's Type Check check-run, named in the check-run section below. The pin also asserts the zh row differs from both the key and the en row.
  • (b) rls.length === 0 in the empty-state condition, judged: required for the direction, not a widening. At the base the empty message fired whenever a set had no objects, no system permissions and no tab permissions; a policies-only set therefore returned before any section and its authored label / description could never reach a human, contradicting "each key visible in its preview when authored". The only behavior that moves is for sets carrying at least one policy and nothing else: they now render the header and the policy list instead of the message. The message row itself (engine.permissionPreview.empty, en and zh) is unchanged and still accurate, since the matrix is still absent for such a set. Pinned by the policies-only case.
  • (c) Lock timeouts and a 99 queue-timeout retry; model-free commit trailers per AGENTS.md. Process notes with no contract content. Nothing to escalate.
  • (d) Class-a out-of-scope finding on AppPreview (a locale-map app label prints [object Object]; normalizeNav drops a childless nav item with a map label; a map-label group reads (unnamed)), judged: pre-existing and not made worse, and the diff's own area rendering avoids it. At the PR base, the header is String(label) (base line 262), normalizeNav keeps a label only when typeof it.label === 'string' (base line 128) and falls back to label || unnamed (base line 148). At the head those three sites are byte-identical (head lines 304, 137, 157); the diff adds nothing to that path. The new readAreas resolves both label and description through resolveI18nLabel and never through String() (head lines 186–187), and the areas pin's locale-map case asserts document.body holds no [object Object] in zh-CN and en-US. The finding is outside this card's four keys; the dev left it for the seat to raise, naming objectui#10862 as the next queued change on the file and giving dedupe words. Escalated to the seat to file as its own card; it does not move this verdict.
  • (e) "承接者:无": the relay of the label into the injected listView stays, rendering nowhere on this route. Unchanged from the base, harmless, and the direction asked for a visible heading rather than the relay's removal. Answered: leave; not this card.

Check-runs on 335a6c38cb81d90d2700e3f3e3ecabf143466149 (filter=latest):

All 43 runs completed by 2026-09-29T12:54Z (read at 12:44Z with 13 in progress, re-read until none remained): 40 success, 3 skipped, 0 failure, 0 in progress. By gate family, each conclusion the verdict rests on:

  • Type Check: success. pnpm type-check (app-shell's tsc --noEmit and tsc -p tsconfig.test.json, which compiles the three pins) plus the steps check:i18n-keys, check:i18n-drift and check:i18n-designer-parity: the designer-table parity and drift gates of ③a are green here.
  • Lint: success. Repo-wide pnpm lint, the vi.mock override-shape check, the test-path-roots check and the CLI self-check.
  • Test (shard 1/8) through Test (shard 8/8): success, each; Test: success (the aggregate); Test (dist pins): success. The three new pins run in these shards.
  • Build & E2E: success. Build Docs: success. Bundle Analysis: success. Spec Main Shape Gate: success.
  • Changeset Declaration, Changeset Bump Policy, Changeset Fixed Group Check, Changeset Claim Re-read, Changeset Overwrite Report: success (the ② family).
  • Governed Surface Queue Guard, Line Citation Gate, Control Byte Scan, Inert vi.mock Specifier Check, Pre-Install Import Graph Check, Shell Escape Residue Scan, README Export Check, Action Ref Convention, Doc Component Type Check, Doc Snippet Type Check, Doc Example Id Check, Doc Fence Language Check, Internal Docs Link Check, Docs Route Eager Closure Check, Skill Example Check, Skill Eval Token Check, Skill Guide Path Check, Live E2E (informational), label: success.
  • Skipped (not applicable to this head): dependabot, Test (coverage shard N/4, the unexpanded matrix name), Test (coverage).

Implemented-by: claude/issue-11027-preview-display-keys
Reviewed-by: session_011p7ikEivgXefNDaE5S5Uec

VERDICT: PASS


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.

metadata-admin previews: render an app area's description, each RLS policy's label / description, and a view container's label

2 participants