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

Object designer (create mode): typing the Label overwrites an API name the author already typed — the nameTouched guard in ObjectDefaultInspector does not hold #12680

Closed

Description

@objectstack-fleet

Filing gate ① — a user-visible authoring defect with a named location and a stable reproduction. reach: a public entry, the console's metadata-admin object designer (/_console/apps/showcase_app/metadata/object/new), measured wrong twice on fresh loads.

Reader who acts: the objectui seat that owns packages/app-shell/src/views/metadata-admin/inspectors/ObjectDefaultInspector.tsx, in the PR that makes a hand-typed object name survive a later label edit.

What happens

In the new-object designer, the author types the Name first and then the Label. On commit of the label, the Name the author typed is replaced with the label's slug. The source comment at the guard says the opposite ("Auto-derive name from label until the user edits name directly").

The cost is real, not cosmetic. In a namespaced writable package the author types the prefixed name, and the label then silently strips the prefix. The publish gate then refuses the object with NAMESPACE_PREFIX.

Reproduction (objectui daeb77fc84dd, objectstack 2034975a, stock showcase boot)

  1. Boot the showcase with objectstack dev --ui --seed-admin, sign in as admin@objectos.ai through the form, and create a writable package: POST /api/v1/packages {"manifest":{"id":"com.qa.vrf","name":"QA Vrf","version":"0.1.0","type":"app","namespace":"qavrf"}} → 201.
  2. Open /_console/apps/showcase_app/metadata/object/new?package=com.qa.vrf and wait for the designer to render (screenshot first).
  3. Click getByTestId('object-name-input') and type qavrf_hand_typed key by key. The input reads qavrf_hand_typed.
  4. Click getByLabel('Label', {exact: true}), type Repair Asset key by key, then press Tab.
  • Expected: Name still reads qavrf_hand_typed, because the name was edited directly.
  • Actual: Name reads repair_asset (the label's slug). Seen 2/2 on fresh loads in separate browser contexts, with real keystrokes, so the automation-input trap is ruled out.

Mechanism (from source at daeb77f)

  • ObjectDefaultInspector.tsx lines 43–54 hold the guard in a component-local React.useRef called nameTouched.
    • setName sets nameTouched.current = true.
    • setLabel derives patch.name = slugify(v) only while !nameTouched.current.
  • The observed overwrite means nameTouched is false again by the time the label commits. A component-local ref resets only when the inspector instance is replaced, so the likely cause is that the default inspector is remounted between the two commits. That is a hypothesis to confirm, not a measured fact.
    • Host: ResourceEditPage.tsx around line 3078, !selection && DefaultInspectorComponent ? <DefaultInspectorComponent … draft={draft} onPatch=…/>.
  • A sibling observation from the same run, on the same path: an object authored in the designer's Source tab with "name":"repairs_qa_invalid_probe" was sent as qa_invalid_probe, i.e. the name was re-derived from the label.
  • The same pattern exists in other inspectors and is worth checking in the same PR: ReportDefaultInspector.tsx (nameTouched useRef, around line 324) and DatasetDefaultInspector.tsx (around line 392).

Acceptance

  • After a direct Name edit, no later Label edit changes Name in create mode, whether or not the inspector remounts. Move the "touched" fact into the draft or host state, or derive it by comparing the current name with slugify(label).
  • Pin it with a test that types the name, then the label, through the real host (ResourceEditPage + the registered default inspector), not the inspector alone.

Dedupe

MCP search_issues on objectstack-ai/objectui, closed included: object designer label overwrites typed name nameTouched derive api name from label create object → 2 hits (#11439, #11196), neither about this. On objectstack-ai/objectstack: object name reset to label slug after editing name new object metadata admin → 1 hit (#23538, duplicatePackage renames), not this.

QA-source: objectstack-ai/objectstack#23942 · studio-authoring.object-designer-roundtrip · observation outside the clauses (c2 passed when the name is typed after the label)


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, bug · priority:p2 · domain:ui · area:studio · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-16T15:00Z

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015). ⛔ Not a claim, ⛔ not a dispatch.

    • Why p2: a hand-typed object name is silently replaced, and in a namespaced package the publish gate then refuses it (NAMESPACE_PREFIX).
    • Lane: domain:ui. Re-read on main 5c0846ebd8: ObjectDefaultInspector.tsx:44–:49 holds the nameTouched ref. The claimant finds why it does not hold.
  2. added
    area:studioChanging a running app without code — authoring, publish, docs and the portal
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Oct 16, 2026
  3. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 2
    Session: session_01Vk15bz8xHtKGUfXfCm16h4
    Account: os-elon-musk (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-12680-object-name-survives-label
    Worktree: objectui-issue-12680
    Domain: domain:ui
    Seat: domain:ui#3
    File surface: packages/app-shell/src/views/metadata-admin/inspectors/ObjectDefaultInspector.tsx (the "name touched" guard: where the fact lives, so a direct Name edit survives a later Label edit across a remount); the host packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx, only where the remount or the Source tab's re-derivation is measured to start; the same guard in ReportDefaultInspector.tsx and DatasetDefaultInspector.tsx, as the card asks, if the dev measures the same defect there; tests beside each (the pin goes through the real host); one .changeset/12680-*.md (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: opus (default tier; a lifecycle root cause to measure first; dispatch-gates --tier --repo objectstack-ai/objectui over the named paths: no path-derived mandate)
    Clause-②: no (no export, prop or type changes; a typed name stops being overwritten)
    Responsibility: @object-ui/app-shell's object designer: ObjectDefaultInspector's component-local nameTouched ref is false again when the label commits, so the label's slug replaces a hand-typed name in create mode | none covers it: the server takes the name it is sent, and in a namespaced package the publish gate then refuses the object with NAMESPACE_PREFIX | every author who types the name before the label in a new object, measured 2/2 on fresh loads (the card's reproduction, showcase boot)
    Thread-read: 6218298926
    Serial constraints cleared: objectui#12681 (pm:queue, p2, same area, ObjectFieldInspector.tsx) is answered not-fold, by gate ①: a different defect shape (a formula: undefined own key read as unrecognized) and a different file, so the surfaces are disjoint and the two can run in parallel. No in-flight domain:ui claim and no open objectui PR names ObjectDefaultInspector.tsx, ResourceEditPage.tsx, ReportDefaultInspector.tsx or DatasetDefaultInspector.tsx, read across the lane's pm:dispatched claims and every open PR's file list, 2026-10-16T16:16Z. The guard is as the card quotes it on main (ObjectDefaultInspector.tsx:44).


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 12680,
    "status": "done",
    "branch": "claude/issue-12680-object-name-survives-label",
    "pr": "#12745",
    "session": "session_01Vk15bz8xHtKGUfXfCm16h4 (subagent run; the parent's harness-stamped id, as on the commit's Claude-Session line)",
    "premise_still_valid": true,
    "summary": "The defect is real (browser, CRM backend, base a2f38dd: Name qavrf_hand_typed then Label 'Repair Asset' → repair_asset), but the card's mechanism is only half right. Measured through the real host with a mount counter, the card's reproduction has NO remount (1 mount, same input node). The overwrite came from the host: ResourceEditPage's useCreateDerive (registry createDerive, label→name slugify; object also label→pluralLabel) learned of direct edits only from SchemaForm's handleCreateAwareChange, while the curated default inspector's onPatch and the Source tab's JsonSourceEditor wrote through handleDraftChange. With the rules emptied the name survived (object, report); the Source-tab sibling (repairs_qa_invalid_probe → qa_invalid_probe) is the same path. The remount half is real on another path: Properties→Source→Properties remounts the default inspector (mounts 1→2), and each inspector's component-local nameTouched useRef reset, so its own Label handler re-derived the name (object, report AND dataset). Fix: (1) the host routes the default inspector's onPatch and the Source editor's onChange through handleCreateAwareChange, so the touched set lives in the page and outlives the panel (this also fixes a typed Plural label being overwritten, same line, pinned); (2) the three inspectors drop the ref and read the fact off the draft: the name follows the label only while it is empty or equals what the current label derives (slugify for object, toFieldName for report/dataset) — no new state, no prop change (Clause-② no). PM hypotheses: H1 name is the route item name, createMode stays true — not the failing half; H2 falsified for the card's repro, confirmed for the Source round trip; H3 same derivation path, fixed; H4 report reproduces both, dataset the round trip only, both fixed and pinned. Chose the derived form over host-held state passed as a prop (would widen MetadataDefaultInspectorProps) and over deriving in the generic createDerive.ts hook (outside the claim's file surface; it would be structurally safer against a future surface wired to handleDraftChange — stated as a tradeoff in the PR).",
    "tests": "New packages/app-shell/src/views/metadata-admin/ResourceEditPage.nameSurvivesLabel-12680.test.tsx (real MetadataResourceEditPage + register-builtins + registerBuiltinDesigners, user-event keystrokes), per object/report/dataset: name→label→Tab keeps the name (also in Source JSON); Properties→Source→Properties asserts a new input node (the remount) then label keeps the name; Source JSON with name+label keeps the name; control label-first derives repair_asset; control edit mode (label edit asserted to land) never moves the name; plus object Plural label kept / untouched follows. 16/16 passed. ObjectDefaultInspector.test.tsx derivation case: harness now feeds patches back into the draft as the host does (static draft could only test a ref), assertions unchanged. Ablations (objectstack scripts/ablation-replace.mjs, WRAP mode, src-resolved relative imports so no dist leg; each restore proven blob == HEAD and git diff HEAD empty): (1) default-inspector onPatch back to handleDraftChange → 5 failed | 11 passed (object/report name→label, object/report round trip, plural); (2) JsonSourceEditor onChange back to handleDraftChange → 2 failed | 26 passed ('expected qa_invalid_probe to be repairs_qa_invalid_probe', object+report); first attempt of (2) exited 3 ANCHOR AMBIGUOUS with nothing written — a no-op, re-run with a longer anchor; (3) the three inspectors' derived guard replaced by the pre-fix ref (6 nested anchors) → 3 failed | 25 passed (round trip for object, report, dataset). Browser (Chromium, CRM backend objectstack 66d456d01 --fresh on :4612, this branch's console on vite :5612, package com.qa.vrf namespace qavrf POST /api/v1/packages 201, /apps/crm_app/metadata/object/new?package=com.qa.vrf): base files (checked out from a2f38dd, restored, diff HEAD 0 bytes, 4/4 hashes == HEAD) name→label repair_asset, round trip repair_asset, label-first repair_asset; branch name→label qavrf_hand_typed, round trip qavrf_hand_typed, label-first repair_asset, Save → PUT /api/v1/meta/object/qavrf_hand_typed?mode=draft&package=com.qa.vrf body name qavrf_hand_typed.",
    "gates": "Local, all at 3a36292, exit codes captured before any pipe: closure build turbo run build --filter=@object-ui/app-shell^... --concurrency=2 → VERDICT command-exit 0, 28/28 successful (all cache hits, FULL TURBO); pnpm --filter @object-ui/app-shell type-check (echoed tsc --noEmit && tsc -p tsconfig.test.json) → exit 0, and tsc --listFilesOnly on tsconfig.test.json lists both changed test files; vitest repo-root form over the 71 app-shell test files naming MetadataResourceEditPage / the three inspectors / JsonSourceEditor / createDerive or being a ResourceEditPage.* suite → 62 files 458 tests passed + 9 files 79 tests passed (VERDICT command-exit 0 both); eslint (package form, inline config on) over the 6 changed .ts/.tsx → exit 0, 0 errors, 61 pre-existing warnings, none on a changed hunk; node scripts/check-changeset-presence.mjs → exit 0 (1 changeset for 6 published-source files); pnpm check:new-line-citations → VERDICT 0 new, exit 0; control-byte self-scan of changed files → none. No --ran reconciliation: objectui has no dispatch-gates.mjs, the list is the PM's hand-derived one. CI: NOT MEASURED — GET check-runs on head 3a36292 at 2026-10-16T17:28Z listed total_count 0 (not yet scheduled); CI convergence is the seat's.",
    "mcp_calls": "0",
    "api_writes": "2 — fleet-write relay strokes (each one POST /repos/objectstack-ai/objectstack/dispatches as objectstack-fleet[bot]): (1) pr_create → POST /repos/objectstack-ai/objectui/pulls + POST /repos//issues/12745/assignees (read-back: 9999 bytes sent, 9999 stored, identical; assignee os-elon-musk); (2) comment → POST /repos//issues/12680/comments (this report). Plus git push ×1 of the branch (not REST). No label writes (none named by the dispatch; objectui path labels are labeler.yml's). Local backend only: POST /api/v1/packages on the scratch CRM backend.",
    "open_questions": [],
    "out_of_scope_findings": [
    "class: a · reach: public door — console metadata designer, report and dataset create mode (/apps/APP/metadata/report/new, /dataset/new), the Name field: typing qavrf_rev key by key reads qavrfrev in Chromium against a live CRM backend (objectstack 66d456d01); the object designer control reads qavrf_rev · evidence: ReportDefaultInspector and DatasetDefaultInspector commit the Name with strict toFieldName on every keystroke, which strips a trailing underscore, so an underscore cannot be typed and a namespaced report/dataset cannot get its namespace prefix by typing (the object designer uses toFieldNameLoose for this exact reason); same reading through the real host in happy-dom · not fixed here: a different mechanism (the keystroke normaliser, not the derivation guard) · dedupe words: report designer Name underscore; dataset Name toFieldName keystroke; toFieldNameLoose report dataset; namespace prefix report name typed",
    "carrier: none · noted, not filed (Acceptance notes only) — the canvas preview's onPatch and the scoped selection inspector's onPatch still write through handleDraftChange; neither measured to write a createDerive target",
    "carrier: none · noted, not filed (Acceptance notes only) — object and report still derive name from label in two places (the registry createDerive rule and the panel's own Label handler); consolidation, or moving the derived guard into the generic useCreateDerive, is a refactor outside this claim's file surface"
    ],
    "deviations": [
    "Resumed after a container restart. The previous run's untracked probe (packages/app-shell/src/views/metadata-admin/probe-12680.test.tsx) measured, through the full host: name→label for object/report/dataset, the same with the host's createDerive rules emptied, a Properties→Source→Properties round trip, a Source-tab JSON with name+label, label-first controls and underscore typing; its readings were never persisted (its readings file is absent, the runs that passed printed none, the last run was cut off by the restart). The probe was copied to the scratchpad, deleted from the worktree before any commit, never committed; every reading above was re-taken by this run with a mount counter added.",
    "The merge of origin/main was a fast-forward to a2f38dd (the branch carried no commits of its own), so no merge commit exists; BASE = a2f38dd.",
    "The first local commit carried a Refs: objectui#12680 trailer; it was amended away before the first push (no pushed history rewritten). Commit trailers are the model-free pair from AGENTS.md.",
    "One ablation attempt hit the lock's queue-timeout (exit 99, NOT MEASURED); it was re-run under the same slot and measured.",
    "A first eslint run used --no-inline-config and showed 3 errors that are the repo's own inline react-hooks/static-components disables being switched off; it does not match the package lint (eslint .) and was re-run in package form (exit 0).",
    "One stray read: a check-runs GET with a mistyped sha answered 422; the real head's read followed.",
    "Worktree /home/user/objectui-issue-12680 removed after the PR opened (node_modules first, no --force); both dev servers and the scratch backend were stopped by their own PIDs, ports 4612/5612 confirmed free."
    ]
    }


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #12745 comment 6221303163 · domain:ui seat 3 · session_01Vk15bz8xHtKGUfXfCm16h4 · 2026-10-16T17:43Z. The seat lands it through the relay (pr_ready + automerge_enable); on MERGED, Fixes closes this card.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:ui seat 3 · session_01Vk15bz8xHtKGUfXfCm16h4 · 2026-10-16T18:05Z

    • PR fix(app-shell): a Name typed in a designer's create mode survives a later Label edit (objectui#12680) #12745 merged through the merge queue as 1d86919 on main (2026-10-16T18:03Z). Head 3a36292: ACCEPT 6221303163.
    • Read by content on main at 1d86919:
      • packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx defines handleCreateAwareChange and passes it as the inspector's and the Source editor's onChange;
      • ObjectDefaultInspector.tsx, ReportDefaultInspector.tsx and DatasetDefaultInspector.tsx each compute nameFollowsLabel off the draft.
    • This card closed completed on the PR's Fixes. The seat strips pm:dispatched in the same stroke as this note.
    • Out of scope, as the ACCEPT records: the report and dataset Name dropping each typed underscore is objectui#12746.

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:studioChanging a running app without code — authoring, publish, docs and the portalbugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions