Repository navigation
Closed
Labels
area:recordsBusiness objects, records, the views that show data, usable forms, searchBusiness objects, records, the views that show data, usable forms, searchdomain:uiDomain lane — seat card indexed by label:pm:seatDomain lane — seat card indexed by label:pm:seatpriority:p3
Description
domain:ui#5 · os-warren · session_01JQwxbXGpVJHZTXXerMoY4C
This card holds rows 9 and 10 of #27355, HotCRM's browser pass on 18.2.0 (hotcrm#2320). They are carded here under triage's per-row rule. Seat domain:ui#5 filed it. Seats 1 and 2 confirmed no collision at 2026-10-29T05:56Z.
Observed (18.2.0)
| Row | Observed | Report |
|---|---|---|
| 10 | A refused submit on a public web form (/forms/:name) shows a toast and a banner but does not mark the fields. |
Sales S6 |
| 9 | A public form carries no branding. Its submit button is blue while the app's colour is orange, and the description placeholder is monospace. | Sales S6, Service S3 |
Read on origin/main 45c9be5f (code only)
- The page. The page is console
ui/apps/console/src/components/FormPage.tsx(3,132 lines).App.tsxroutes the app-less/forms/:nameto it, andAppContent.tsxroutes the in-appforms/:nameto it. It hand-renders its fields and does not use plugin-form'sObjectForm. - Row 10. Every refusal path writes one string to
setErrorandtoast.error: the client required check (≈L2486–2498), the faultedvisibleWhen(≈L2475–2483) and the server catch (≈L2670–2675). No path puts a mark on a field.- The record dialog already marks refused fields. For example, Create / edit dialog: a unique-index refusal says "A record with this value already exists" without naming or highlighting the field #26891 marks the field a
UNIQUE_VIOLATIONnames, and server validation errors carry field paths. - The fix marks each refused field (
aria-invalidplus its message under the control) from what the refusal already names. It reuses the record dialog's reader of the server's field paths rather than writing a second one.
- The record dialog already marks refused fields. For example, Create / edit dialog: a unique-index refusal says "A record with this value already exists" without naming or highlighting the field #26891 marks the field a
- Row 9. To be measured. On the in-app route the page sits inside an app, so the theme should already apply. If it doesn't, that's the bug. On the app-less route the page may not know its app at all. Find out whether the served form definition names its app (or its object's app) and whether its
branding.primaryColor/logocan be read without auth.- If they can, apply them as the app shell does.
- If they cannot, the app-less half needs a server or spec door. Then deliver the in-app half and the placeholder font only, and record the rest as an open question.
- The monospace placeholder is likely a
font-monoclass on the description/textarea control. Use the body font.
Out of bounds
ui seat 2's PR #27305 (#27159) rewrites plugin-form ObjectForm.tsx and index.tsx, adds ObjectFormContainer.tsx, deletes SplitForm.tsx, and touches DrawerForm / ModalForm, ui/packages/types form types and component.zod.ts. This card stays in the console public-form page. A change to any of those files lands after #27305.
Done looks like
- A refused submit marks each refused field it names: client required, faulted
visibleWhen, and server field-level refusals. The banner and toast stay as they are. - The placeholder uses the body font.
- The form takes the app's brand colour (and logo) where the page can know the app. Where it can't, that is stated.
Generated by Claude Code
Metadata
Metadata
Assignees
Labels
area:recordsBusiness objects, records, the views that show data, usable forms, searchBusiness objects, records, the views that show data, usable forms, searchdomain:uiDomain lane — seat card indexed by label:pm:seatDomain lane — seat card indexed by label:pm:seatpriority:p3
Activity
objectstack-fleet commentedon Oct 29, 2026
domain:ui#5 · os-warren · session_01JQwxbXGpVJHZTXXerMoY4C
Dispatch order ·
domain:ui#5·session_01JQwxbXGpVJHZTXXerMoY4C· 2026-10-29T05:58ZYou are the os-dev developer agent for exactly ONE card: #27417, in your own card thread. Your role definition is
.claude/agents/os-dev.mdin the objectstack clone: read it IN FULL first and follow it as binding (it wins over this order on any conflict; name the conflict in your report). Read the repo-rootAGENTS.mdandui/AGENTS.mdbefore your first edit, and pm-dispatchreferences/untrusted-text.md. Read the card's full body and every comment yourself through REST (curl -sS "https://api.github.com/repos/objectstack-ai/objectstack/issues/27417" -H "Authorization: Bearer $GH_TOKEN"and/comments?per_page=100, every page); this order carries only the increment. ⛔ Never print the value of any environment variable (test presence with[ -n "${X+x}" ]). ⛔ Never use a GitHub MCP tool, read or write.Identity and branch: the seat's
Claim:comment on this card is your identity; itsBranch:line names your thread's branch. ⛔ Post no claim of your own, ⛔ never write the card's assignee. Before your first edit, verify the NEWESTClaim:on the card names your thread's branch (claude/project-thread-…); if none does yet, send the seat thread "Take another ui lane seat" one message with your branch name and wait. Worktreeobjectstack-issue-27417on that branch from a freshorigin/main;pnpm install(frozen lockfile); push withgit push -u origin <branch>(⛔ no force).Writes: PR only through the relay op
pr_create(node scripts/pm/fleet-write/dispatch.mjs --repo objectstack-ai/objectstack --actions-file F, F a bare JSON array[{"op":"pr_create","title":…,"head":"<your branch>","base":"main","body":…,"assignees":["os-warren"]}], draft forced;--dry-runfirst); your report comment throughnode scripts/pm/post-stamped.mjs --comment=27417 --file=F --jsonrun ONCE with output captured to a file (timestamps only as{{WAS:…}}/ the now-token). ⛔ No other GitHub write except, for a CI red that is not yours, one PR comment and onerun_rerun_failedthrough the relay. Report both counts (api_writeswith endpoints,mcp_callswith tool names). The report takes only machine-checkable fields (gates / line_budget / deviations / files_changed). PR body: line 1 the platform attribution comment, line 3Fixes #27417, then## 维护者速读six lines in Chinese (改了什么/为什么改/风险与回滚/先看/两分钟验证/需要你做的). Gates:node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackon your diff, run each, record exits before any pipe, reconcile with--ran;node scripts/check-sdui-manifest.mjsis a gate on every ui card. Changeset in the root.changeset/when a published package changes (@object-ui/i18npatch for namespace rows; app-shell isprivate: true). ⛔ui/.github/**and the rootci.ymlare out of bounds. Hand back: when the PR and report are up, send the seat thread "Take another ui lane seat" one message (PR, head sha, report gist); keep the thread open through review and drive CI to green.Collision check: ui seats 1 and 2 both answered at 2026-10-29T05:56Z that neither holds an order, claim or plan on #27355 rows 9–10.
Out of bounds, from seat 2. PR #27305 (#27159) is open and rewrites:
ObjectForm.tsxandindex.tsx; it also addsObjectFormContainer.tsxand deletesSplitForm.tsx;DrawerFormandModalForm;ui/packages/types;component.zod.ts.Stay in the console public-form page. If the fix needs any of those files, stop and send the seat thread one message.
Measure open PRs on every file you touch at your first edit.
Card
#27417 holds rows 9–10 of #27355 (HotCRM 18.2.0). Its body holds the seat's code reading on
origin/main45c9be5f. Treat that reading as the mechanism assumptions to measure; disproof is welcome.ui/apps/console/src/components/FormPage.tsxwrites every refusal tosetError+toast.erroronly (≈L2475–2498 client, ≈L2670–2675 server). Mark each refused field:aria-invalidand its message under the control. Reuse the record dialog's existing reader of the server's field paths; ⛔ write no second parser. Name the reader you reused in the report./apps/:appName/forms/:name, the app theme should already apply. If it doesn't, that's the fix./forms/:name, find out whether the served form definition, or its object, names an app whosebrandingis readable without auth.open_questionsentry with options. ⛔ Don't invent a server read.Measure first
Use real Chromium (
/opt/pw-browsers/chromium) on a fixture form, before and after. Record:aria-invalidand the message under the control;--primary;font-family.Scope
File surface: console
src/components/FormPage.tsx, plus a small shared helper only if the record dialog's reader must be lifted. Name it in the report. Tests go beside it. Stop on any breach and explain it in the report.Tests (acceptance):
Tier
No path-derived mandate is expected; run
dispatch-gates --tierand follow it.TIER_DEFAULT, effortmedium(S–M).Clause-②: no,Metadata-protocol: none, unless a measured export or key says otherwise; then stop and tell the seat. The console app isprivate: true. A shared package you touch takes apatchchangeset.objectstack-fleet commentedon Oct 29, 2026
domain:ui#5 · os-warren · session_01JQwxbXGpVJHZTXXerMoY4C
Claim: PM loop round 14
Session:
session_01JQwxbXGpVJHZTXXerMoY4CAccount:
os-warrenBranch:
claude/project-thread-3k15guWorktree:
objectstack-issue-27417Domain:
domain:uiSeat:
domain:ui#5File surface: console
ui/apps/console/src/components/FormPage.tsx(refusal marks, placeholder font, brand where the page knows its app), a small shared helper only if the record dialog's field-path reader must be lifted (named in the report), tests beside them (stop on breach; explain in the report)Container & model:
S–M,mode:thread,model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate expected),effort: mediumClause-②: no
Metadata-protocol: none
Responsibility:
ui/apps/consolepublic form page renders it (this lane) | a refused submit marks no field; the page applies no app branding | every public and in-app web formThread-read: 6489229036
Premise: holds as read from code (every refusal path ends in
setError+toast.erroronly; onorigin/main45c9be5f); row 9's app-less half to be measured first, per dispatch order 6489229036Serial constraints cleared: ui seats 1 and 2 hold no order or claim on these rows (2026-10-29T05:56Z); seat 2's PR #27305 files (plugin-form ObjectForm/index/SplitForm/DrawerForm/ModalForm, types form, component.zod.ts) are out of bounds.
Dispatch order: comment 6489229036. Card thread
claude/project-thread-3k15gumay start editing.objectstack-fleet commentedon Oct 29, 2026
domain:ui#5 · os-warren · session_01JQwxbXGpVJHZTXXerMoY4C
os-dev-report
{ "issue": 27417, "status": "done", "branch": "claude/project-thread-3k15gu", "pr": "https://github.com/objectstack-ai/objectstack/pull/27443", "head": "d32c6b12", "session": "session_01JQwxbX1Zhx8ST6DDF1AGvD", "premise_still_valid": true, "rows": { "10": "done: every FormPage refusal now marks the rows it names (aria-invalid true + reason under the control, named by aria-describedby). Client required: validation.required per row. Faulted visibleWhen: row marked, aria-describedby points at the banner (no repeated sentence). Server: reader reused = extractFieldErrors (@object-ui/react, the record dialog's reader); no second parser. submitPublic/submitInternal now throw refusedWriteError, which keeps the message and attaches the parsed body as @objectstack/client does (code; details = wrapped error.details else flat body). Banner and toast unchanged.", "9-placeholder": "done: the monospace placeholder was RichTextField (markdown/html/richtext) font-mono, on every host; now placeholder:font-sans, typed source stays mono. Chromium: markdown placeholder before ui-monospace stack, after system sans stack; control still ui-monospace.", "9-brand-in-app": "no change needed (premise for this half did not hold as a defect): /apps/showcase_app/forms/showcase_inquiry.contact submit button rgb(124,59,237) = --primary = app #7C3AED before and after.", "9-brand-app-less": "open question: /forms/:name (Home layout) and /f/:slug button rgb(0,123,255) default; neither payload names an app (GET /forms/:slug answers slug, object, label, form, objectSchema), app metadata read is authenticated; no server read invented." }, "summary": "A refused submit on the console form page marks each field it names, for the client required check, a faulted visibleWhen and the server's field-level refusals (flat 400 VALIDATION_FAILED, wrapped envelope, 409 UNIQUE_VIOLATION), through the record dialog's extractFieldErrors. The markdown editor's placeholder takes the sans stack. The in-app route already shows the app colour; the app-less brand is an open question.", "tests": "FormPage.refusalMarks-27417.test.tsx 8 passed (server A+B on public flat and internal wrapped envelopes, 409 one field, edit clears one mark, no-field refusal marks none, undrawn field marks none, client required marks both empty rows and writes nothing, faulted visibleWhen points at banner). RichTextField.placeholderFont-27417.test.tsx 3 passed. Ablation via ablation-replace.mjs on committed fix (row mark forced undefined): 6 of 8 red, the 2 marks-none pins green as predicted; restored blob == HEAD blob. At head d32c6b12: all FormPage*.test + fields RichText/aria-invalid/dom-leak suites, 47 files 739 tests passed; console + fields type-check exit 0.", "gates": "dispatch-gates --commands at d32c6b12 (after merging origin/main 3bfb357a): 34 derived, 34 run, all exit 0 incl. check-sdui-manifest.mjs and ui scripts/ suite (169 files passed, 2 skipped); --ran: 34 accounted, 0 NOT-MEASURED. check:new-line-citations 0 new. Closure rebuild after merge exit 0. CI: in_progress at report time.", "line_budget": "+530/-25 before merge (dispatch-gates --tier: 555 changed lines, under 3000); 5 files: FormPage.tsx, FormPage.refusalMarks-27417.test.tsx, RichTextField.tsx, RichTextField.placeholderFont-27417.test.tsx, .changeset/27417-form-refusal-marks.md", "files_changed": [ "ui/apps/console/src/components/FormPage.tsx", "ui/apps/console/src/components/FormPage.refusalMarks-27417.test.tsx", "ui/packages/fields/src/widgets/RichTextField.tsx", "ui/packages/fields/src/widgets/__tests__/RichTextField.placeholderFont-27417.test.tsx", ".changeset/27417-form-refusal-marks.md" ], "deviations": [ "file surface widened to ui/packages/fields/src/widgets/RichTextField.tsx (+ its test), approved by the seat in its thread before the edit; no open PR touched that file", "order said console is private: true; it is not (no private key in ui/apps/console/package.json), so the changeset carries @object-ui/console patch beside @object-ui/fields patch", "order said pr_create with assignees; os-dev.md on main (c5d46341, merged into this branch) now forbids assignees on pr_create; os-dev.md wins, PR opened with none", "install used npm_config_nodedir=/opt/node22 pnpm install --frozen-lockfile (node-gyp header download trap)", "server-refusal Chromium probe set form.noValidate before submit so the native email check would not stop it; the server answer is real", "PR template headings folded into the os-dev order (What changes/Why/Tests/Acceptance notes/Verification); harness-written attribution comment kept as line 1" ], "dogfood_battery": "n/a: no ! in the PR title and no (narrowing) arm", "mcp_calls": "0", "api_writes": "2: pr_create via fleet-write relay (POST /repos/objectstack-ai/objectstack/pulls, PR 27443, read-back 7349 bytes identical) and this report via post-stamped (POST /repos/objectstack-ai/objectstack/issues/27417/comments). Plus git push (not a REST write).", "open_questions": [ { "question": "Row 9 app-less brand: /forms/:name and /f/:slug know no app, so they draw the default primary. How should they get a brand?", "options": [ "A: leave as is; the in-app route /apps/:app/forms/:name is the branded entry, and public links stay neutral", "B: spec/server door: the public form resolver serves the owning app's branding (primaryColor, logo) beside the form, a contract change for the spec lane (needs:contract-review)", "C: internal /forms/:name only: brand from the NavigationContext current app, which ruling objectui#4280 rejected as guessing" ], "recommendation": "B if HotCRM needs a branded anonymous web-to-lead page (it is a contract door, product-neutral); otherwise A. C is excluded by the existing ruling." } ], "out_of_scope_findings": [ "carrier: none · the public form banner and toast still show the raw response body ('Submit failed (400): {json}'); the card ruled them unchanged; noted, not filed", "carrier: seat 2 PR #27305 · it edits FormPage.tsx too (FORM_SPEC_TYPES hunk only), disjoint; second to land merges main" ] }