Skip to content

fix(app-shell): a report's or dataset's Label with no machine name leaves the Name empty (objectui#12789) - #12806

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-12789-label-derives-empty-name
Oct 16, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-12789-label-derives-empty-name

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #12789
Clause-②: no (designer only; no export, prop or type changes)

What changes

In create mode, the report and dataset designers derive the Name from the Label until the author types a Name. They derived it with toFieldName, which returns the placeholder field when nothing is left. So a Label in a non-Latin script (收入), or a cleared Label, named the report or dataset field without telling the author. The dataset then saved as field (reading below), and a second such item would collide with it.

Both Label handlers now derive with toFieldNameOrEmpty, a new function beside toFieldName in previews/object-fields-io.ts. It applies the same strict rule, but returns '' where toFieldName returns field. The Name stays empty, as it does in the object designer (whose slugify returns ''), and the save path asks the author for a name. nameFollowsLabel compares the stored Name with the same function. So:

  • a Name left empty by a non-Latin Label still follows a Latin Label typed later;
  • a Name the author typed is never taken for the placeholder. On main, a Name typed as field next to a non-Latin Label was overwritten by the next Label edit.

A Label that yields a machine name derives exactly what it derived before: the strict form, no trailing _, the f_ prefix for a leading digit, and field from the Label Field.

toFieldName keeps its field fallback. Its body is now toFieldNameOrEmpty(raw) || 'field', which returns the same result for every input. Its existing pin in object-fields-io.test.ts is unchanged and green: toFieldName('') and toFieldName('报修工单') both return field. Its other callers:

  • CreateItemDialog (studio-design) relies on the fallback: it treats a finalName equal to field as "the input yielded no identifier" and refuses to submit.
  • genGroupKey never reaches the fallback: it calls toFieldName only when the label has an ASCII letter or digit, and uses group otherwise.
  • ObjectFormCanvas re-exports it, and nothing in the repository imports that re-export.

The new function lives in a module that the package's exports (. only) and its src/index.ts barrel do not name. So the package entry is unchanged, as the Clause-② line says.

Files: ReportDefaultInspector.tsx and DatasetDefaultInspector.tsx (the import, the nameFollowsLabel comparison, the Label handler's derivation, and comments), previews/object-fields-io.ts (the new function, with toFieldName delegating to it), one new test beside each inspector, a toFieldNameOrEmpty block in object-fields-io.test.ts, and .changeset/12789-report-dataset-label-leaves-name-empty.md (@object-ui/app-shell patch).

Browser readings (Chromium, real keystrokes)

Backend: objectstack 66d456d01 examples/app-crm, objectstack dev --seed-admin --fresh on a private port. A writable package was created with POST /api/v1/packages {"manifest":{"id":"com.qa.vrf",…,"namespace":"qavrf"}} → 201. Console: this branch's worktree on Vite, proxied to that backend. "Before" was read at 8df2608 (unchanged main, before any edit). "After" was read at e4b532b, on the same dev server after the edit. Pages: /apps/crm_app/metadata/{report,dataset,object}/new?package=com.qa.vrf.

The sequence on a fresh form: type the Label 收入, then Tab. Then clear the Label. Then type Revenue Board and Q into the cleared Label.

designer Name after Label 收入, before → after Name after the Label is cleared, before → after Latin Label typed onto the cleared one (mid / final), before and after
report field → empty field → empty revenue_board / revenue_board_q (unchanged)
dataset field → empty field → empty revenue_board / revenue_board_q (unchanged)
object (control) empty → empty empty → empty revenue_board / revenue_board_q (unchanged)

On a second fresh form, the Label was typed as Revenue, then replaced with 收入, then with Revenue Board. The Name read revenue → field → revenue_board before, and revenue → empty → revenue_board after, in both designers. The object designer read revenue → empty → revenue_board both times.

What Save says (Label 收入, the Name left as derived, Save pressed)

designer before (8df2608): Name field after (e4b532b): Name empty
report PUT /api/v1/meta/report/field?mode=draft&package=com.qa.vrf → 422. The name passed; the request was refused only because no dataset was bound ("a report needs dataset + values (measure names).") no request. Banners: "A name is required." and "This metadata does not match the spec — 2 validation error(s).", with Name "Identifier must be at least 2 characters" and "Identifier must be lowercase snake_case, starting with a letter, and may contain only letters, numbers, and underscores"
dataset PUT /api/v1/meta/dataset/field?mode=draft&package=com.qa.vrf → 200, "Saved dataset 'field' (env-wide, package=com.qa.vrf, state=draft)" no request. Banner: "A name is required."
object (control) no request. "A name is required." and the spec banner (1 error: Name must match ^[a-z_][a-z0-9_]*$) the same

So an empty Name asks the author for one: the host's save path refuses an empty identity ("A name is required.") before any request is sent, and the client spec check names the identifier rule.

Tests

New, one beside each inspector. Each renders the inspector with a host double that merges every patch back into the draft, and types real keystrokes with user-event:

  • ReportDefaultInspector.labelLeavesNameEmpty-12789.test.tsx
  • DatasetDefaultInspector.labelLeavesNameEmpty-12789.test.tsx

Each file pins five things:

  1. The Label 收入 leaves the Name empty, in the box and in the draft.
  2. A cleared Label leaves the Name empty: Revenue derives revenue, and clearing the Label empties it.
  3. A Name left empty by 收入 still follows a Latin Label typed later (Revenue Board derives revenue_board).
  4. A Name the author typed as field while the Label is 收入 stays field after a later Latin Label.
  5. Control: a Latin Label still derives the strict form. 2024 Revenue derives f_2024_revenue, and the Label Field derives field.

object-fields-io.test.ts gains a toFieldNameOrEmpty block. It returns '' where toFieldName returns field (empty, blank, 收入, 报修工单, punctuation only), and the same value as toFieldName on every input that yields a name.

Run at e4b532b, after the closure build, in repo-root form: pnpm exec vitest run over every file whose name starts with ReportDefaultInspector, DatasetDefaultInspector or ObjectDefaultInspector, every object-fields-io test, ResourceEditPage.nameSurvivesLabel-12680.test.tsx, and both CreateItemDialog tests (the caller that relies on the fallback) → Test Files 23 passed (23), Tests 235 passed (235).

Red on unchanged main, and the ablations

Each leg ran the two new inspector test files (10 tests); A5 also ran object-fields-io.test.ts. Every mutation went through ablation-replace.mjs from the objectstack checkout: the anchor was hit once, the replacement landed, and the blob changed on disk. R0 replaced whole files instead, checked by blob hash against 8df2608. Every restore was proven by a blob hash equal to HEAD and an empty git diff HEAD, and the tree was clean at the end (0 changed paths). The tests import the inspectors and object-fields-io through relative source paths, so no dist/ build sits between a mutation and its run.

leg mutation (both inspectors unless noted) result what went red
R0: unchanged main both inspectors replaced by their 8df2608 blobs (toFieldNameOrEmpty count 0) 8 failed, 2 passed pins 1, 2 and 4 in both files, and pin 3 at its first step (field where an empty Name is expected)
A1 (pins 1, 2) the derivation gets the placeholder back: toFieldNameOrEmpty(v) || 'field' 6 failed, 4 passed pins 1, 2 and 3 in both files
A2 (pin 4) the nameFollowsLabel comparison gets the placeholder back: (toFieldNameOrEmpty(…) || 'field') 2 failed, 8 passed pin 4 in both files: the next Label edit replaced the author's field with revenue
A3a (pin 5) the derivation treats field as "no name": toFieldNameOrEmpty(v) === 'field' ? '' : toFieldNameOrEmpty(v) 2 failed, 8 passed pin 5 in both files: the Label Field left the Name empty
A5 (helper; object-fields-io.ts) toFieldNameOrEmpty returns 'field' instead of '' 9 failed, 41 passed the helper's first pin, and pins 1–4 in both inspector files

Two more legs stayed green, and each green has a cause:

  • A3: the derivation swapped for toFieldNameLoose. Both rules trim() first, so they differ only on trailing punctuation, which none of these pins type. That difference is pinned by objectui#12746's pin 3 (Revenue -- Board derives revenue_board). Its file is in the suite run above, and PR fix(app-shell): a report's or dataset's Name keeps every underscore typed (objectui#12746) #12788 ablated it.
  • A4: the nameValue === '' arm dropped from nameFollowsLabel. With the comparison now on toFieldNameOrEmpty, an empty Name next to a non-Latin Label equals the Label's derivation, so pin 3 holds through the comparison alone. The arm still matters for a Name the author clears next to a Latin Label, which these pins do not cover.

Local gates (all at e4b532b)

  • pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' run build → exit 0 (scope 29 of 47 workspace projects).
  • the vitest run above → exit 0.
  • pnpm --filter @object-ui/app-shell run type-check (tsc --noEmit && tsc -p tsconfig.test.json) → exit 0. --listFiles on the test project lists both new test files and object-fields-io.test.ts.
  • pnpm exec eslint on the six changed source and test files → exit 0, 0 errors. All 7 warnings fall on lines this diff does not touch (react-refresh/only-export-components, @typescript-eslint/no-explicit-any).
  • node scripts/check-changeset-presence.mjs → exit 0: "6 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)".
  • pnpm check:new-line-citations → exit 0, VERDICT new-cross-file-line-citations: 0 new citation(s).

CI runs everything else.

Acceptance notes

  • Not changed here: CreateItemDialog (studio-design) still uses toFieldName's field as a sentinel (finalName !== 'field'), so it also refuses an input whose real identifier is field, such as the Label Field. I read this from source and did not reproduce it in a browser. Nobody is carrying it. toFieldNameOrEmpty would replace the sentinel if anyone takes it up.
  • Found while measuring, reported to the seat, not filed from here: with no base object picked, the dataset designer's Save sends "object":"example_object", the spec create seed's placeholder object. Reading: PUT /api/v1/meta/dataset/probe_ds?mode=draft&package=com.qa.vrf with body {"name":"probe_ds","label":"Probe Ds","object":"example_object",…} → 200, while the Base object box read "Select an object…". This change does not touch it.

Generated by Claude Code

…aves the Name empty (objectui#12789)

The report and dataset designers derived the Name from the Label with
`toFieldName`, whose empty result is the placeholder `field`. A Label in a
non-Latin script (`收入`), or a cleared Label, therefore named the item
`field` silently. Both now derive with `toFieldNameOrEmpty`, the same
strict rule with no placeholder, beside `toFieldName` in
`previews/object-fields-io.ts`; the Name stays empty, as the object
designer's does, and the save path asks for one. `nameFollowsLabel`
compares against the same function. `toFieldName` keeps its `field`
fallback for its other callers.

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 75 chunks) 3129.9 KB 3744.1 KB
Main entry chunk (gzip) 75.6 KB 350 KB
Entry file index-CJiwMJuj.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.42KB 7.85KB
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) 41.15KB 10.86KB
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) 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) 603.95KB 146.15KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 248.96KB 69.45KB
fields (index.js) 291.36KB 74.46KB
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.79KB 11.90KB
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.23KB 39.59KB
plugin-designer (index.js) 233.95KB 50.08KB
plugin-detail (index.js) 263.65KB 69.71KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 180.28KB 46.60KB
plugin-gantt (index.js) 178.36KB 45.08KB
plugin-grid (index.js) 270.84KB 75.07KB
plugin-kanban (index.js) 59.42KB 18.48KB
plugin-list (index.js) 135.74KB 34.83KB
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) 94.68KB 24.36KB
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.23KB 39.46KB
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

席位核对 — ACCEPT · domain:ui seat 3 · session_01Vk15bz8xHtKGUfXfCm16h4 · 2026-10-16T22:15Z

Head reviewed: e4b532b7dd8e3463f96092abfc1a036c8b0e2452, one round.

  • Card objectui#12789; claim 6225126363, Clause-②: no.
  • No export, prop or type changes, and no contract face. The gates are CI plus the seat's own read.

Implemented-by: claude/issue-12789-label-derives-empty-name
Reviewed-by: session_01Vk15bz8xHtKGUfXfCm16h4

1. Shape.

  • Draft PR against main. The body's first line is Fixes #12789, and the body has no other closing keyword (scanned in full).
  • 7 files, +294/−9, all inside the claim's surface:
    • ReportDefaultInspector.tsx (+11/−3) and DatasetDefaultInspector.tsx (+12/−4): the Label derivation and the nameFollowsLabel comparison now use toFieldNameOrEmpty;
    • previews/object-fields-io.ts (+21/−2): the new toFieldNameOrEmpty, with toFieldName delegating to it;
    • two new pin files and an added block in object-fields-io.test.ts;
    • changeset .changeset/12789-report-dataset-label-leaves-name-empty.md: @object-ui/app-shell: patch.
  • No content/docs/releases/.

