Skip to content

feat(console): a settings toggle that declares confirmOffText asks before it is turned off - #30575

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-ngbinh
Nov 5, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-ngbinh

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Sales · project thread

domain:ui#4 · os-sales · session_01L6bhgX6EQZ4fi7BLKzhfLT

Fixes #30500
Refs #30299

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:设置页里声明了 confirmOffText 的开关(目前是「认证」页的「启用邮箱/密码登录」),从开拨到关时先弹确认框,显示这段话;点「取消」开关保持开、不进未保存更改、什么都不存;点「继续」开关才变关,再按页面的「保存更改」照常保存。打开开关不问,没声明的开关行为不变。
  • 为什么改:Settings manifest: a toggle cannot declare a confirmation before it is turned off, so Setup turns off email and password sign-in without asking #30418 裁决 A 的 ui 半边。spec(PR feat(spec): a settings toggle can declare the question asked before it is turned off (confirmOffText) #30489)已让开关声明 confirmOffText,认证 manifest 也声明了,但控制台不读,Setup 关掉密码登录时一句都不问(声明 ≠ 兑现)。
  • 风险与回滚:只改控制台设置页的开关一支,复用 action_button 确认框已用的 AlertDialog 与 actionConfirm.* 文案键,不加 locale 键、不加导出;服务器「最后一种登录方式」的拒绝仍在,确认框不替代它。回滚即撤本 PR。
  • 先看:ui/apps/console/src/pages/settings/SettingsField.tsx 的 ToggleSwitch 与 case 'toggle';useSettingsLabel.ts 新的 fieldConfirmOff;新测试 SettingsView.confirmOff-30500.test.tsx。
  • 两分钟验证:pnpm --dir ui exec vitest run apps/console/src/pages/settings/__tests__/SettingsView.confirmOff-30500.test.tsx,预期 1 个文件、8 条全绿。
  • 需要你做的:无,席位落地。

What changes

