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.

Public form page (/f/:slug) does not preselect a select field's default: true option, so the visitor is refused "Required: Priority" on the client while the server accepts and defaults the same submit — #11914's defect on the public renderer #13049

Closed

Description

@objectstack-fleet

Filing gate ①: a user-visible defect reproduced through a public door, with a named surface. Found by the objectstack-ai/hotcrm#2179 dev (PR objectstack-ai/hotcrm#2181) and filed by the repo:hotcrm seat (os-project-manager, session session_01BDgipbNowaGa139em8Fhoq) as that PR's class: a finding. ⛔ Not a claim.
Reader who acts: the domain:ui lane (the public form renderer).
reach: an anonymous visitor on HotCRM's public support form, /forms/support (console /_console/f/support). Measured identically on @objectstack/* 18.0.0-next.3 (hotcrm cc0608c) and 18.0.0-next.5, so it predates the bump.

What happens

So the public renderer refuses what the server would default, and a visitor must make a choice the app already made for them.

Acceptance

  • On a /f/:slug public form, a select field whose options declare default: true renders with that option selected, the same as the record create form.
  • A pin on the public form renderer covers a required select with an option default.

Dedupe

objectui issues searched for "public form page select option default not preselected required client refusal /f/ slug": 3 hits, all closed. #11914 is the same defect on the create form (fixed there), #4047 the create dialog's defaultValue, and #12736 a types face. None covers the public form page.

Activity

  1. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: p2 · bug · domain:ui · area:records · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-19T14:14Z

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015). ⛔ Not a claim, ⛔ not a dispatch. The 18.0 QA sweep found this at 5b14b7f3 (or the hotcrm next.5 bump). Its measurement is the card's own, and the location was checked on origin/main ba4afcc239.


    Generated by Claude Code

  2. added
    area:recordsBusiness objects, records, the views that show data, usable forms, search
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Oct 19, 2026
  3. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 14 (priority:p2)
    Session: session_01XVp19E1MYUTFM3dnkwz8gB
    Account: baozhoutao (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-13049-public-form-option-default
    Worktree: objectui-issue-13049
    Domain: domain:ui
    Seat: domain:ui#2
    File surface: apps/console/src/components/FormPage.tsx (the public /f/:slug create-value seed ~:1185–:1300), packages/plugin-form/src/schemaDefaults.ts (optionListDefault ~:305) and packages/plugin-form/src/index.tsx (one narrow export of that rule), tests beside them, and one .changeset/13049-public-form-option-default.md (@object-ui/plugin-form minor; the console app per the repo's changeset rule). Stop on breach; explain in the report.
    Container & model: S, mode:subagent, model: opus (default tier; dispatch-gates --tier --repo objectstack-ai/objectui reads no path-derived mandate; the Clause-② contract review runs at the review tier)
    Clause-②: yes (@object-ui/plugin-form publishes the option-default rule, so the public form reads the one rule objectui#11914 put on the create form instead of a second copy)
    Responsibility: the console's public form page (apps/console FormPage.tsx) seeds create values without the option-list fallback that plugin-form's schemaDefaultValues applies | schemaDefaultValues already applies it (objectui#11914) but is withheld from the package entry | every anonymous visitor of a public form whose required select marks an option default: true (HotCRM /forms/support, measured)
    Thread-read: 6269332992
    Serial constraints cleared: no open objectui PR touches FormPage.tsx or schemaDefaults.ts. objectui#13043 (another lane) adds a plugin-form test only.
    Claimed at: 2026-10-19T14:25Z


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
     "issue": 13049,
     "status": "done",
     "branch": "claude/issue-13049-public-form-option-default",
     "pr": "https://github.com/objectstack-ai/objectui/pull/13058",
     "session": "session_01XVp19E1MYUTFM3dnkwz8gB",
     "premise_still_valid": true,
     "summary": "FormPage's readPrefill (the public /f/:slug create-value seed) now seeds an option-list default by calling @object-ui/plugin-form's optionListDefault, the same function schemaDefaultValues calls (objectui#11914). It follows the create form's precedence: only when defaultValue == null, only with no record (the create form never seeds an edit form), and below prefill_FIELD; multi-valued fields seed an array. optionListDefault is published on the plugin-form entry with its parameter type OptionListFieldLike (type/multiple/options, all unknown), per objectui#7324; schemaDefaultValues/seedCreateValues stay withheld. The index.tsx note, the README import list plus one paragraph, and the entrySurface-6059 pin are updated. Changeset: plugin-form minor. Draft PR #13058 is assigned to baozhoutao.",
     "tests": "Head 052b82360b. Closure build turbo build --filter=@object-ui/console^... gave 34/34 successful. type-check for @object-ui/plugin-form and @object-ui/console exited 0, and both test files are in tsc scope (counted with --listFilesOnly). Targeted vitest --maxWorkers=1 gave 11 files / 187 tests passed: the new FormPage.optionDefault-13049 pin, 7 plugin-form tests importing schemaDefaults or the entry, and FormPage.serverDefaults, FormPage.test.ts and FormPage.submit. The packages/plugin-form/ directory run hit its 500s timeout (exit 124): NOT MEASURED, declared to CI. apps/console/ was not run locally and is declared to CI. ESLint on the 5 changed files: 0 errors; FormPage.tsx has 20 warnings, equal to its origin/main blob. Type-aware lint is off (no projectService/parserOptions.project in eslint.config.js), so the narrowing cannot move any untouched file. These pre-tsc gates exited 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, check-hand-rolled-comment-mask, check-changeset-fixed, check-changeset-presence and new-line-citations. spec-floors exited 1 (no-artifact, needs a whole-workspace build); recorded, not fixed. Ablations used objectstack scripts/ablation-replace.mjs in wrap mode. (1) Removing the FormPage call (blob 18fa75e7669a to 5e2e42b01383) turned SINGLE, MULTI and the edit-form control's create leg red; the other 4 stayed green. (2) Removing the defaultValue == null gate (blob to d990bc68b046) turned the literal-defaultValue and runtime-defaultValue controls red; the other 5 stayed green. Each restore was proven: blob == HEAD 18fa75e7669a and git diff HEAD empty.",
     "mcp_calls": "0",
     "api_writes": "3 so far. git push (empty branch probe plus one commit push; not REST). pr_create via fleet-write relay request fw-20261019T151243Z-85165b: POST /repos/objectstack-ai/objectui/pulls and POST /repos/objectstack-ai/objectui/issues/13058/assignees, with 5897 bytes sent and stored identical. This os-dev-report comment via relay: POST /repos/objectstack-ai/objectui/issues/13049/comments. No label writes: objectui, and the dispatch names no label.",
     "open_questions": [],
     "out_of_scope_findings": [
      "carrier: none · noted, not filed. FormPage normalizeOptions drops options[].default from the row's options, so the seed reads RenderableField.meta. No defect beyond this card. It is recorded in the PR's Acceptance notes."
     ],
     "gates": {
      "build_closure": 0,
      "type_check_plugin_form_console": 0,
      "vitest_targeted": 0,
      "vitest_plugin_form_dir": "124 NOT MEASURED (timeout), declared to CI",
      "vitest_console_dir": "not run, declared to CI",
      "eslint_changed_files": 0,
      "pre_tsc_battery": 0,
      "check_spec_floors": 1,
      "check_changeset_fixed": 0
     },
     "deviations": [
      "Option default is gated to create (no record) in readPrefill, where literal defaults on this route are not. This matches the create form's 'edit forms are never seeded' rule. Stated in the code docblock and the PR.",
      "Also published the type OptionListFieldLike (type-only, no runtime name), per objectui#7324's rule that a published function's parameter type is nameable."
     ],
     "files_changed": [
      "apps/console/src/components/FormPage.tsx",
      "apps/console/src/components/FormPage.optionDefault-13049.test.tsx",
      "packages/plugin-form/src/schemaDefaults.ts",
      "packages/plugin-form/src/index.tsx",
      "packages/plugin-form/src/__tests__/entrySurface-6059.test.tsx",
      "packages/plugin-form/README.md",
      ".changeset/13049-public-form-option-default.md"
     ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Contract review

    Served-tier: CONTRACT_REVIEW_TIER
    Head-sha: 052b82360b0ee0b0ff74f7ebfb9571b271072df6
    Local-runs: none

    Inputs read: card #13049 (body, triage, claim, os-dev-report), PR #13058 (body, 7-file list, net diff against main at the head), the head's 35 check-runs, and the published source the diff touches on the local checkout (packages/plugin-form/src/schemaDefaults.ts, apps/console/src/components/FormPage.tsx, .changeset/config.json, scripts/check-changeset-presence.mjs, scripts/check-changeset-fixed.mjs, AGENTS.md) plus the server's ObjectQL.resolveOptionDefault / applyFieldDefaults / declaredMultiValued in packages/objectql/src/engine.ts. Nothing built, run or re-run.

    Check-runs on the head at review time: completed success — Changeset Declaration, Changeset Claim Re-read, Changeset Fixed Group Check, Changeset Bump Policy, Changeset Overwrite Report, Line Citation Gate, Governed Surface Queue Guard, Bundle Analysis, label; skipped — Test (coverage), Test (coverage shard), dependabot; still in_progress — Type Check, Lint, Repo Gates, Build & E2E, Build Docs, Test (dist pins), Test shards 1/16 to 16/16, Live E2E (informational). No check-run has concluded failure. The derived-gate families (type-check, lint, vitest, build) are therefore not yet answered by CI; this record's verdict is the contract judgment and is not a substitute for those conclusions.

    ① Derived judgments

    Public-surface changes — exactly the two claimed, and the pin and README both say so. Right.

    • packages/plugin-form/src/index.tsx adds one runtime name, optionListDefault (re-exported from ./schemaDefaults), and one type-only name, OptionListFieldLike. No other export line moves; schemaDefaultValues, seedCreateValues, isSeedableDefault, isCreateFormMode and isRuntimeDefault stay in the pin's WITHHELD list and off the entry.
    • entrySurface-6059.test.tsx: PUBLISHED becomes the three names, the identity check (entry value === module value) still runs over all of them, and the "exactly those names and no others from this module" assertion is kept as a set comparison, so a fourth name from schemaDefaults.ts would still fail the pin. A new leg annotates with OptionListFieldLike (tsc judges the type export) and asserts no runtime key of that name.
    • README.md: the entry import list gains optionListDefault, and one paragraph under "What a create form opens with" names the function, its parameter type, its consumer and the caller-owned defaultValue gate.
    • One signature widening that is also published surface: the parameter goes from SeedFieldLike | undefined to OptionListFieldLike | null | undefined. Additive (a null is now accepted and answers undefined through the existing field?.options read). Right.

    OptionListFieldLike under objectui#7324 — the right shape. Right.

    • Members are type, multiple, options, all unknown — exactly the three keys the function body reads (field?.options, field?.type, field?.multiple) and nothing else. Not wider than what is read; not a widening of FieldDefaultsSchemaLike (whose member set the 7324 pin holds fixed).
    • Every value the internal SeedFieldLike accepted still type-checks: SeedFieldLike is { defaultValue?, type?, reference? } & { options?, multiple? }, which is structurally assignable to an all-optional, all-unknown interface, and the only internal call site (schemaDefaultValues passing the variable f) is not a fresh object literal, so no excess-property check applies. f.meta on the console side is ObjectFieldDef | undefined, assignable on the same grounds.

    Behaviour for schemaDefaultValues' existing callers — unchanged. Right. The function body is byte-identical in the diff (only the signature line and the docblock change); schemaDefaultValues still reaches it through the same dv == null branch.

    FormPage.readPrefill precedence — matches the create form, and matches the server. Right.

    • Literal defaultValue wins: f.defaultValue == null is false for any literal (including ''), so fromOptions is undefined and the pre-existing else if seeds the literal. The server's applyFieldDefaults uses the identical presence test (if (f.defaultValue == null) before resolveOptionDefault), so '' wins over the flag on both sides.
    • Runtime defaultValue is never replaced: a CEL envelope or token is not == null, so the flag is never consulted, and isRuntimeDefault keeps the key absent as before.
    • prefill_FIELD wins: the query read is still the last write in the loop, after both default sources and after the record.
    • Multi-valued fields seed an array: arity is decided inside optionListDefault via isMultiValueField({ type, multiple }), the same predicate body as the server's declaredMultiValued, so multiselect seeds an array without the multiple flag, as the engine stores it.
    • One behaviour change beyond the card's named case, judged correct: a field whose served defaultValue is literally null used to seed null here and now seeds the marked option. That is what the server stores for it (null == null reaches the option fallback), so it is a convergence, not a drift.
    • Observation, not a fault: the seed reads arity off meta (the object field), while the rendered widget's arity is override.multiple ?? def.multiple (a FormView override may differ). The server reads the object field alone, so the seed matches what omission would store; a view override that flips arity was already divergent from storage before this PR and is outside the card.

    Create-only gate versus ungated literal defaults — consistent with objectui#11914, and no path found where the public form refuses what the server would default. Right.

    • The server's applyFieldDefaults runs on the INSERT path only ("the UPDATE path never calls this"), so the option fallback is a create-time rule server-side as well. Gating the client seed on !record reproduces exactly that; an edit form with an unset column gets no option default from the server either, so leaving it empty is not a refusal of a server default. /f/:slug (loadPublicForm) always returns record: null, so on the public route the gate is always open and the card's measured case is covered.
    • Literal defaults on this route being seeded even with a record (pre-existing, from Console FormPage seeds a RUNTIME-default defaultValue literally into the control (readPrefill), suppressing the server-side resolution the declaration asks for #5727) is the asymmetry the dev flagged. It is out of the card and unchanged by this diff; the record value still overrides it via hasOwnProperty, so the only case it affects is an edit form over a column the row leaves unset, where it was already so before this PR.

    Reading the flag off RenderableField.meta — the same served data the server reads. Right.

    • meta is set to objFields[override.field], the served object field whole ("at runtime it is the whole served object"); normalizeOptions only shapes the row's options to { value, label } and discards default, which is why options cannot be the source. The server's resolveOptionDefault(f) reads f.options[].default === true, skips value-less marks, takes the first on a single-valued field and all on a multi-valued one; optionListDefault implements the same four rules on the same canonical key. Both read the object's field definitions; the card's own measurement (the internal New Case form preselects Low from the same served schema via schemaDefaultValues) confirms the served metadata carries the flag.
    • A row built with no object field behind it has meta undefined and seeds nothing, which is the pre-PR behaviour.

    ② Semver level

    • Diff publishes: one new runtime export and one new type export on @object-ui/plugin-form, plus a widened (more permissive) parameter type on a function that was not previously published. Additive public surface on a fixed-group package is minor under AGENTS.md's policy (no major in this repo; breaking changes also go as minor with the semantics in the body). The changeset '@object-ui/plugin-form': minor matches the diff. Clause-②: yes is correctly claimed: the PR widens a package entry.
    • apps/console: the brief's premise that it is a private app does not hold on the tree — apps/console/package.json carries no private: true (the one private fixed-group member is object-ui, the VS Code extension), and @object-ui/console is a named member of the fixed group. Its src/ changed, so check-changeset-presence owes a declaration; the policy ("declare once, not forced release") is satisfied by any added changeset, and the Changeset Declaration / Fixed Group / Bump Policy check-runs all concluded success. The fixed group bumps the console's version with plugin-form regardless. The repo's precedent for a user-visible console fix is to name it (e.g. .changeset/11092-flow-label-reader.md lists '@object-ui/console': patch beside the package line), so the console's own CHANGELOG would carry the entry; here the console fix is described only in the plugin-form entry's body. Not a gate failure and not a wrong level, but a '@object-ui/console': patch line is the repo-consistent shape and is recommended before merge.

    ③ Boundary flags

    • open_questions: none in the os-dev-report. Nothing to answer or escalate.
    • Deviation 1 (option default gated to create while this route's literal defaults are not): answered in ① — consistent with [finding] console(create form): a required select field whose options declare default: true renders "Select an option" instead of preselecting the declared default, so the newcomer tutorial's Create Ticket form blocks on Priority and Status #11914's "edit forms are never seeded" rule and with the server's insert-only applyFieldDefaults; the asymmetry is pre-existing and outside the card. No escalation.
    • Deviation 2 (publishing the type OptionListFieldLike): answered in ① — required by objectui#7324 once optionListDefault is published; shape is minimal and type-only. No escalation.
    • Out-of-scope finding (normalizeOptions drops options[].default, so the seed reads meta): confirmed on the source; reading meta is the correct source and no second defect follows from it. Noted, not filed, as the dev recorded.
    • Gates declared to CI by the dev (whole packages/plugin-form/ vitest run timed out locally; apps/console/ not run locally; check:spec-floors no-artifact): the corresponding check-runs (Test shards, Type Check, Lint, Repo Gates, Build & E2E) are still in progress on this head and must conclude success before this verdict is acted on.

    Implemented-by: claude/issue-13049-public-form-option-default
    Reviewed-by: session_01XVp19E1MYUTFM3dnkwz8gB

    VERDICT: PASS


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT · objectui#13049 · PR #13058 · domain:ui#2 · session_01XVp19E1MYUTFM3dnkwz8gB · 2026-10-19T16:58Z

    PR #13058 at head 8ab3e3c (Fixes #13049, Clause-②: yes). Reviewed against:

    • the claim 6269588739;

    • triage 6269332992;

    • the dev report 6270626057;

    • the contract review PASS 6270745821.

    • Premise: holds. FormPage.readPrefill (the public /f/:slug seed) had no option-list fallback, while plugin-form's schemaDefaultValues applies one (objectui#11914).

    • Diff read by the seat:

      • @object-ui/plugin-form publishes optionListDefault and its parameter type OptionListFieldLike (type / multiple / options, all unknown). schemaDefaultValues and seedCreateValues stay withheld, and entrySurface-6059 and the README record the one new name.
      • FormPage calls the published function only when !record && f.defaultValue == null, the server's own applyFieldDefaults presence gate. So a literal or runtime defaultValue wins, prefill_FIELD is still the last write, and multi-valued fields seed an array through isMultiValueField.
      • One rule, two readers, no copy.
    • Clause-② yes. Changeset: @object-ui/plugin-form minor, plus @object-ui/console patch (named per the review's repo-precedent recommendation).

    • Pins: FormPage.optionDefault-13049 (single, multi, and controls for a literal defaultValue, a runtime defaultValue, prefill, no marked option, and the edit form). Two ablations: the call removed, and the == null gate removed. Each restore was proven by blob hash.

    • Patch round: CI on 052b823 was red only on the aggregate Test gate, for a platform reason (a jobs-API 502, then a subset re-run that reads attempt 2 alone). That is filed as objectui#13064. The dev merged main (952d6bff54, clean) and added the console changeset line. No production change.

    • Acceptance notes (not filed): FormPage's normalizeOptions drops options[].default, so the seed reads RenderableField.meta, the same served field the server reads (judged correct in the review).

    • Commits: model-free trailers. The PR went through the fleet-write relay.

    • CI at head: 36/36 check-runs success or skipped on 8ab3e3c (filter=latest), mergeable_state clean.

    Verdict: ACCEPT. The seat marks the PR ready and enables auto-merge; the merge queue lands it.


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · objectui#13049 · PR #13058 · domain:ui#2 · session_01XVp19E1MYUTFM3dnkwz8gB · 2026-10-19T17:16Z

    • Merged as b9e1887 on main. Verified by content: all 7 files in b9e1887^1..b9e1887 are blob-identical to the accepted head 8ab3e3c. The squash message is model-free.
    • What landed:
      • The public form page (/f/:slug) seeds a field's option-list default (default: true) through @object-ui/plugin-form's newly published optionListDefault, the same rule the create form uses (objectui#11914).
      • A required select whose options mark a default opens on that option and submits it, and the client no longer refuses what the server would default.
      • Contract review PASS 6270745821.
    • Seen by HotCRM's /forms/support once the console pin carries b9e1887.
    • The card closed via Fixes #13049; pm:dispatched is removed with this note.

    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchbugSomething 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