2. Premises measured (the dev's browser readings, audited by the seat in the run's transcript).

  • Setup: objectstack 66d456d01 examples/app-crm --fresh, a writable package com.qa.vrf (namespace qavrf), Chromium.
case before (8df2608) this PR (e4b532b)
report / dataset, Label 收入 Name field Name empty
report / dataset, Label cleared Name field Name empty
a Latin Label typed later revenue_board / revenue_board_q the same, unchanged
object designer (control) empty empty
  • Save on an empty Name sends no request. The report shows "A name is required." with the client spec banner, the dataset shows "A name is required.", and the object designer behaves the same.
  • On main, by contrast, the dataset saved silently as field: PUT /api/v1/meta/dataset/field answered 200, "Saved dataset 'field'".

3. Gates (check-runs pinned to this head).

  • 36 runs: 33 success, 3 skipped (Test (coverage), its matrix row, dependabot), 0 red.
  • Among the successes: Lint, Type Check, Repo Gates, Test, Test (shard 1/16) … (16/16), Test (dist pins), Build & E2E, Build Docs, Bundle Analysis (PASS, 6225575516: eager closure 3129.9 KB of 3744.1 KB), Live E2E (informational), the changeset checks, Line Citation Gate and Governed Surface Queue Guard.
  • The dev's local subset:
    • 23 files / 235 tests, covering every report, dataset and object inspector suite, every object-fields-io test, the objectui#12680 host test and both CreateItemDialog tests;
    • the closure build and the app-shell type-check;
    • eslint, with 0 errors;
    • changeset presence and new-line citations.
  • Red on main's inspectors: 8 failed / 2 passed.
  • Ablations, each restored and proven: A1 6 red, A2 2 red, A3a 2 red, A5 9 red. A3 and A4 stay green, and the PR explains why:
    • A3, the loose rule, differs only on trailing punctuation, which objectui#12746's pin covers in the same run;
    • A4's arm is redundant with the OrEmpty comparison.

