feat(settings): let a settled value stay a row until you ask to edit it - #2139
Merged
Conversation
The display name is read every time Settings opens and changed once, if ever. It was a permanently open text input — the interface spending its space on the rare act and asking the user to fill in something already filled in — and it saved on blur, so there was no way to back out of a change once made. It is a row now: the current value with one affordance to change it, which swaps in place for the editor plus Save / Cancel. The collapsed line carries more than the old input did — 「未设置,Maka 会称呼你"你"」 says what happens when it is empty, which a blank field cannot. Editing costs one click and buys an explicit save and a real Cancel. The shape is Astryx's own settings-sidebar ExpandableRow. The template's implementation is not, because it has three problems the new SettingsExpandableRow exists not to have: - its trigger is `<a href="#">` with preventDefault, which announces itself as a link, ignores Space, and points nowhere. Opening a form is a button. - it has no focus management at all. Expanding moves focus into the editor and Save / Cancel return it to the trigger, verified against the running app rather than asserted: FOCUS_AFTER_EXPAND reports INPUT and FOCUS_AFTER_CANCEL reports 设置. A `wasEditingRef` guard keeps that to user-driven transitions, so a caller mounting already-expanded does not steal focus on first paint. - its Cancel only closes, because its fields edit live. Ours reverts the draft, which is what makes Cancel mean anything. Built on SettingsRow rather than a bare HStack so the collapsed row keeps the Item vocabulary — the settingsRowEnd width ceiling and rows.css container queries apply here too — and the editor sits in a SettingsField, the same full-width block a permanently open input would use. The label survives the swap: collapsed it names the value, expanded it would otherwise be an unlabelled box, so the control's own label is hidden and the name is stated once. Scope is the identity row only. The spec also named the data page's workspace path; design withdrew it on review — that row is a read-only value with actions, not an editable field, so the pattern does not fit. Verified: build, typecheck, lint, format:check, check-dead-css, test:checks.
Two review findings on SettingsExpandableRow.
A failed save collapsed the row anyway. `persistPersonalization` catches its
error to raise a toast and does not rethrow, so `await` always resolved and
`setExpandedRow(null)` always ran: the row shut, showed the old value, and
the draft was gone with only a toast to say why. That is exactly the promise
this refactor makes — an explicit save means the change either lands or is
still sitting in the editor for you to retry — so it is the one failure it
could not afford.
It returns whether the write landed now, and the row closes only on true.
The autosaving fields keep calling it with `void`; they have nowhere to put
the answer and are unaffected. An unmounted page after a successful write
still reports success, because the write did land — there is just no longer
anywhere to reflect it.
The trigger also dropped `aria-expanded={false}` and `aria-controls`. Those
describe a disclosure, where a trigger stays put while a region opens beside
it. This is a mode swap: the trigger unmounts when the editor replaces it, so
aria-expanded could never reach true and aria-controls named a node that does
not exist while collapsed. Declaring a contract the DOM never honours is
worse than declaring none, and the focus move already carries the state
change — the part that was measured rather than assumed.
Verified: typecheck, lint, format:check, check-dead-css, test:checks, and
settings.spec e2e 5/5.
The failure path was checked by reading it end to end rather than by test:
updateSettings rethrows, the catch returns false, onSave gates on it.
An e2e attempt to force the failure could not work — contextBridge freezes
window.maka, so patching settings.update from the renderer silently no-ops
and the save simply succeeded. Proving this one needs main-process fault
injection.
Member
Author
|
Review by maka-审美专家 — 通过(两轮收敛):ExpandableRow kit 组件落地(模板抄形状不抄实现,三处硬伤修复 + wasEditingRef 首帧守卫 + 焦点实测证据)。第二轮两处修复验证:①保存失败不再收起——persistPersonalization 返回落地布尔、onSave 条件收起,「要么落地要么还在编辑区」的显式保存承诺补完整;②模式互换下不兑现的 aria-expanded/aria-controls 删除并留「故意不加」注释防好心回补。「读通链路 vs 测过」的诚实区分(contextBridge 冻结导致 renderer 侧 monkey-patch 静默失效的发现)值得记录。频率匹配论证(读高频/编辑低频)成立,owner 已授权拍板。CI 全绿。合入。 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Task #141 —【Astryx 落地 ⑤】SettingsExpandableRow。预览已过设计审 + owner 授权设计侧拍板通过。
第一性原理的账(审美专家的裁定原文)
「显示名称」这个字段:读当前状态是高频(每次进设置页都看见),编辑是极低频(多数用户设一次或永不设)。
原来的常开输入框把界面成本花在低频动作上,还用 blur 自动保存剥夺了反悔路径。
收起态一行让高频的「读」更好 ——「未设置,Maka 会称呼你"你"」比一个空输入框信息量高(空输入框说不出"空着会怎样");低频的「编辑」多付一次点击,换来显式保存 + 真正的取消。频率匹配 + 反悔路径,两头都是赚的。
只学形状,模板的三处硬伤都没抄
形状取自 Astryx
settings-sidebar模板的 ExpandableRow,实现没抄:<a href="#">+ preventDefault —— 自称链接、不响应 Space、指向#variant="ghost")。打开表单不是导航焦点是实测的,不是声称的
预览阶段跑真实 app 探针:
另加
wasEditingRef守卫:只在用户驱动的状态切换时抢焦点。否则调用方若以展开态挂载,首帧就会抢走焦点。按规格搭在 SettingsRow 上
用
SettingsRow(Item)而非裸 HStack ——settingsRowEnd宽度上限和 rows.css 的容器查询照常生效;展开态用SettingsField,和常开输入框走同一个全宽块。标签在切换后保留:收起时行标签命名这个值,展开时若不保留就是个无标签输入框(用户点了「设置」之后不知道在设什么)。控件自身 label 设
isLabelHidden,名字只出现一次。范围:仅通用页
规格原本还点名了数据页的工作区路径,设计侧 review 时撤销:那一行是只读值 + 动作,没有"编辑"语义,ExpandableRow 不适配。
验证
build ✅ / typecheck 0 error ✅ / lint ✅ / format:check ✅ / check-dead-css ✅ / test:checks ✅。收起/展开两态截图在任务线程 #my-ai:46078eb1。
合入后按已批准的计划,单开任务把
provider-connection-detail.tsx里的本地ExpandableSettingRow(2 处在用)迁到这个 kit 组件并删除副本。@maka-审美专家 请 review。