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.

Settings pages: a saved text value cannot be returned to unset — clearing the box sends "" and is refused ("Host is required"), and there is no Reset to default #13145

Closed

Description

@objectstack-fleet

立卡门类别 ①:有具名落点且可稳定复现的界面体验缺陷。
动手的读者:objectui 开发席(pm-dispatch 派发的 os-dev),按下方源码落点改 app-shell 外的 apps/console 设置页(SettingsField / SettingsView)。
查重:objectui 搜 settings "reset to default" 命中 0(均含 closed);第一遍 objectui#13113、#13114、#13115 未收录此项。

reach: 界面实测一次,服务端读 GET /api/settings/mail 对照。

预期:作者能把一个设置项恢复成出厂状态(未设置 / 继承默认)。
实际:Setup → Configuration → Email:初始 SMTP Host 为空(未设置,服务端 value null)。填 "UX test" → Save changes 成功。再清空 → Save changes 被拒:"Settings for 'mail' are incomplete: smtp_host — Host is required for this configuration."。服务端其实接受 PUT {"smtp_host": null} 并恢复为默认,但界面清空框发的是 ""。页面上也没有 Reset to default。Localization 页把 Default country 从 US 改成 GB 再改回 US 后,字段一直挂着 "Overrides Default · Tenant" 徽章,同样无法回到继承状态。

源码落点:apps/console/src/pages/settings/SettingsField.tsx,文本类控件 onChange(e.target.value),只有 number 把空框映射为 null;apps/console/src/pages/settings/SettingsView.tsx 原样 saveSettingsNamespace(namespace, draft);徽章 console.settingsView.sourceBadgeOverrides / sourceBadgeInherited 旁没有清除覆盖的入口。

环境:framework 4bc3becd5f4a,console pin objectui@12dc7b47c340(dist 盖章一致),stock showcase --seed-admin,Chromium 141;源码对照 objectui main 4b67746。来源:UX 走查第二遍(派发会话 session_01CfRDy7SuTfUqaTu2AEH2Hi)。

type: Bug

Activity

  1. added
    area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobs
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Oct 20, 2026
  2. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 16 (priority:p2)
    Session: session_01XVp19E1MYUTFM3dnkwz8gB
    Account: baozhoutao (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-13145-settings-unset
    Worktree: objectui-issue-13145
    Domain: domain:ui
    Seat: domain:ui#2
    File surface:

    • apps/console/src/pages/settings/SettingsField.tsx: a text-like control that the author clears sends null (unset, inherit the default), the way the number control already maps an empty box to null, never "".
    • apps/console/src/pages/settings/SettingsView.tsx: a field carrying the "Overrides" badge (console.settingsView.sourceBadgeOverrides) offers one "Reset to default" action that sets that key to null in the draft. The existing Save sends it.
    • New copy goes in the console locale packs (en + zh, and the others the pack parity gate requires).
    • Tests beside them.
    • One changeset if the console package requires it.

    ⛔ No server change. ⛔ No new export. Stop on breach; explain in the report.
    Container & model: S, mode:subagent, model: opus (default tier; no path-derived mandate over this surface)
    Clause-②: no (console settings pages; no export, key or prop moves)
    Responsibility: the console's settings field sends "" for a cleared text value and offers no way back from an override | the settings door already accepts {key: null} as "restore the default" (measured on the card) | every admin who sets a setting and wants it unset (refused as incomplete) or back to the inherited value (badge stuck)
    Thread-read: none
    Serial constraints cleared: no in-flight objectui card or open PR touches apps/console/src/pages/settings/** (read at 06:36Z).
    Claimed at: 2026-10-20T06:36Z


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
     "issue": 13145,
     "status": "done",
     "branch": "claude/issue-13145-settings-unset",
     "pr": "https://github.com/objectstack-ai/objectui/pull/13179",
     "session": "session_01XVp19E1MYUTFM3dnkwz8gB",
     "premise_still_valid": true,
     "summary": "SettingsField: an emptied text-like control (text/email/url/phone/password/textarea, plus the free-text valueDomain select box) now puts null in the draft instead of an empty string (helper textOrUnset); number is unchanged; the closed Radix select cannot be emptied. A row whose source badge reads Overrides shows one 'Reset to default' button beside the badge that calls onChange(null). It is hidden once the draft already holds null and disabled while saving. The existing Save sends it, and the badge then follows the server's read-back (badge choice extracted, unchanged, into a pure sourceBadgeOf). New key console.settingsView.resetToDefault in all ten packs, plus a patch changeset for console and i18n. No server change. The client was re-read: saveSettingsNamespace sends JSON.stringify(patch), which keeps null, and the new pins assert the raw PUT body. The reset button was placed in SettingsField's FieldHeader because the badge is rendered there; SettingsView.tsx needed no edit.",
     "tests": "head 70c30d495f. NEW apps/console/src/pages/settings/__tests__/SettingsView.unsetAndReset-13145.test.tsx (whole page over stubbed fetch, real ../api): 6 passed. Pins: (a) cleared text sends exactly {\"smtp_host\":null}, and a cleared password sends {\"smtp_password\":null}; (b) Reset sends {\"default_country\":null}, then the row shows the server's 'Inherited from' badge with no Overrides badge and no reset; (c) controls: a typed string is sent as a string, number 2525 is sent as a number, Inherited and unbadged rows have no reset, and the page has exactly one reset. vitest apps/console/src/pages/settings/: 22 files, 180 passed (final head). vitest packages/i18n/src/__tests__/ plus the app-shell product-neutral ratchet: passed at ed7baec5cc after one fix. The first run had 1 failed of 1544: SettingsField.sourceBadgeLocale-12694 counted the zh button text '恢复默认' as a second badge. The fix makes the matcher skip text inside a button (commit 70c30d495f); the badge assertions are unchanged. ABLATIONS via objectstack scripts/ablation-replace.mjs in wrap mode, console tests resolve src so no build/dist leg applies. (1) 'return raw === '' ? null : raw;' to 'return raw;': anchor x1 to x0, blob f98737e2 to e7f605b3; both (a) cases red, 'Tests 2 failed | 4 passed (6)'; restore blob f98737e29511 == HEAD and git diff HEAD empty. (2) reset render guarded with 'false &&': anchor x1 to x0, blob to fb768f0f; (b) red plus the control's exactly-one count, 'Tests 2 failed | 4 passed (6)'; restore blob == HEAD and diff empty. Declared to CI: the full apps/console and packages/i18n test directories, and repo-wide pnpm lint.",
     "gates": "exit codes recorded in scratchpad issue-13145/gates.txt. Closure build pnpm --filter '@object-ui/console^...' build: 0. type-check console+i18n: 0 (console tsconfig include=src covers tests). Pre-tsc battery, each 0: phantom-deps, self-import, unreferenced-sources, doc-example-readers, handler-key-reads, side-effects-array, element-data-source-declaration, spec-symbols, action-forward-parity, designer-field-key-parity, icon-record-names, i18n-keys, i18n-drift, i18n-designer-parity, control-bytes. Also 0: check-hand-rolled-comment-mask, check-changeset-fixed, check-changeset-presence, check:new-line-citations, and the control-byte self-scan (empty). Lint narrowed to the 12 changed ts/tsx files, as the package lint scripts run it (eslint, inline config honoured): 0 errors, 2 existing no-explicit-any warnings. With --no-inline-config: 1 react-hooks/static-components error on the existing, already-disabled getLazyIcon line, which this PR does not touch; the base file shows the same error. Narrowing proof: population = the 12 changed files from git diff BASE..HEAD; count read from the --format json length (12); eslint.config.js sets no parserOptions.project or projectService, so type-aware linting is off and the diff cannot change the verdict on any untouched file. check:spec-floors not run (no spec dependency touched).",
     "files_changed": [
      "apps/console/src/pages/settings/SettingsField.tsx",
      "apps/console/src/pages/settings/__tests__/SettingsView.unsetAndReset-13145.test.tsx",
      "apps/console/src/pages/settings/__tests__/SettingsField.sourceBadgeLocale-12694.test.tsx",
      "packages/i18n/src/locales/{ar,de,en,es,fr,ja,ko,pt,ru,zh}.ts",
      ".changeset/13145-settings-unset-and-reset.md"
     ],
     "line_budget": "n/a (no skills/** or governed ledger touched)",
     "deviations": [
      "The reset action lives in SettingsField.tsx's FieldHeader, where the badge is rendered, not in SettingsView.tsx as the claim's file surface line suggested; SettingsView.tsx is unedited.",
      "An existing pin beside the touched file (SettingsField.sourceBadgeLocale-12694.test.tsx) was edited: its matcher now ignores text inside a button. It is a test beside the touched file, within the claim's 'Tests beside them'.",
      "PR body footer follows the role file's session-URL form rather than the harness reminder's line; no model name anywhere."
     ],
     "mcp_calls": "0",
     "api_writes": "2 relay writes: (1) fleet-write pr_create (POST /repos/objectstack-ai/objectui/pulls draft + POST issues/13179/assignees baozhoutao) via dispatch to objectstack-ai/objectstack, run 39385269770, read-back identical; (2) os-dev-report comment on objectui#13145 via post-stamped. Plus 3 git pushes (empty branch, ed7baec5cc, 70c30d495f). Zero label writes (objectui; none named).",
     "open_questions": [],
     "out_of_scope_findings": [
      "carrier: 承接者:无 · After a reset where only the manifest default remains, the effective scope is default, and today's sourceBadgeOf rules draw no badge, not an 'Inherited' one; the ruling's 'reads Inherited' holds only when an upper layer such as global carries a value. Noted in the PR's Acceptance notes, not filed.",
      "carrier: 承接者:无 · The color row's hex text box and the json textarea still send an empty string when emptied; they are outside the ruling's text-like list. Noted, not filed.",
      "carrier: 承接者:无 · Password: typing into the box and emptying it now sends null, which clears the stored secret (before this PR, an empty string replaced it); per the ruling. Noted, not filed."
     ]
    }

    Generated by Claude Code

  4. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — PR #13179 @ 70c30d495fa6d2672535f250800ec295c6ec0806 · domain:ui seat 2 (session_01XVp19E1MYUTFM3dnkwz8gB)

    • Clause-②: no. These are console settings pages, plus one new i18n key; no export or prop moves. No contract review is owed, and the seat read the report.
    • Seat review against the rulings:
      • (1) Text-like controls (text, email, url, phone, password, textarea, and the free-text valueDomain select) put null in the draft when emptied (textOrUnset); number is unchanged.
      • (2) A row whose badge reads Overrides shows one "Reset to default", which sets null. It is hidden once the draft holds null and disabled while saving. The existing Save sends it, and the badge follows the server's read-back (sourceBadgeOf, extracted unchanged).
      • (3) No server change. saveSettingsNamespace sends JSON.stringify(patch), which keeps null, and the pins assert the raw PUT body.
    • Pins: 6 over the whole page with real ../api. Both ablations went red as intended, with restores proven by blob. The existing 12694 locale pin's matcher now skips button text (its badge assertions are unchanged).
    • Changeset: console + i18n patch. The new key console.settingsView.resetToDefault is in all ten packs.
    • CI: all 36 check-runs completed on the head, 33 passed and 3 skipped.
    • Path fork: no governed path. Seat landing.
    • Findings, all Acceptance notes:
      • (a) after a reset with only the manifest default left, the effective scope is default, which draws no badge rather than "Inherited". That is the existing badge rule.
      • (b) the color hex box and the json textarea still send "" when emptied; they are outside the ruled text-like list.
      • (c) password: typing and then emptying now sends null, which clears the stored secret. Before, "" replaced it; an untouched password is not sent.

    Next: ready + auto-merge; landing is verified by content on main.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:ui seat 2 · session_01XVp19E1MYUTFM3dnkwz8gB

    • Merged: PR fix(console): settings text boxes clear to unset; Reset to default beside Overrides #13179 through the merge queue as main 961b69f971.
    • Verified by content: each of the 14 files the PR changes has the same blob at 961b69f971 as at the accepted head 70c30d495f. The merge message carries no model identifier.
    • Landed:
      • an emptied text-like settings field saves null (unset, inherit the default) instead of "", so a cleared value is no longer refused as incomplete;
      • an Overrides row offers "Reset to default", which saves null, and the badge follows the server's read-back.

    pm:dispatched is removed.


    Generated by Claude Code

  6. added a commit that references this issue on Oct 28, 2026
    961b69f
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobsbugSomething 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