4. Accepted deviations.

  • toFieldName's body becomes toFieldNameOrEmpty(raw) || 'field'. The claim held the 'field' fallback unchanged, and it is: the result is identical for every input, and its existing pin ('' and 报修工单 both give field) is green. Delegating keeps the strict rule in one spelling.
    • CreateItemDialog relies on that fallback as a sentinel and is untouched.
    • genGroupKey never reaches the empty case.
  • Housekeeping, cleaned up. The dev wrote one push log beside its worktree, outside the scratchpad, and created a temporary ref in the shared .git. Both were deleted at once, and the final tree had 0 changed paths. Noted, not repeated: the next dispatch says never to create refs in the shared .git.
  • Prose checked against the diff:
    • the changeset's "Such a Label now leaves the Name empty, as the object designer already does, and saving asks you for a name";
    • "A Label that yields a name derives exactly what it derived before";
    • the toFieldNameOrEmpty docblock.

5. Out of scope (where each went).

  • A create flow saves the spec seed's placeholder binding. A dataset with no base object saves object: example_object, and its reads are refused (measured). It is the second occurrence of this card's class. → filed objectui#12810, the family card, with an enumeration pin over BUILTIN_METADATA_CREATE_SEEDS.
  • CreateItemDialog's 'field' sentinel also refuses an input whose real identifier is field. Read from source only. → Acceptance notes.

6. Landing.

  • Not governed: check-governed-queue-guard.mjs --test over the 7 paths reads NOT GOVERNED.
  • 303 lines changed, under the 3000-line threshold.
  • Clause-②: no. Every check is green or an expected skip.
  • ⇒ The seat lands it: relay pr_ready + automerge_enable, through the merge queue.
  • On MERGED, Fixes closes objectui#12789.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 16, 2026 22:16
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 16, 2026 22:17
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 16, 2026
Merged via the queue into main with commit 0b80f50 Oct 16, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-12789-label-derives-empty-name branch October 16, 2026 22:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants