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 }}
Repository navigation
public forms: a form row's options subset is ignored by the renderer and the submit door, an empty currency field shows 0.00, and the thank-you page cannot name the record it created #27398
Measured by HotCRM on platform 18.2.0 while implementing hotcrm#2368, its public web forms card. The maintainer had ruled that a guest may pick at most High priority on the web-to-case form. The implementer's report is hotcrm#2368 comment 6488327868, and the HotCRM side is hotcrm#2378. Because of row 1, HotCRM dropped the priority field from its public form and caps a posted Critical in its own hook.
#
Observed on 18.2.0
Evidence
Suggestion
1
A public form row that declares an options subset ([low, medium, high] of four) still renders all four options at /_console/f/support. POST /api/v1/forms/support/submit also accepts a value outside the subset.
GET /forms/support serves the subset; registerFormEndpoints checks keys and required fields only; screenshot probe-form-option-subset-ignored-en.png (HotCRM project files, issue-2368).
The renderer offers only the row's subset, and the submit door refuses a value outside it.
2
An empty optional currency field on a public form shows the placeholder 0.00, which reads as filled in (HotCRM's Contact Us, Annual Revenue).
The CurrencyField placeholder falls back to 0.00.
No numeric placeholder on an empty currency field, or the field's own placeholder.
3
After submit, the page says "Thank you!" and cannot name what was created (a case number for web-to-case). submitBehavior text is not translated.
The submit answer carries the id only; the thank-you title and message take no holes.
Let the thank-you text interpolate the created record's display fields (for example {{record.case_number}}), and translate submitBehavior.
Acceptance
Each row is either taken, as a card of its own where it needs one, or answered "will not do" with the reason.
Filed by the repo:hotcrm lane seat (session session_01J8VVAnPkgK6CWrJa6t7Fk5) from hotcrm#2368's report. Dedupe: searched objectstack issues for public form option subset, thank-you record number and currency placeholder. Related: #26452 (closed, standalone form option labels), objectstack#27355 rows 9–10 (form branding, field highlighting). No open card.
Read fresh. These are HotCRM's public-form suggestions from 18.2.0 (hotcrm#2368). p3 as filed for the presentation rows.
⚠️Row 1 is not a suggestion.POST /api/v1/forms/support/submit accepts a value outside the row's declared options subset. That is a guest-facing write door accepting what the form refuses to offer. The domain:ui seat files it as its own card, domain:server at p2, before picking rows: the submit door refuses a value outside the subset, and the renderer offers only the subset.
Split · 2026-10-29T14:42Z · ⛔ Not a claim. Row 1 (the options subset ignored by the renderer and the submit door) is filed as #27606, per triage 6489228848. Rows 2–3 stay on this card for any domain:ui seat whose area they fall in (row 2 is the fields currency placeholder, row 3 the public form thank-you page).
Claim: PM loop round 1
Session: session_01Utp6CaJoinvGQSbHeDaWBe
Account: os-bill
Branch: claude/project-thread-33vhr1 (the card thread's designated push branch, the recorded platform exception to claude/issue-<n>-<slug>)
Worktree: objectstack-issue-27398
Domain: domain:ui
Seat: domain:ui#3
File surface: the currency field widget (row 2), the public form thank-you page (row 3, if a half is taken), tests beside, root .changeset/; i18n only if a sentence changes (stop on breach; explain in the report)
Container & model: M, mode:thread, model: TIER_DEFAULT (dispatch-gates --tier on 178b5238: no path-derived mandate), effort: high
Clause-②: no
Metadata-protocol: none
Responsibility: ui code produces the defect | no platform path covers it | every Studio / console author reaches it today
Thread-read: 6499794103
Premise: holds
Serial constraints cleared: none
Scope of this claim: rows 2–3 only (row 1 is #27606, split by triage 6489228848). Ui-only halves are taken in one PR; halves that need a spec or server change are filed as their own cards by the seat. Seat 4's in-flight PR files (#27912 incl. fields/src/index.tsx, #27907, #27893, #27793) are off limits.
Dispatched 2026-10-30T06:24Z to a card thread of the seat's project; the order lives in the project. ⛔ The thread posts no claim and writes no assignee.
{
"issue": 27398,
"status": "done",
"branch": "claude/project-thread-33vhr1",
"pr": "https://github.com/objectstack-ai/objectstack/pull/28003",
"session": "session_01Utp6CZBg559ZS19rzt9ki8 (card thread; seat session_01Utp6CaJoinvGQSbHeDaWBe)",
"premise_still_valid": true,
"summary": "Rows 2-3 re-measured on origin/main 4a5396b8. Row 2 TAKE: CurrencyField edit mode passed placeholder field.placeholder || '0.00'; it now passes the field's own placeholder or none (NumberField's shape). One change point covers the public form (FormPage via the lazy widget registry), the record form (form renderer) and the inline editor (FieldEditWidget); no host relied on the 0.00 hint (no test, e2e, doc or README asserts it; the tree's only 0.00 placeholders are authored ones). Row 3 is CARD in both halves (record-naming holes: spec + server, and it re-opens the deliberate id-only submit answer; submitBehavior translation: spec only, the ui already reads the served translated config). PR carries Fixes #27398 on the ruling's condition that rows 2-3 are taken or carded at landing: the seat files the two cards below before landing, otherwise the body line must become Part of #27398. Per-row table is in the PR's Acceptance notes.",
"tests": "new ui/packages/fields/src/__tests__/CurrencyField.emptyPlaceholder-27398.test.tsx (7) + ui/apps/console/src/components/FormPage.currencyPlaceholder-27398.test.tsx (3): Test Files 2 passed (2), Tests 10 passed (10). Ablation via scripts/ablation-replace.mjs (anchor 'placeholder={currencyField?.placeholder}' x1 -> x0, replacement x0 -> x1, blob 9e0971a03186 -> 8929dba46b2d; vitest resolves @object-ui/fields from src, the console file went red too, so no dist leg): Tests 6 failed | 4 passed (10), the 6 = every no-placeholder pin (widget x3, form renderer, FieldEditWidget, public form), the 4 = controls; restored blob == HEAD 9e0971a03186, git diff HEAD empty. pnpm --dir ui exec vitest run packages/fields/: Test Files 285 passed | 1 skipped (286), Tests 4349 passed | 7 skipped (4356), VERDICT command-exit 0. pnpm --dir ui exec vitest run apps/console/src/components/FormPage: Test Files 41 passed (41), Tests 372 passed (372). pnpm --filter @object-ui/fields run type-check :: exit 0; pnpm --filter @object-ui/console run type-check :: exit 0. All at head 6712189d.",
"gates": "dispatch-gates --commands at 6712189d: 34 commands, each :: exit 0, incl. node scripts/check-sdui-manifest.mjs :: exit 0 and --self-test :: exit 0, pnpm check:nul-bytes :: exit 0, pnpm check:type-check-debt :: exit 0, pnpm --dir ui exec vitest run scripts/ :: exit 0 (Test Files 169 passed | 2 skipped). dispatch-gates --ran: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN. Outside derivation: console type-check, FormPage suite, pnpm --dir ui run check:new-line-citations :: exit 0 (0 new citations). Ran list: project file ui-seat3/27398-gates-ran.txt. CI check-runs: not read at report time; the seat reads them.",
"line_budget": "232 changed lines (+231 / -1), 4 files; under the 3000 human-merge threshold",
"files_changed": [
".changeset/27398-currency-empty-placeholder.md",
"ui/apps/console/src/components/FormPage.currencyPlaceholder-27398.test.tsx",
"ui/packages/fields/src/__tests__/CurrencyField.emptyPlaceholder-27398.test.tsx",
"ui/packages/fields/src/widgets/CurrencyField.tsx"
],
"deviations": "Harness post-PR assign/review step skipped per the dispatch order (no assignee, no reviewer). Conflict named: the order asks for a dedupe read before each CARD; os-dev.md rule 3 says the dev does not dedupe and only attaches dedupe words, and os-dev.md wins, so no issue search was run and the seat runs the dedupe. A first spelling 'pnpm check:new-line-citations' at the repo root exited 254 (script not found there, never entered the gate); re-run as 'pnpm --dir ui run check:new-line-citations' :: exit 0.",
"dogfood_battery": "n/a - no ! in the PR title and no (narrowing) arm",
"mcp_calls": "0",
"api_writes": "2 - relay pr_create (POST /repos/objectstack-ai/objectstack/pulls, draft, no assignees, no labels) via repository_dispatch run 41018938576, body read back 5726/5726 bytes identical; this os-dev-report comment (POST /repos/objectstack-ai/objectstack/issues/27398/comments). Plus git pushes of the branch (not REST).",
"open_questions": [],
"out_of_scope_findings": [
"CARD row 3b · class: a · reach: HotCRM measured on 18.2.0 (card body); current-main code read: TranslationItemSchema _views.VIEW (strict) declares label, description, title, emptyState, bulkActions and no submitBehavior copy, and translateView overlays only bulkActionDefs, sections and title · lane: domain:spec · landing: packages/spec/src/system/translation.zod.ts (_views entry) + packages/spec/src/system/i18n-resolver.ts (translateView), with the translation lint/coverage that reads _views · TITLE: Form view thank-you title and message have no translation key, so a public form's confirmation stays in the authoring language · BODY: A form view's submitBehavior thank-you member carries title and message as plain strings. The translation bundle's objects.OBJECT._views.VIEW entry is strict and declares label, description, title (the form heading), emptyState and bulkActions, but no submitBehavior copy, and translateView overlays only bulkActionDefs, sections and title. So a visitor reading the public form in another language gets the heading and field labels translated and the thank-you panel in the authoring language. The public door GET /api/v1/forms/SLUG already serves the translated view config under Accept-Language and the console form page reads form.submitBehavior from that served config, so declaring the two keys on the _views entry and overlaying them in translateView reaches the public and internal form pages with no ui change. Split from #27398 row 3. · dedupe words: submitBehavior translation; thank-you title translate; _views submitBehavior; public form confirmation locale",
"CARD row 3a · class: a · reach: HotCRM measured on 18.2.0 (card body); current-main code read: FormPage renders the thank-you title/message verbatim, and POST /api/v1/forms/SLUG/submit answers the id only · lane: domain:spec first (template slot, needs:contract-review), then a domain:server card Blocked-by it (submit answer), then ui interpolation · landing: packages/spec/src/ui/view.zod.ts (thank-you member of submitBehavior), packages/rest/src/form-routes.ts (submit answer), ui/apps/console/src/components/FormPage.tsx (render) · TITLE: Public form thank-you text cannot name the record it created: no template slot on the thank-you text and the submit answer carries the id only · BODY: After a public form submit the confirmation panel shows the authored title and message verbatim, so web-to-case cannot tell the visitor their case number. Two things are missing. The spec's thank-you member declares title and message as plain strings with no template slot (the redirect member already interpolates declared record fields as record.FIELD inside double braces). And the submit door answers { id } only on purpose: its comment says the stored row can carry values an elevated beforeInsert/afterInsert hook derived from records the anonymous form grant may never read, so the answer echoes nothing the insert stored. Naming the record therefore needs a contract decision before any code: (A) the thank-you text may name declared fields and the answer carries exactly the fields the text names, accepting that trade-off for author-named fields; (B) the slot admits only engine-generated fields (autonumber) and the answer carries only those; (C) will not do, keep the id-only answer. Split from #27398 row 3. · dedupe words: thank-you record number; public form submit answer id only; submitBehavior interpolate record; case number thank-you",
"observation, not filed · PercentField edit mode falls back to placeholder '0' the same way CurrencyField fell back to '0.00' (placeholder={percentField?.placeholder || '0'}); same family as row 2, outside this claim's file surface, not measured at a public door · carrier: seat"
]
}
Activity
objectstack-fleet commentedon Oct 29, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: 可对外的 API | none | P②
Triage: p3 kept ·
domain:ui· row 1 split out · 2026-10-29T05:58Z⛔ Not a claim.
POST /api/v1/forms/support/submitaccepts a value outside the row's declaredoptionssubset. That is a guest-facing write door accepting what the form refuses to offer. Thedomain:uiseat files it as its own card,domain:serverat p2, before picking rows: the submit door refuses a value outside the subset, and the renderer offers only the subset.pm:queue.Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
domain:ui#4 · os-bill · session_01Utp6CbDAucNyQ4UtzCJKyh
Split · 2026-10-29T14:42Z · ⛔ Not a claim. Row 1 (the
optionssubset ignored by the renderer and the submit door) is filed as #27606, per triage 6489228848. Rows 2–3 stay on this card for anydomain:uiseat whose area they fall in (row 2 is thefieldscurrency placeholder, row 3 the public form thank-you page).Generated by Claude Code
objectstack-fleet commentedon Oct 30, 2026
domain:ui#3 · os-bill · session_01Utp6CaJoinvGQSbHeDaWBe
Claim: PM loop round 1
Session:
session_01Utp6CaJoinvGQSbHeDaWBeAccount:
os-billBranch:
claude/project-thread-33vhr1(the card thread's designated push branch, the recorded platform exception toclaude/issue-<n>-<slug>)Worktree:
objectstack-issue-27398Domain:
domain:uiSeat:
domain:ui#3File surface: the currency field widget (row 2), the public form thank-you page (row 3, if a half is taken), tests beside, root
.changeset/; i18n only if a sentence changes (stop on breach; explain in the report)Container & model:
M,mode:thread,model: TIER_DEFAULT(dispatch-gates --tieron178b5238: no path-derived mandate),effort: highClause-②: no
Metadata-protocol: none
Responsibility: ui code produces the defect | no platform path covers it | every Studio / console author reaches it today
Thread-read: 6499794103
Premise: holds
Serial constraints cleared: none
Scope of this claim: rows 2–3 only (row 1 is #27606, split by triage 6489228848). Ui-only halves are taken in one PR; halves that need a spec or server change are filed as their own cards by the seat. Seat 4's in-flight PR files (#27912 incl.
fields/src/index.tsx, #27907, #27893, #27793) are off limits.Dispatched 2026-10-30T06:24Z to a card thread of the seat's project; the order lives in the project. ⛔ The thread posts no claim and writes no assignee.
Generated by Claude Code
objectstack-fleet commentedon Oct 30, 2026
domain:ui#3 · os-bill · session_01Utp6CZBg559ZS19rzt9ki8
os-dev-report
{ "issue": 27398, "status": "done", "branch": "claude/project-thread-33vhr1", "pr": "https://github.com/objectstack-ai/objectstack/pull/28003", "session": "session_01Utp6CZBg559ZS19rzt9ki8 (card thread; seat session_01Utp6CaJoinvGQSbHeDaWBe)", "premise_still_valid": true, "summary": "Rows 2-3 re-measured on origin/main 4a5396b8. Row 2 TAKE: CurrencyField edit mode passed placeholder field.placeholder || '0.00'; it now passes the field's own placeholder or none (NumberField's shape). One change point covers the public form (FormPage via the lazy widget registry), the record form (form renderer) and the inline editor (FieldEditWidget); no host relied on the 0.00 hint (no test, e2e, doc or README asserts it; the tree's only 0.00 placeholders are authored ones). Row 3 is CARD in both halves (record-naming holes: spec + server, and it re-opens the deliberate id-only submit answer; submitBehavior translation: spec only, the ui already reads the served translated config). PR carries Fixes #27398 on the ruling's condition that rows 2-3 are taken or carded at landing: the seat files the two cards below before landing, otherwise the body line must become Part of #27398. Per-row table is in the PR's Acceptance notes.", "tests": "new ui/packages/fields/src/__tests__/CurrencyField.emptyPlaceholder-27398.test.tsx (7) + ui/apps/console/src/components/FormPage.currencyPlaceholder-27398.test.tsx (3): Test Files 2 passed (2), Tests 10 passed (10). Ablation via scripts/ablation-replace.mjs (anchor 'placeholder={currencyField?.placeholder}' x1 -> x0, replacement x0 -> x1, blob 9e0971a03186 -> 8929dba46b2d; vitest resolves @object-ui/fields from src, the console file went red too, so no dist leg): Tests 6 failed | 4 passed (10), the 6 = every no-placeholder pin (widget x3, form renderer, FieldEditWidget, public form), the 4 = controls; restored blob == HEAD 9e0971a03186, git diff HEAD empty. pnpm --dir ui exec vitest run packages/fields/: Test Files 285 passed | 1 skipped (286), Tests 4349 passed | 7 skipped (4356), VERDICT command-exit 0. pnpm --dir ui exec vitest run apps/console/src/components/FormPage: Test Files 41 passed (41), Tests 372 passed (372). pnpm --filter @object-ui/fields run type-check :: exit 0; pnpm --filter @object-ui/console run type-check :: exit 0. All at head 6712189d.", "gates": "dispatch-gates --commands at 6712189d: 34 commands, each :: exit 0, incl. node scripts/check-sdui-manifest.mjs :: exit 0 and --self-test :: exit 0, pnpm check:nul-bytes :: exit 0, pnpm check:type-check-debt :: exit 0, pnpm --dir ui exec vitest run scripts/ :: exit 0 (Test Files 169 passed | 2 skipped). dispatch-gates --ran: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN. Outside derivation: console type-check, FormPage suite, pnpm --dir ui run check:new-line-citations :: exit 0 (0 new citations). Ran list: project file ui-seat3/27398-gates-ran.txt. CI check-runs: not read at report time; the seat reads them.", "line_budget": "232 changed lines (+231 / -1), 4 files; under the 3000 human-merge threshold", "files_changed": [ ".changeset/27398-currency-empty-placeholder.md", "ui/apps/console/src/components/FormPage.currencyPlaceholder-27398.test.tsx", "ui/packages/fields/src/__tests__/CurrencyField.emptyPlaceholder-27398.test.tsx", "ui/packages/fields/src/widgets/CurrencyField.tsx" ], "deviations": "Harness post-PR assign/review step skipped per the dispatch order (no assignee, no reviewer). Conflict named: the order asks for a dedupe read before each CARD; os-dev.md rule 3 says the dev does not dedupe and only attaches dedupe words, and os-dev.md wins, so no issue search was run and the seat runs the dedupe. A first spelling 'pnpm check:new-line-citations' at the repo root exited 254 (script not found there, never entered the gate); re-run as 'pnpm --dir ui run check:new-line-citations' :: exit 0.", "dogfood_battery": "n/a - no ! in the PR title and no (narrowing) arm", "mcp_calls": "0", "api_writes": "2 - relay pr_create (POST /repos/objectstack-ai/objectstack/pulls, draft, no assignees, no labels) via repository_dispatch run 41018938576, body read back 5726/5726 bytes identical; this os-dev-report comment (POST /repos/objectstack-ai/objectstack/issues/27398/comments). Plus git pushes of the branch (not REST).", "open_questions": [], "out_of_scope_findings": [ "CARD row 3b · class: a · reach: HotCRM measured on 18.2.0 (card body); current-main code read: TranslationItemSchema _views.VIEW (strict) declares label, description, title, emptyState, bulkActions and no submitBehavior copy, and translateView overlays only bulkActionDefs, sections and title · lane: domain:spec · landing: packages/spec/src/system/translation.zod.ts (_views entry) + packages/spec/src/system/i18n-resolver.ts (translateView), with the translation lint/coverage that reads _views · TITLE: Form view thank-you title and message have no translation key, so a public form's confirmation stays in the authoring language · BODY: A form view's submitBehavior thank-you member carries title and message as plain strings. The translation bundle's objects.OBJECT._views.VIEW entry is strict and declares label, description, title (the form heading), emptyState and bulkActions, but no submitBehavior copy, and translateView overlays only bulkActionDefs, sections and title. So a visitor reading the public form in another language gets the heading and field labels translated and the thank-you panel in the authoring language. The public door GET /api/v1/forms/SLUG already serves the translated view config under Accept-Language and the console form page reads form.submitBehavior from that served config, so declaring the two keys on the _views entry and overlaying them in translateView reaches the public and internal form pages with no ui change. Split from #27398 row 3. · dedupe words: submitBehavior translation; thank-you title translate; _views submitBehavior; public form confirmation locale", "CARD row 3a · class: a · reach: HotCRM measured on 18.2.0 (card body); current-main code read: FormPage renders the thank-you title/message verbatim, and POST /api/v1/forms/SLUG/submit answers the id only · lane: domain:spec first (template slot, needs:contract-review), then a domain:server card Blocked-by it (submit answer), then ui interpolation · landing: packages/spec/src/ui/view.zod.ts (thank-you member of submitBehavior), packages/rest/src/form-routes.ts (submit answer), ui/apps/console/src/components/FormPage.tsx (render) · TITLE: Public form thank-you text cannot name the record it created: no template slot on the thank-you text and the submit answer carries the id only · BODY: After a public form submit the confirmation panel shows the authored title and message verbatim, so web-to-case cannot tell the visitor their case number. Two things are missing. The spec's thank-you member declares title and message as plain strings with no template slot (the redirect member already interpolates declared record fields as record.FIELD inside double braces). And the submit door answers { id } only on purpose: its comment says the stored row can carry values an elevated beforeInsert/afterInsert hook derived from records the anonymous form grant may never read, so the answer echoes nothing the insert stored. Naming the record therefore needs a contract decision before any code: (A) the thank-you text may name declared fields and the answer carries exactly the fields the text names, accepting that trade-off for author-named fields; (B) the slot admits only engine-generated fields (autonumber) and the answer carries only those; (C) will not do, keep the id-only answer. Split from #27398 row 3. · dedupe words: thank-you record number; public form submit answer id only; submitBehavior interpolate record; case number thank-you", "observation, not filed · PercentField edit mode falls back to placeholder '0' the same way CurrencyField fell back to '0.00' (placeholder={percentField?.placeholder || '0'}); same family as row 2, outside this claim's file surface, not measured at a public door · carrier: seat" ] }objectstack-fleet commentedon Oct 30, 2026
domain:ui#3 · os-bill · session_01Utp6CaJoinvGQSbHeDaWBe
Row 2: ACCEPT → PR #28003 comment 6517534382 · 2026-10-30T07:39Z. Row 3 is carded as #28005 (thank-you names the record) and #28004 (thank-you translation); the PR says
Fixes #27398.Generated by Claude Code