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: Save is accepted while a visible required field is empty — the console draws the required asterisk but never checks it, though the spec declares required as "renderer + server-side validation" #13239
Claim: PM loop round 17 (priority:p2)
Session: session_01XVp19E1MYUTFM3dnkwz8gB
Account: baozhoutao (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-13239-settings-required
Worktree: objectui-issue-13239
Domain: domain:ui
Seat: domain:ui#2
File surface (one root cause: the settings page draws required but never checks it before Save):
apps/console/src/pages/settings/SettingsView.tsx (a pre-save check) and SettingsField.tsx (the inline field message);
packages/i18n/src/locales/*.ts, only if no existing "required" message fits (every locale, key parity);
tests in apps/console/src/pages/settings/__tests__/;
one .changeset/13239-settings-required.md (@object-ui/consolepatch, plus @object-ui/i18n if its rows move).
⛔ 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)
Clause-②: no (internal console page)
Responsibility: the console honours the renderer half of required ("renderer + server-side validation", settings-manifest.zod.ts:436) with an asterisk only | the server's TOUCH gate judges only changed keys, by the seat ruling on objectstack-ai/objectstack#24767 | every admin saving a settings page with a visible required field left empty (measured: Setup → Email, HotCRM F-18)
Thread-read: none
Serial constraints cleared:
objectui#13259 (open, another seat) edits SettingsView.tsx for objectui#13254 (language keeps the draft). Whichever lands second merges main.
objectui#13145 (961b69f, unset to null) and objectui#13163 (leave guard) landed.
Claimed at: 2026-10-20T13:26Z
{
"issue": 13239,
"status": "done",
"branch": "claude/issue-13239-settings-required",
"pr": "https://github.com/objectstack-ai/objectui/pull/13262",
"session": "session_01XVp19E1MYUTFM3dnkwz8gB",
"premise_still_valid": true,
"summary": "Premise held on origin/main 84b43263fd: SettingsField drew the asterisk only, and SettingsView had no pre-save required check. SettingsView.onSave now runs emptyRequiredKeys before any request. It checks every specifier that has a key, required true and an input type, is visible by the page's own evalVisibility over the live values, and is not locked. It judges the effective value (draft over stored) with the server's own empty predicate from validatePatch: null, undefined, or a blank-after-trim string; false and 0 are values. When a field is empty, no request is sent. Each empty field gets form.fieldRequired (an existing key in every pack) through the same fieldErrors slot that server SETTINGS_VALIDATION refusals use. Focus moves to the first empty field, and the error toast reuses fieldErrorsToast with the row labels. SettingsField rows gain data-settings-key so the page can find the row to focus, and the toggle row gains the same error slot. Server contract unchanged; no new export, prop or i18n key.",
"tests": "At source commit ac2e0e6a91 (head 74419a69a7 adds only the changeset .md). (1) New SettingsView.requiredCheck-13239.test.tsx, pins (a) (b) (c) (d) plus an env-locked-with-value case and a draft-clears-stored-Host case: Test Files 1 passed (1), Tests 6 passed (6), lock VERDICT command-exit 0. (2) Family: every test file naming SettingsView or SettingsField plus the WorkspaceTimezonePrompt suites, 28 files including 3 in app-shell: Test Files 28 passed (28), Tests 232 passed (232), VERDICT command-exit 0. (3) Ablation via objectstack scripts/ablation-replace.mjs: anchor 'if (missing.length > 0) {' replaced by 'if (false && missing.length > 0) {'. Anchor x1 -> x0, blob 3a3e4640187d -> c0af13347bb1. (a), (b) and the draft-clear case went red (3 failed | 3 passed); the controls stayed green. Restore: blob after restore 3a3e4640187d == HEAD blob, git diff HEAD empty, and a trap restore was also armed. No dist step applies: apps/console tests import the source. (4) Typecheck: turbo build of the @object-ui/console^... closure, 34/34 tasks, VERDICT 0; then pnpm --filter @object-ui/console run type-check: VERDICT command-exit 0, 0 'error TS'. The console tsconfig includes src with no test exclude. (5) eslint narrowed to the 3 changed source and test files: --format json counted 3 files, 0 errors, 6 warnings, all on lines this diff did not write. Config invariance: --print-config shows no project or projectService (not type-aware), so the diff cannot move a verdict on an untouched file. Whole pnpm lint is left to CI. (6) Gates in the battery, each exit 0: check: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, check-hand-rolled-comment-mask.mjs, check-changeset-fixed.mjs, check-changeset-presence.mjs, check:new-line-citations (0 new). Declared to CI: the full apps/console test directory. CI state at report time: in_progress / not read.",
"mcp_calls": "0",
"api_writes": "2 relay dispatches (POST objectstack-ai/objectstack dispatches): pr_create (POST /repos/objectstack-ai/objectui/pulls, draft, then POST /repos/objectstack-ai/objectui/issues/13262/assignees with baozhoutao; read-back identical, 5309 bytes) and this os-dev-report comment (POST /repos/objectstack-ai/objectui/issues/13239/comments). Plus 3 git pushes (empty branch probe, 2 commits). 0 label writes: objectui path labels belong to labeler.yml, and the dispatch named no labels.",
"files_changed": [
"apps/console/src/pages/settings/SettingsView.tsx",
"apps/console/src/pages/settings/SettingsField.tsx",
"apps/console/src/pages/settings/__tests__/SettingsView.requiredCheck-13239.test.tsx",
".changeset/13239-settings-required.md"
],
"gates": "type-check console 0; vitest new 0 (6/6); vitest family 0 (28 files, 232 tests); eslint narrowed 0 (0 errors); battery of 15 check:* plus check-hand-rolled-comment-mask all 0; check-changeset-fixed 0; check-changeset-presence 0; check:new-line-citations 0. check:spec-floors not run (not in the dispatch list).",
"line_budget": "n/a: no skills/** and no governed surface touched",
"deviations": [
"Ruling 4 reading: every locked row is skipped, not only a locked row with a value. Measured: the service resolves an env lock as locked: true, lockedReason 'Set via env: NAME', with the env value in value, and the row's control is disabled. A locked row with a value is therefore filled, as ruled. A locked row without a value (for example an env var set to an empty string) is also skipped, because checking it would block Save on a field the admin cannot edit.",
"The SettingsField toggle row gains the field-error slot (FieldError plus aria-invalid/aria-describedby on the Switch). It was the one input row outside wrapper, so a required toggle with no value would have blocked with nothing marked. This is still inside the claimed SettingsField inline-message surface.",
"Commit trailers follow the dispatch order (Co-authored-by: Claude, no model name), not the harness attribution reminder, which names a model.",
"Union runs were at ac2e0e6a91; the later head 74419a69a7 differs only by the .changeset md file."
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: none. Noted, not filed. In SettingsField, cloneElement of aria-invalid/aria-describedby onto the Radix Select root (the select type without valueDomain) and onto RadioGroup does not reach a DOM control, so those rows show the message without aria-invalid. Read-only inference, not measured in a browser. Not in the PR's Acceptance notes: the body is written once and the note came later, so the seat may add it there. Dedupe words: settings select aria-invalid cloneElement, settings field error radix select."
]
}
Scope note from objectstack triage (R539): objectstack#24882 measured the same mechanism for typed values. · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-20T14:14Z
SettingsView saves from a button with no <form>, so the browser's constraint validation never runs for url / email inputs either (SettingsView.tsx:209–216 at the console pin). Branding's Logo URL accepted "not a url" with 200.
The server half (format checks for email / url / color in validatePatch, per ADR-0007) is objectstack#24882's.
This card's Save-time check covers both: a visible required field that is empty, and a visible typed field (email, url, color) whose value fails its format. Both are flagged inline on the field. One extra pin: Branding's Logo URL not a url blocks Save.
PR #13262: new head 54f4078306. It is a merge of origin/main (588d11d2b1, which carries objectui#13259 / objectui#13254) into 74419a69a7. No rebase and no force-push. The PR body was not edited.
Settings family under the lock at 54f4078306, including SettingsView.languageKeepsDraft-13254.test.tsx and SettingsView.requiredCheck-13239.test.tsx: Test Files 29 passed (29), Tests 239 passed (239), VERDICT command-exit 0. The first lock attempt hit queue-timeout 99 (holder: the issue-13261 run). The retry on the same slot was granted.
@object-ui/console^... closure build, then pnpm --filter @object-ui/console run type-check: VERDICT command-exit 0, 0 error TS.
eslint on the 3 touched source and test files: 3 files, 0 errors, 5 warnings, none on lines this branch wrote. Battery of 15 check:*, plus check:new-line-citations, check-hand-rolled-comment-mask, check-changeset-fixed and check-changeset-presence: all exit 0.
Writes this round: 1 git push (the merge commit) and this comment, through the relay. MCP calls: 0.
Clause-②: no. An internal console page; no export, prop or i18n key added; the server contract is unchanged. No contract review is owed, and the seat read the report.
Seat review: SettingsView.onSave runs emptyRequiredKeys before any request. It checks every keyed, required, input-typed specifier that is visible by the page's own evalVisibility over the live values and is not locked. It judges the effective value (draft over stored) with the server's own empty predicate from validatePatch: null, undefined, or blank after trim; false and 0 are values. When a field is empty, no request is sent. Each empty field gets form.fieldRequired (an existing key in every pack) through the same fieldErrors slot that server SETTINGS_VALIDATION refusals use. Focus moves to the first empty field, and the toast reuses fieldErrorsToast. This honours the renderer half of settings-manifest.zod.ts:436.
Deviations accepted: (1) Ruling 4: every locked row is skipped, not only a locked row with a value. An env lock disables the control, so checking an empty locked row would block Save on a field the admin cannot edit. (2) The toggle row gains the field-error slot so a required toggle is marked, within the claimed SettingsField surface.
Pins: (a) Mail with Host empty, changing only From name: no request, and Host marked; (b) blocked on From email; (c) CONTROL: all filled saves; (d) CONTROL: hidden and empty does not block; plus env-locked-with-value and draft-clears-stored-Host cases. The ablation turned (a), (b) and the draft-clear case red with the controls green, and the restore was proven by blob. The settings family (28 files, 232 tests) is green.
Changeset: @object-ui/consolepatch.
CI: all 36 check-runs completed on the head, 33 passed and 3 skipped.
Findings: Acceptance notes. Cloning aria-invalid / aria-describedby onto a Radix Select root (the select type without valueDomain) and onto RadioGroup likely does not reach a DOM control, so those rows would show the message without aria-invalid. This is a read-only inference, unmeasured, so it is not filed.
Next: ready + auto-merge; landing is verified by content on main.
Verified by content: each of the 4 files the PR changes has the same blob at 1ca759a16f as at the accepted head 54f4078306. The merge message carries no model identifier.
Landed: a settings page blocks Save while a visible, unlocked required field is empty. It sends no request, marks the field inline through the server-error slot, and focuses the first such field. Hidden and env-locked rows are not checked, and the server's TOUCH gate is unchanged. This is the renderer half of settings-manifest.zod.ts's required.
Activity
objectstack-fleet commentedon Oct 20, 2026
Claim: PM loop round 17 (
priority:p2)Session:
session_01XVp19E1MYUTFM3dnkwz8gBAccount:
baozhoutao(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-13239-settings-requiredWorktree:
objectui-issue-13239Domain:
domain:uiSeat:
domain:ui#2File surface (one root cause: the settings page draws
requiredbut never checks it before Save):apps/console/src/pages/settings/SettingsView.tsx(a pre-save check) andSettingsField.tsx(the inline field message);packages/i18n/src/locales/*.ts, only if no existing "required" message fits (every locale, key parity);apps/console/src/pages/settings/__tests__/;.changeset/13239-settings-required.md(@object-ui/consolepatch, plus@object-ui/i18nif its rows move).⛔ 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)Clause-②: no (internal console page)
Responsibility: the console honours the renderer half of
required("renderer + server-side validation",settings-manifest.zod.ts:436) with an asterisk only | the server's TOUCH gate judges only changed keys, by the seat ruling on objectstack-ai/objectstack#24767 | every admin saving a settings page with a visible required field left empty (measured: Setup → Email, HotCRM F-18)Thread-read: none
Serial constraints cleared:
SettingsView.tsxfor objectui#13254 (language keeps the draft). Whichever lands second mergesmain.961b69f, unset to null) and objectui#13163 (leave guard) landed.Claimed at: 2026-10-20T13:26Z
Generated by Claude Code
objectstack-fleet commentedon Oct 20, 2026
os-dev-report
{ "issue": 13239, "status": "done", "branch": "claude/issue-13239-settings-required", "pr": "https://github.com/objectstack-ai/objectui/pull/13262", "session": "session_01XVp19E1MYUTFM3dnkwz8gB", "premise_still_valid": true, "summary": "Premise held on origin/main 84b43263fd: SettingsField drew the asterisk only, and SettingsView had no pre-save required check. SettingsView.onSave now runs emptyRequiredKeys before any request. It checks every specifier that has a key, required true and an input type, is visible by the page's own evalVisibility over the live values, and is not locked. It judges the effective value (draft over stored) with the server's own empty predicate from validatePatch: null, undefined, or a blank-after-trim string; false and 0 are values. When a field is empty, no request is sent. Each empty field gets form.fieldRequired (an existing key in every pack) through the same fieldErrors slot that server SETTINGS_VALIDATION refusals use. Focus moves to the first empty field, and the error toast reuses fieldErrorsToast with the row labels. SettingsField rows gain data-settings-key so the page can find the row to focus, and the toggle row gains the same error slot. Server contract unchanged; no new export, prop or i18n key.", "tests": "At source commit ac2e0e6a91 (head 74419a69a7 adds only the changeset .md). (1) New SettingsView.requiredCheck-13239.test.tsx, pins (a) (b) (c) (d) plus an env-locked-with-value case and a draft-clears-stored-Host case: Test Files 1 passed (1), Tests 6 passed (6), lock VERDICT command-exit 0. (2) Family: every test file naming SettingsView or SettingsField plus the WorkspaceTimezonePrompt suites, 28 files including 3 in app-shell: Test Files 28 passed (28), Tests 232 passed (232), VERDICT command-exit 0. (3) Ablation via objectstack scripts/ablation-replace.mjs: anchor 'if (missing.length > 0) {' replaced by 'if (false && missing.length > 0) {'. Anchor x1 -> x0, blob 3a3e4640187d -> c0af13347bb1. (a), (b) and the draft-clear case went red (3 failed | 3 passed); the controls stayed green. Restore: blob after restore 3a3e4640187d == HEAD blob, git diff HEAD empty, and a trap restore was also armed. No dist step applies: apps/console tests import the source. (4) Typecheck: turbo build of the @object-ui/console^... closure, 34/34 tasks, VERDICT 0; then pnpm --filter @object-ui/console run type-check: VERDICT command-exit 0, 0 'error TS'. The console tsconfig includes src with no test exclude. (5) eslint narrowed to the 3 changed source and test files: --format json counted 3 files, 0 errors, 6 warnings, all on lines this diff did not write. Config invariance: --print-config shows no project or projectService (not type-aware), so the diff cannot move a verdict on an untouched file. Whole pnpm lint is left to CI. (6) Gates in the battery, each exit 0: check: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, check-hand-rolled-comment-mask.mjs, check-changeset-fixed.mjs, check-changeset-presence.mjs, check:new-line-citations (0 new). Declared to CI: the full apps/console test directory. CI state at report time: in_progress / not read.", "mcp_calls": "0", "api_writes": "2 relay dispatches (POST objectstack-ai/objectstack dispatches): pr_create (POST /repos/objectstack-ai/objectui/pulls, draft, then POST /repos/objectstack-ai/objectui/issues/13262/assignees with baozhoutao; read-back identical, 5309 bytes) and this os-dev-report comment (POST /repos/objectstack-ai/objectui/issues/13239/comments). Plus 3 git pushes (empty branch probe, 2 commits). 0 label writes: objectui path labels belong to labeler.yml, and the dispatch named no labels.", "files_changed": [ "apps/console/src/pages/settings/SettingsView.tsx", "apps/console/src/pages/settings/SettingsField.tsx", "apps/console/src/pages/settings/__tests__/SettingsView.requiredCheck-13239.test.tsx", ".changeset/13239-settings-required.md" ], "gates": "type-check console 0; vitest new 0 (6/6); vitest family 0 (28 files, 232 tests); eslint narrowed 0 (0 errors); battery of 15 check:* plus check-hand-rolled-comment-mask all 0; check-changeset-fixed 0; check-changeset-presence 0; check:new-line-citations 0. check:spec-floors not run (not in the dispatch list).", "line_budget": "n/a: no skills/** and no governed surface touched", "deviations": [ "Ruling 4 reading: every locked row is skipped, not only a locked row with a value. Measured: the service resolves an env lock as locked: true, lockedReason 'Set via env: NAME', with the env value in value, and the row's control is disabled. A locked row with a value is therefore filled, as ruled. A locked row without a value (for example an env var set to an empty string) is also skipped, because checking it would block Save on a field the admin cannot edit.", "The SettingsField toggle row gains the field-error slot (FieldError plus aria-invalid/aria-describedby on the Switch). It was the one input row outside wrapper, so a required toggle with no value would have blocked with nothing marked. This is still inside the claimed SettingsField inline-message surface.", "Commit trailers follow the dispatch order (Co-authored-by: Claude, no model name), not the harness attribution reminder, which names a model.", "Union runs were at ac2e0e6a91; the later head 74419a69a7 differs only by the .changeset md file." ], "open_questions": [], "out_of_scope_findings": [ "carrier: none. Noted, not filed. In SettingsField, cloneElement of aria-invalid/aria-describedby onto the Radix Select root (the select type without valueDomain) and onto RadioGroup does not reach a DOM control, so those rows show the message without aria-invalid. Read-only inference, not measured in a browser. Not in the PR's Acceptance notes: the body is written once and the note came later, so the seat may add it there. Dedupe words: settings select aria-invalid cloneElement, settings field error radix select." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 20, 2026
Scope note from objectstack triage (R539): objectstack#24882 measured the same mechanism for typed values. ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-20T14:14ZSettingsViewsaves from a button with no<form>, so the browser's constraint validation never runs forurl/emailinputs either (SettingsView.tsx:209–216at the console pin). Branding's Logo URL accepted "not a url" with 200.email/url/colorinvalidatePatch, per ADR-0007) is objectstack#24882's.requiredfield that is empty, and a visible typed field (email,url,color) whose value fails its format. Both are flagged inline on the field. One extra pin: Branding's Logo URLnot a urlblocks Save.Generated by Claude Code
objectstack-fleet commentedon Oct 20, 2026
os-dev-report addendum (merge round)
PR #13262: new head
54f4078306. It is a merge oforigin/main(588d11d2b1, which carries objectui#13259 / objectui#13254) into74419a69a7. No rebase and no force-push. The PR body was not edited.reactimport line inSettingsView.tsx. Main droppeduseCallback(the console: switching the display language on a settings page discards its unsaved edits in place — SettingsView reloads its namespace whenever the translation function changes #13254 load effect is now keyed on the namespace alone), and this branch addeduseRef(the focus target of a blocked save). Resolved touseEffect, useMemo, useRef, useState. The two behaviours touch disjoint logic: console: switching the display language on a settings page discards its unsaved edits in place — SettingsView reloads its namespace whenever the translation function changes #13254 changes the load effect, and this branch changesonSavebefore the request plus the focus effect. Nothing was picked away.54f4078306, includingSettingsView.languageKeepsDraft-13254.test.tsxandSettingsView.requiredCheck-13239.test.tsx:Test Files 29 passed (29),Tests 239 passed (239), VERDICT command-exit 0. The first lock attempt hit queue-timeout 99 (holder: the issue-13261 run). The retry on the same slot was granted.@object-ui/console^...closure build, thenpnpm --filter @object-ui/console run type-check: VERDICT command-exit 0, 0error TS.check:*, pluscheck:new-line-citations,check-hand-rolled-comment-mask,check-changeset-fixedandcheck-changeset-presence: all exit 0.Generated by Claude Code
objectstack-fleet commentedon Oct 20, 2026
ACCEPT — PR #13262 @
54f40783063aa8ac744fbc54cf1f1513e41763b8·domain:uiseat 2 (session_01XVp19E1MYUTFM3dnkwz8gB)SettingsView.onSaverunsemptyRequiredKeysbefore any request. It checks every keyed,required, input-typed specifier that is visible by the page's ownevalVisibilityover the live values and is not locked. It judges the effective value (draft over stored) with the server's own empty predicate fromvalidatePatch: null, undefined, or blank after trim;falseand0are values. When a field is empty, no request is sent. Each empty field getsform.fieldRequired(an existing key in every pack) through the samefieldErrorsslot that serverSETTINGS_VALIDATIONrefusals use. Focus moves to the first empty field, and the toast reusesfieldErrorsToast. This honours the renderer half ofsettings-manifest.zod.ts:436.SettingsFieldsurface.@object-ui/consolepatch.main(54f4078306, no rebase). The only conflict was the React import line. console: switching the display language on a settings page discards its unsaved edits in place — SettingsView reloads its namespace whenever the translation function changes #13254 changes the load effect and Settings pages: Save is accepted while a visible required field is empty — the console draws the required asterisk but never checks it, though the spec declaresrequiredas "renderer + server-side validation" #13239 changesonSaveand adds the focus effect, which are disjoint. The settings family including both suites (29 files, 239 tests) and the console type-check are green at the merge head.aria-invalid/aria-describedbyonto a RadixSelectroot (the select type withoutvalueDomain) and ontoRadioGrouplikely does not reach a DOM control, so those rows would show the message withoutaria-invalid. This is a read-only inference, unmeasured, so it is not filed.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_01XVp19E1MYUTFM3dnkwz8gBmain1ca759a16f.1ca759a16fas at the accepted head54f4078306. The merge message carries no model identifier.settings-manifest.zod.ts'srequired.pm:dispatchedis removed.Generated by Claude Code
fix(console): block a settings save while a visible required field is…