Skip to content

fix(app-shell): a report's or dataset's Name keeps every underscore typed (objectui#12746) - #12788

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-12746-name-keeps-underscore
Oct 16, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-12746-name-keeps-underscore

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

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

What changes

The report and dataset designers' Name box (create mode) committed every keystroke through the strict toFieldName. That rule trims a trailing _, so the underscore was gone before the next key arrived: qavrf_rev typed key by key read qavrfrev, and a report or dataset in a namespaced package could not get its qavrf_ prefix by typing. Clearing the box also wrote field back into it.

Both boxes now commit through toFieldNameLoose, imported from where it already lives beside toFieldName (previews/object-fields-io.ts), which is what the object designer's Name does. Nothing else moves, so the strict form stays exactly where the object designer keeps its own:

  • the Label → Name derivation still uses strict toFieldName, so a Label typed with a trailing space or punctuation never leaves a trailing _ in the Name;
  • nameFollowsLabel still compares the stored Name with the strict form of the Label. A Name the author extends with _ therefore stops following the Label, and a later Label edit leaves it alone;
  • the spec's identifier rule still judges the saved name (the client check for report, the runtime for both). The loose form only ever yields a lowercase identifier that starts with a letter, or an empty box.

An existing item's name is untouched: the Name box renders only in create mode, as before.

Files: ReportDefaultInspector.tsx and DatasetDefaultInspector.tsx (the Name commit and its import in each, plus a comment), one test beside each, the objectui#12680 host-level test (its report and dataset cases typed qavrfrevenue with a comment saying those Name fields could not carry a separator; they now type qavrf_hand_typed like the object case), and .changeset/12746-report-dataset-name-keeps-underscore.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. Console: this branch's worktree on Vite, proxied to it. A writable package created with POST /api/v1/packages {"manifest":{"id":"com.qa.vrf",…,"namespace":"qavrf"}} → 201. Pages: /apps/crm_app/metadata/{report,dataset,object}/new?package=com.qa.vrf. Name typed qavrf_rev key by key, then Label typed Revenue Board and tabbed out of. Control: a fresh form, Label only, typed Revenue Board then Q.

designer Name after typing, before (3e2c763) after (a5cbab9) Name after the Label edit, after Label only: mid-edit / final, before and after
report qavrfrev qavrf_rev qavrf_rev revenue_board / revenue_board_q (unchanged)
dataset qavrfrev qavrf_rev qavrf_rev revenue_board / revenue_board_q (unchanged)
object (control) qavrf_rev qavrf_rev qavrf_rev revenue_board / revenue_board_q (unchanged)

After the fix, a dataset named by typing qavrf_rev (Label Revenue, base object opportunity) saved through the designer's Save button: PUT /api/v1/meta/dataset/qavrf_rev?mode=draft&package=com.qa.vrf → 200, Saved dataset 'qavrf_rev' (env-wide, package=com.qa.vrf, state=draft).

Tests

New, one beside each inspector, each rendering the inspector with a host double that merges every patch back into the draft (real user-event keystrokes):

  • ReportDefaultInspector.nameKeepsUnderscore-12746.test.tsx
  • DatasetDefaultInspector.nameKeepsUnderscore-12746.test.tsx

Each file pins four things: (1) qavrf_rev typed key by key stays qavrf_rev in the box and in the draft; (2) a cleared Name stays empty and can be typed again; (3) the Label → Name derivation keeps the strict form (Revenue -- Board derives revenue_board, then Q derives revenue_board_q); (4) a _ typed onto a derived Name (qavrf → qavrf_) makes the Name the author's, so a later Label edit (Qavrf Board) leaves qavrf_.

Final run at a5cbab9, after the closure build: pnpm exec vitest run over the report, dataset and object inspector suites (every file whose name starts with ReportDefaultInspector, DatasetDefaultInspector or ObjectDefaultInspector) plus ResourceEditPage.nameSurvivesLabel-12680.test.tsx → Test Files 14 passed (14), Tests 125 passed (125).

Red on unchanged main, and one ablation per pin

Each leg ran the three touched test files (24 tests). Every mutation was proven on disk (anchor count and blob hash), and every restore was proven by blob hash equal to HEAD and an empty git diff HEAD.

leg mutation result what went red
R0: unchanged main (pins 1, 2 and 4) both inspectors replaced by their 3e2c763 blobs (toFieldNameLoose count 0 in each) 10 failed, 14 passed report and dataset pin 1 (qavrfrev for qavrf_rev), pin 2 (field for an empty box), pin 4 (qavrf for qavrf_); objectui#12680 report and dataset typed-Name cases (qavrfhandtyped for qavrf_hand_typed)
A3 (pin 3) the Label derivation's toFieldName(v) replaced by toFieldNameLoose(v) in both inspectors 2 failed, 22 passed pin 3 in both files: the Name stuck at revenue_ (the loose derivation leaves revenue_, which no longer equals the strict form of the Label, so the Name stops following)
A4 (pin 4) nameFollowsLabel compares the strict form of the Name (toFieldName(nameValue)) in both inspectors 2 failed, 22 passed pin 4 in both files: the Label edit replaced the author's qavrf_ with qavrf_board

Pin 3 is green on unchanged main by construction (main applies the strict form everywhere); A3 is the leg that shows it can fail. All tests here import the inspectors through relative source paths, so no dist/ build sits between a mutation and its run.

Local gates (all at a5cbab9)

  • pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' run build → exit 0 (28 packages built).
  • the vitest run above → exit 0.
  • pnpm --filter @object-ui/app-shell run type-check (tsc --noEmit && tsc -p tsconfig.test.json; the test project's include glob covers the new *.test.tsx files) → exit 0.
  • pnpm exec eslint on the five changed source and test files → exit 0, 0 errors; 7 warnings, all 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, "5 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).

Everything else is CI's.

Acceptance notes

  • Not changed here: the report and dataset Label → Name derivation writes field when the Label holds no Latin letters or is cleared. In Chromium, on this branch (the derivation lines are byte-identical to 3e2c763): Label 收入 derives Name field in both designers, and clearing the Label leaves Name field. The object designer derives an empty Name in both cases, which asks the author for one. The cause is the strict toFieldName fallback ('field' for an empty result) in the derivation, which this card keeps as it is. Reported to the seat, not filed from here.

Generated by Claude Code

…yped (objectui#12746)

The report and dataset designers committed Name with the strict
toFieldName on every keystroke, which trims a trailing underscore before
the next key arrives, so qavrf_rev typed key by key read qavrfrev and a
namespaced report or dataset could not be named by typing. Both now
commit with toFieldNameLoose, as the object designer does; the Label
derivation and the nameFollowsLabel comparison keep the strict form.

Claude-Session: https://claude.ai/code/session_01Vk15bz8xHtKGUfXfCm16h4
Co-authored-by: Claude <noreply@anthropic.com>
…rscore (objectui#12746)

One file beside each inspector, with a host double that merges each patch
back: qavrf_rev typed key by key stays qavrf_rev, a cleared Name stays
empty, the Label derivation keeps the strict form, and a trailing
underscore typed onto a derived Name keeps it from following the Label.
The objectui#12680 host-level cases now type the same namespaced name into
all three designers; their comment said the report and dataset Name could
not carry a separator.

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, 74 chunks) 3127.2 KB 3744.1 KB
Main entry chunk (gzip) 75.6 KB 350 KB
Entry file index-CS2v0NZd.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) 600.51KB 145.04KB
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.67KB 11.85KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 15.35KB 5.51KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.82KB 2.38KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 54.94KB 16.05KB
plugin-charts (index.js) 84.97KB 23.36KB
plugin-chatbot (index.js) 201.89KB 48.13KB
plugin-dashboard (index.js) 146.23KB 39.59KB
plugin-designer (index.js) 233.95KB 50.08KB
plugin-detail (index.js) 263.85KB 69.74KB
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) 132.61KB 34.08KB
plugin-map (index.js) 27.07KB 8.96KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 46.66KB 13.13KB
plugin-timeline (index.js) 39.21KB 11.85KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 93.43KB 23.94KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 12.07KB 3.68KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 120.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-16T20:56Z

Head reviewed: a5cbab9b1ea0fe6897a39c1f84de9127af30696f, one round.

  • Card objectui#12746; claim 6223918154, 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-12746-name-keeps-underscore
Reviewed-by: session_01Vk15bz8xHtKGUfXfCm16h4

1. Shape.

  • Draft PR against main. The body's first line is Fixes #12746, and the body has no other closing keyword (scanned in full).
  • 6 files, +227/−9:
    • ReportDefaultInspector.tsx (+8/−2) and DatasetDefaultInspector.tsx (+7/−2). Each change is the Name box's onCommit: toFieldName(v) becomes toFieldNameLoose(v), imported from previews/object-fields-io.ts beside toFieldName, not copied;
    • one new test file beside each inspector;
    • ResourceEditPage.nameSurvivesLabel-12680.test.tsx (+5/−5), accepted below;
    • changeset .changeset/12746-report-dataset-name-keeps-underscore.md: @object-ui/app-shell: patch.
  • The Label → Name derivation and nameFollowsLabel keep the strict toFieldName, as ObjectDefaultInspector does.
  • 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 with real keystrokes.
designer qavrf_rev typed, before (3e2c763) after (a5cbab9) Label-only control
report qavrfrev qavrf_rev, kept through a later Label edit revenue_board mid-edit, revenue_board_q final, unchanged
dataset qavrfrev qavrf_rev, kept through a later Label edit the same, unchanged
object (control) qavrf_rev qavrf_rev the same, unchanged
  • A dataset named qavrf_rev saved through the designer: PUT /api/v1/meta/dataset/qavrf_rev?mode=draft&package=com.qa.vrf answered 200.

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, 6224333879: eager closure 3127.2 KB of 3744.1 KB), Live E2E (informational), the changeset checks, Line Citation Gate and Governed Surface Queue Guard.
  • The dev's local subset:
    • 14 files / 125 tests, every report, dataset and object inspector suite plus the objectui#12680 host test;
    • the closure build and the app-shell type-check;
    • eslint, 0 errors;
    • changeset presence and new-line citations.
  • On unchanged main's inspectors, the 3 touched test files read 10 failed / 14 passed. The pins received qavrfrev and field, and the objectui#12680 cases received qavrfhandtyped. The restore was proven.
  • Ablations: A3, a loose Label derivation, turns the strict-derivation pin red (revenue_). A4, comparing the strict form of the Name, turns the trailing-underscore pin red (qavrf_board). Each was restored and proven.

4. Accepted deviations.

  • ResourceEditPage.nameSurvivesLabel-12680.test.tsx is outside the claim's two inspectors.
    • It is this seat's objectui#12680 host test. Its report and dataset cases typed qavrfrevenue under a comment that said those Name fields "carry no separator".
    • This change makes that comment false. The cases now type qavrf_hand_typed, as the object case does, and they are red on main.
    • It is test-only and keeps the pin truthful, so the seat accepts it without a claim re-statement.
  • A cleared Name now stays empty instead of reading field, and the Name follows the Label again. That is pinned, and it matches the object designer.
  • A trailing _ typed onto a derived Name stops it following the Label. That is pinned, and it matches the object designer. The spec's identifier rule accepts a trailing _, measured by the dev's probe.
  • Prose checked against the diff:
    • the changeset's "A cleared box stays empty, and the Name then follows the Label again";
    • "A Name derived from the Label still uses the strict form";
    • "An existing report's or dataset's name is unchanged and still not editable".

5. Out of scope (where each went).

  • A Label in a non-Latin script, or a cleared Label, derives the report or dataset Name field, where the object designer leaves it empty. Measured in Chromium. → filed objectui#12789.
  • The dev's shell left a stray 6-byte /build.pid (a PID) at the container's filesystem root. It is outside every repository and is not part of this PR. The dev's own rm was refused by the session's safety check, so the seat has reported it to the maintainer rather than removing it.

6. Landing.

  • Not governed: check-governed-queue-guard.mjs --test over the 6 paths reads NOT GOVERNED.
  • 236 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#12746.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 16, 2026 20:57
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 16, 2026 20:57
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 16, 2026
Merged via the queue into main with commit 94b2489 Oct 16, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-12746-name-keeps-underscore branch October 16, 2026 21:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants