You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
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
预期:作者能把一个设置项恢复成出厂状态(未设置 / 继承默认)。
实际: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" 徽章,同样无法回到继承状态。
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
{
"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."
]
}
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.
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.
Activity
objectstack-fleet commentedon Oct 20, 2026
Claim: PM loop round 16 (
priority:p2)Session:
session_01XVp19E1MYUTFM3dnkwz8gBAccount:
baozhoutao(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-13145-settings-unsetWorktree:
objectui-issue-13145Domain:
domain:uiSeat:
domain:ui#2File surface:
apps/console/src/pages/settings/SettingsField.tsx: a text-like control that the author clears sendsnull(unset, inherit the default), the way the number control already maps an empty box tonull, 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 tonullin the draft. The existing Save sends 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
objectstack-fleet commentedon Oct 20, 2026
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
objectstack-fleet commentedon Oct 20, 2026
ACCEPT — PR #13179 @
70c30d495fa6d2672535f250800ec295c6ec0806·domain:uiseat 2 (session_01XVp19E1MYUTFM3dnkwz8gB)valueDomainselect) putnullin the draft when emptied (textOrUnset); number is unchanged.null. It is hidden once the draft holdsnulland disabled while saving. The existing Save sends it, and the badge follows the server's read-back (sourceBadgeOf, extracted unchanged).saveSettingsNamespacesendsJSON.stringify(patch), which keepsnull, and the pins assert the raw PUT body.../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).patch. The new keyconsole.settingsView.resetToDefaultis in all ten packs.default, which draws no badge rather than "Inherited". That is the existing badge rule.""when emptied; they are outside the ruled text-like list.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
objectstack-fleet commentedon Oct 20, 2026
Landed ·
domain:uiseat 2 ·session_01XVp19E1MYUTFM3dnkwz8gBmain961b69f971.961b69f971as at the accepted head70c30d495f. The merge message carries no model identifier.null(unset, inherit the default) instead of"", so a cleared value is no longer refused as incomplete;null, and the badge follows the server's read-back.pm:dispatchedis removed.Generated by Claude Code
fix(console): settings text boxes clear to unset; Reset to default be…