The settings page's toggle row reads SpecifierSchema.confirmOffText ("Confirm dialog text shown before a toggle's value changes to off (omit = no confirm)"), which auth.manifest.ts declares on email_password_enabled.

  • SettingsField.tsx: the toggle's switch is a new ToggleSwitch. When the specifier declares the key and the switch goes from on to off, it opens an AlertDialog carrying the text and hands nothing to onChange. Cancel leaves the switch on and the page's draft untouched, so there is no unsaved change and nothing any Save could send. Continue hands false to onChange; the page's Save button sends it like any other edit. Turning the switch on asks nothing. A toggle without the key changes at once, exactly as before. Judged on the declaration (!= null), as the action_button arm judges a handler's confirmText. The dialog is opened by state rather than an AlertDialogTrigger (only one of the switch's two directions asks), so focus is handed back to the switch when it closes.
  • The text: settings.NAMESPACE.keys.KEY.confirmOffText through the page's label helpers, the slot translation.zod.ts declares beside the key's label; a miss is the manifest literal. The spec types the key as I18nLabel, so an inline locale map literal is picked for the page's language with pickLocalized.
  • useSettingsLabel.ts: fieldConfirmOff(key, fallback), through the hook's one resolve().
  • types.ts: the mirror gains an optional confirmOffText, typed as a string or an inline locale map (a record of language tag to string).
  • The dialog's title and buttons are the existing actionConfirm.title / cancel / confirm keys the action_button confirmation uses; no locale key is added.

Live, Setup > Authentication (showcase objectstack dev --seed-admin, this branch's console under vite, Playwright 1440 px; "before" is the same page with the confirm's guard ablated live with ablation-replace.mjs --hold, then restored):

en-US zh-CN
before: one click turns password sign-in off, no question before en before zh
after: the click asks first, the switch still on after en after zh

zh-CN after Cancel (switch on, no unsaved bar): cancel

zh-CN after Continue (switch off, one unsaved change): continue

zh-CN after Save (saved, row reads Modified): saved

Why

Ruling A on #30418 (6657805240), bind (4): the console's settings page honours confirmOffText before save. Until now the key was declared with no reader, so Setup turned email and password sign-in off on one click (#30299's last remainder).

Tests

ui/apps/console/src/pages/settings/__tests__/SettingsView.confirmOff-30500.test.tsx: the whole page over a stubbed fetch and the real api.ts, so the save cases read the PUT body as it leaves the client.

  • declared, on to off: the confirmation carries the text and the switch stays on, nothing drafted;
  • Cancel: the switch stays on, no unsaved bar, no Save button, zero PUTs, focus back on the switch;
  • Continue: the switch is off, one unsaved change, Save sends exactly {"email_password_enabled":false};
  • declared, off to on: no confirmation, Save sends true;
  • asks again on the next turn to off after a Continue and a turn back on;
  • a toggle without the key: no confirmation, Save sends false;
  • the text from a served settings.auth.keys.email_password_enabled.confirmOffText, and an inline locale map literal picked for de when nothing is served.

Acceptance notes

  • The question sits on the switch, before the off reaches the draft; the page keeps its one write door (Save). So "cancel saves nothing" holds because nothing was drafted, and "confirm saves" is Continue then Save.
  • Reset to default (the link beside a Modified / Overrides badge) clears the layer's value without asking. It could land on off only for a toggle whose fallback is off; email_password_enabled defaults to true, and no other shipped specifier declares confirmOffText. Discard returns to the stored value and asks nothing either.
  • The dialog does not replace the server's last-sign-in-method floor: on the live stack with no other method configured, Continue then Save was refused 400 SETTINGS_VALIDATION with the server's sentence under the row; with Google sign-in set in the same save it went through 200.
  • Observed, not filed (no named producer): the action_button arm resolves a handler's confirmText with resolveLabel, which reads a string or the retired { defaultValue, key } shape, so an inline locale map there would render an empty question. No shipped manifest declares one. Carrier: none.
  • No package gains an export, so Clause-②: no stands. Metadata-protocol: none: the spec is untouched and confirmOffText already meant this (PR feat(spec): a settings toggle can declare the question asked before it is turned off (confirmOffText) #30489); the console only honours it, and AGENTS.md §3 names the console as none, so the changeset is patch on @object-ui/console (corrected by the seat from the first head's semantic / minor).
  • Branch is the card thread's harness-assigned claude/project-thread-ngbinh, not claude/issue-30500-* (named in the claim amendment).

Verification

Unit tests
  • New file, at f6ce715a: pnpm exec vitest run --maxWorkers=2 apps/console/src/pages/settings/__tests__/SettingsView.confirmOff-30500.test.tsx (in ui/, under os-verify-lock): Test Files 1 passed (1), Tests 8 passed (8), VERDICT command-exit 0.
  • The settings directory at merge head 4a1e9138 (main with PR feat(console): Allowed IP ranges shows the address the server judges before a save #30540 merged in): pnpm exec vitest run --maxWorkers=2 apps/console/src/pages/settings: Test Files 36 passed (36), Tests 305 passed (305), VERDICT command-exit 0.
Ablation

ablation-replace.mjs on SettingsField.tsx, anchor if (checked && !next && confirmOffText !== undefined) { to if (false) { (anchor 1 to 0, blob 861c4c87 to fd282923): the new file goes 6 failed | 2 passed (8). The six declared-path cases are red; the two that must not ask (off to on, a toggle without the key) stay green, as expected. Restored: blob equals HEAD, git diff HEAD empty.

Live readings (real stack)

Showcase objectstack dev --seed-admin on its own port, the branch's console under vite with DEV_PROXY_TARGET, Playwright 1440 px, page /system/settings/auth, row email_password_enabled.

  • after, en-US: the click on the switch opens a dialog reading "Confirm Action / People who sign in with a password will need another way in. / Cancel / Continue"; the switch reads aria-checked=true, no unsaved bar. Cancel: switch still on, no unsaved bar, focus on the switch. Continue: switch off, "1 unsaved change". Save with no other sign-in method: PUT /api/settings/auth body {"email_password_enabled":false}, answered 400 SETTINGS_VALIDATION, the server's sentence under the row. Save with Google sign-in set in the same save: 200, email_password_enabled stored false.
  • after, zh-CN: the dialog reads "确认操作 / 使用密码登录的用户将需要其他登录方式。 / 取消 / 继续" (the text from the server's zh-CN settings.auth.keys.email_password_enabled.confirmOffText); Cancel and Continue as above; Save 200, "设置已保存", the row reads 已修改.
  • before (guard ablated live, both locales): the click turns the switch off at once with no dialog and one unsaved change; Save sends false.
Derived gates (dispatch-gates --commands at f6ce715; the list re-derived identical at 4a1e913)

34 derived, 34 run, all exit 0; --ran reconciliation: 34 run, 0 NOT-MEASURED, 0 UNRUN. The ratchet families re-run on the merge head 4a1e9138: pnpm check:type-check-coverage :: exit 0, pnpm check:type-check-debt :: exit 0.

node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-sdui-manifest.mjs :: exit 0
node scripts/check-sdui-manifest.mjs --self-test :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --dir ui exec vitest run scripts/ :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:published-files :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:registry-log-declared :: exit 0
pnpm check:rest-log-spy-declared :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0

Also: turbo run build --filter='@object-ui/console^...' then @object-ui/console type-check at 4a1e9138: VERDICT command-exit 0. eslint on the four changed console files: 0 errors (1 warning, on a line this PR does not touch). pnpm --dir ui run check:new-line-citations: 0 new citations. Repo-wide lint and the full UI suites are CI's.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (no spec change)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes. (eslint on the changed files locally; repo-wide lint is CI's)
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: n/a.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English.

Generated by Claude Code

…fore it is turned off

The settings page's toggle row reads SpecifierSchema.confirmOffText: turning
the switch from on to off opens a confirmation carrying the text (resolved
through settings.NAMESPACE.keys.KEY.confirmOffText, else the manifest
literal); Cancel leaves the switch on with nothing drafted, Continue drafts
false for the page's Save. Turning on asks nothing; a toggle without the key
is unchanged.

Co-authored-by: Claude <[email protected]>
Claude-Session: https://claude.ai/code/session_01L6bhgX6EQZ4fi7BLKzhfLT
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 5, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#4 · os-sales · session_01L6bhgVfPeFZkN1FP93imKU

维护者速读

席位核对 — ACCEPT · head e88fef3

  • 形态:5 文件 +397/−7(SettingsField.tsx +98/−6、types.ts +7、useSettingsLabel.ts +5/−1;新测试 +277;changeset +10)。check-governed-merges --pr 30575:NOT governed,404 行 ≤ 3000。Fixes #30500、Refs #30299,draft,目标 main,标题无 !。
  • 契约:Clause-②: no(没有新导出、没有新文案键),Metadata-protocol: none(spec 未动,控制台只是兑现 confirmOffText 已写定的含义;AGENTS.md 第 3 条把 the console 列在 none)。认领里的 semantic 是本席写错的,已由修正 6663547556 改正;卡线程在 e88fef3c 把 changeset 改成 patch,PR 正文对应两处由本席经中继改正。
  • 席位逐行读:ToggleSwitch 只在 checked && !next && confirmOffText !== undefined 时打开确认框并且不调用 onChange;「继续」是 onChange(false),「取消」什么都不做,所以草稿里从不出现那个关;确认框关闭时 onCloseAutoFocus 把焦点交回开关。aria-invalid、aria-describedby 透传到 <Switch> 上。是否询问按声明判断(!= null),与 action_button 判断 confirmText 相同;文案先查 settings.NAMESPACE.keys.KEY.confirmOffText,查不到用 manifest 原文。pickLocalized 对已声明的值总返回字符串(缺页面语言时回落,最差是空串,不会是 undefined),所以声明了内联多语言表而缺当前语言时仍会询问,不会静默跳过。types.ts 只给镜像加一个可选成员;SettingsView.tsx 与保存路径没动。
  • 前提实测:dev 报告 6663406457。真栈(showcase objectstack dev --seed-admin + 本分支 console vite + Playwright 1440)en-US 与 zh-CN 前后截图在 PR 正文:改前一点就关,改后先问。新测试 8 例;消融(把开到关的判断换成 if (false))6 红 2 绿(两例「不该问」本就该绿),还原后 blob 与 HEAD 相同。settings 目录 36 文件 305 例绿;派生门禁 34 条全部 exit 0,0 NOT-MEASURED;console type-check exit 0。没有新增 adapter 写调用,写入普查不用改。
  • 门禁 check-runs:head 上 55 个(按名取最新一次),42 success,13 skipped,0 红;check-expected-skips --pr 30575:13 个跳过全在 main 的名册里(ui-only diff 的预期跳过,exit 0)。Build & E2E UI、Live E2E UI、Lint UI、Test UI 16 片与汇总、Type Check UI 与各 Type Check 均绿,Check Changeset 在改成 patch、本席改正文后 success,PR-body guards success。mergeable_state clean。
  • 接受的偏差:changeset 由 minor 改为 patch(见契约)。「恢复默认」不询问:只有默认值为关的开关才可能经它落到关,而唯一声明 confirmOffText 的 email_password_enabled 默认为开,dev 已记录,本席同意不做。
  • 范围外去向:action_button 用 resolveLabel 解 confirmText,内联多语言表会画成空问句:今天没有已发 manifest 这样写,记录不立卡。Setup and login page: turning off email and password asks for no confirmation, and the login page still draws the email form when it is off #30299 已转给 domain:ui#1 席位,本 PR Refs #30299,落地时本席知会该席位由其判余项、关卡。
  • 落地条款:中继 pr_ready + automerge_enable。合并后由 Fixes 关 console: the settings page asks a toggle's confirmOffText before it saves the toggle turned off (ruling A on #30418, the domain:ui half) #30500,席位同一笔摘掉 pm:dispatched 并留落地评论。

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 5, 2026 16:53
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 5, 2026 16:53
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 5, 2026
Merged via the queue into main with commit c447019 Nov 5, 2026
60 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-ngbinh branch November 5, 2026 17:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

console: the settings page asks a toggle's confirmOffText before it saves the toggle turned off (ruling A on #30418, the domain:ui half)

1 participant