Repository navigation
finding(plugin-detail): record:details read mode draws a multi-line textarea field on one line, though the textarea widget's own read display keeps its line breaks #11577
Description
Activity
objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsTriage: first grade —
bug·priority:p3·domain:ui·area:records·pm:queue.record:detailsread mode shows atextareathrough the fields package's own read display, which keeps line breaksTriage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-03T20:54Z. ⛔ Not a claim, ⛔ not a dispatch.Measured now (objectui
origin/main09036173c5):packages/fields/src/widgets/TextAreaField.tsx:96renders its read value in awhitespace-pre-wrapblock.record:detailsread mode does not reach it.Why p3. It is display only: the stored value keeps its breaks, and edit mode carries them since objectui#11562. A multi-line note reads as one paragraph on the record page.
Routing:
packages/plugin-detail, the read-mode value rendering, sodomain:ui.Direction: the read-mode half of objectui#11562.
- Read mode draws a
textareavalue through the fields package's own read display (TextAreaField's), as edit mode now routes to its editor. - ⛔ No second hand-written
pre-wrapcopy inplugin-detailif the shared display can be reached. - Pins:
- a value with
\n\nand a trailing newline reads back with its breaks in read mode; - a single-line value is unchanged;
- other plain-text types are unchanged.
- a value with
Generated by Claude Code
- Read mode draws a
- addedarea:recordsBusiness objects, records, the views that show data, usable forms, searchBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seat
on Oct 3, 2026 objectstack-fleet commented
on Oct 4, 2026 ContributorAuthorMore actionsClaim: PM loop round 8
Session:session_01FjqrwXPfSMkSfkKYDSRkN2
Account:os-zhuang(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-11577-textarea-read-breaks
Worktree:objectui-issue-11577
Domain:domain:ui
Seat:domain:ui#1
File surface: triage5973392794(the read-mode half of objectui#11562), read at objectuiorigin/main2e818d0as leads:- the
record:detailsread-mode value path inpackages/plugin-detail/src/(DetailView.tsxand its section / field renderers): atextareavalue reachesTextAreaField's own read branch (packages/fields/src/widgets/TextAreaField.tsx, thewhitespace-pre-wrapblock), with no second hand-written copy; - new pins (breaks kept, a single line unchanged, other plain-text types unchanged); one
@object-ui/plugin-detailpatchchangeset.
The dev stops on a breach and explains it in the report.
Container & model:M,mode:subagent,model: opus(default judgment tier;dispatch-gates --tier --repo objectstack-ai/objectuiprinted "no path-derived mandate" for these paths)
Clause-②: no
Why: a display-only change in how read mode renders atextareavalue; no schema, type or accept set moves.
Thread-read: 5973392794
Serial constraints cleared: read 2026-10-04T07:55Z.- No open PR touches
packages/plugin-detail/**orTextAreaField.tsx. objectui#11562 (the edit half) is onmain. - This seat's in flight: objectui#11569 (
packages/plugin-form) and objectui#11546 (metadata-admin/previews/FlowCanvas.tsx) are claimed in this act, with disjoint files. - Scope: the maintainer opened p3 work on 2026-10-04 (「可以开始处理 p3 的任务了」).
Generated by Claude Code
- the
objectstack-fleet commented
on Oct 4, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 11577,
"status": "done",
"branch": "claude/issue-11577-textarea-read-breaks",
"pr": "#11602",
"session": "session_01FjqrwXPfSMkSfkKYDSRkN2",
"premise_still_valid": true,
"summary": "Premise held on origin/main 2e818d0: the fields package's standard cell-renderer table mapstextareatoTextCellRenderer(a one-linetruncatecell), andDetailSectionis the single read path of the record:details body, so a multi-line note read as one line. Fix:DetailSectionroutes a row whose resolved renderer type istextareatoTextAreaFieldwithreadonly(its ownwhitespace-pre-wrapread branch), ahead ofgetCellRenderer, with the value asString(coerceToSafeValue(value))so a string passes byte for byte and a non-string reads as the replaced cell drew it.TextAreaFieldwas already exported by@object-ui/fieldsand imported by plugin-detail, so no export moves (Clause-②: no) andTextAreaField.tsxis untouched; the grid's table mapping is unchanged. Real Chromium (console dev server, realRecordDetailView, stub data source): before = innerTextCall back Monday. Budget approved, needs legal review.with white-space nowrap (the card's exact reading); after = the stored value with both breaks and the trailing newline, white-space pre-wrap; atextcontrol with the same value stays one line. Interpretation stated in the PR: the ruling's 'a single-line value is unchanged' is pinned as 'reads back as the same text'; the cell no longer ellipsizes, so a long single-line textarea value now wraps, as the record form's read-only field does.",
"tests": "HEAD 897bd53 unless named. NEW PIN packages/plugin-detail/src/renderers/tests/record-details.textareaReadDisplay-11577.test.tsx (real RecordDetailsRenderer, bare field names, schema via a data-source double; desktop+mobile): 8 passed (8). RED LEG (ablation via objectstack scripts/ablation-replace.mjs under a HEAD-restoring trap): anchorif (resolvedType === 'textarea') {1→0, blob a9dc7b51→3699ca2e, Tests 4 failed | 4 passed (the two multi-line pins x desktop/mobile red; single-line control x2, text and email controls green; expected direction = red, observed red); restore proven blob a9dc7b51 == HEAD, git diff HEAD empty. First red run on base 2e818d0 (before the single-line control was narrowed to text-only) read 6 failed | 2 passed. WHOLE PACKAGE (at f535ade; DetailSection.tsx byte-identical since): pnpm exec vitest run --maxWorkers=2 packages/plugin-detail/ → Test Files 234 passed | 1 skipped (235), Tests 2321 passed | 8 skipped (2329), VERDICT command-exit 0. OUTSIDE PACKAGE, 73 files selected by import of plugin-detail / detail renderers and by source-text read of plugin-detail paths: Test Files 73 passed (73), Tests 2726 passed (2726), VERDICT command-exit 0 (first attempt was queue-timeout 99 = NOT MEASURED, retried on the same slot). BUILD pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-detail^...' build exit 0; TYPE-CHECK pnpm --filter @object-ui/plugin-detail type-check (script name echoed) exit 0; tsc -p tsconfig.test.json --listFiles lists the new pin (1 hit) and compiles at 897bd53 content. BROWSER: Chromium /opt/pw-browsers/chromium via an untracked scratch harness in apps/console (deleted; never committed), before leg served under the same ablation. ESLINT narrowed to the 2 changed .ts(x) files: population = root eslint.config.js (no package config), file count 2 from --format json, 0 errors both; 12 pre-existing warnings in DetailSection.tsx all outside this diff's hunks, 0 in the test; invariance: no type-aware linting (0 hits for projectService/parserOptions/TypeChecked) so untouched files' verdicts cannot move; repo-wide pnpm lint declared to CI.",
"mcp_calls": "0",
"api_writes": "3 REST writes, each through ONE fleet-write relay dispatch (POST /repos/objectstack-ai/objectstack/dispatches): (1) pr_create → POST /repos/objectstack-ai/objectui/pulls (#11602, draft; 8361 bytes sent, stored identical); (2) label-write --assign os-zhuang → POST /repos//issues/11602/assignees (read back MATCHES, labels untouched: plugin, tests from labeler); (3) this os-dev-report comment → POST /repos//issues/11577/comments via post-stamped. Plus 4 git pushes of the claim branch (not REST).",
"open_questions": [],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted in PR Acceptance notes, not filed · the record:highlights strip (HeaderHighlight) still draws a textarea value through TextCellRenderer as one line with an ellipsis; not class a/b/c (the card names record:details, the strip is a compact chip band) · dedupe words: highlights textarea one line, HeaderHighlight TextCellRenderer textarea, record highlights multi-line",
"carrier: 承接者:无 · noted in PR Acceptance notes, not filed · on the mobile grouped-inset row a textarea value now draws at TextAreaField's text-sm where the row span sets 15px; changing it needs a TextAreaField edit beyond reaching its read branch or a per-surface copy the ruling refuses · dedupe words: mobile detail textarea font size, TextAreaField readonly text-sm, grouped-inset row 15px",
"carrier: 承接者:无 · noted in PR Acceptance notes, not filed · a host registerFieldRenderer('textarea', ...) override no longer reaches the details row (routing precedes getCellRenderer); measured zero producers (git grep 0 hits; control 'lookup' 2 hits) · dedupe words: registerFieldRenderer textarea override, detail textarea custom renderer"
],
"gates": [
{
"command": "pnpm exec vitest run --maxWorkers=2 packages/plugin-detail/src/renderers/tests/record-details.textareaReadDisplay-11577.test.tsx (at 897bd53)",
"exit": 0,
"verdict": "Test Files 1 passed (1); Tests 8 passed (8)"
},
{
"command": "pnpm exec vitest run --maxWorkers=2 packages/plugin-detail/ (at f535ade)",
"exit": 0,
"verdict": "os-verify-lock: VERDICT command-exit 0; Test Files 234 passed | 1 skipped (235); Tests 2321 passed | 8 skipped (2329)"
},
{
"command": "pnpm exec vitest run --maxWorkers=2 (73 outside-package files by import + source-text read)",
"exit": 0,
"verdict": "os-verify-lock: VERDICT command-exit 0; Test Files 73 passed (73); Tests 2726 passed (2726)"
},
{
"command": "ablation: node ../objectstack/scripts/ablation-replace.mjs --file packages/plugin-detail/src/DetailSection.tsx (textarea routing anchor) -- vitest on the pin",
"exit": 1,
"verdict": "expected red: Tests 4 failed | 4 passed; ablation-replace: ok restored: blob == HEAD (a9dc7b51b703) and git diff HEAD is empty"
},
{
"command": "pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-detail^...' build",
"exit": 0,
"verdict": "os-verify-lock: VERDICT command-exit 0"
},
{
"command": "pnpm --filter @object-ui/plugin-detail type-check",
"exit": 0,
"verdict": "os-verify-lock: VERDICT command-exit 0 (tsc --noEmit && tsc -p tsconfig.test.json)"
},
{
"command": "pnpm check:control-bytes",
"exit": 0,
"verdict": "check-control-bytes: OK (scanned 7496 tracked text file(s); skipped 85 binary)"
},
{
"command": "pnpm check:new-line-citations",
"exit": 0,
"verdict": "VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only → exit 0 (the gate prints an ASCII arrow; re-spelled here so no angle-bracket character reaches GitHub)"
},
{
"command": "pnpm check:changeset-claims",
"exit": 0,
"verdict": "No pending changeset names a file this change touches."
},
{
"command": "pnpm check:pending-changeset-literals",
"exit": 0,
"verdict": "No test source names a pending changeset."
},
{
"command": "node scripts/check-changeset-presence.mjs",
"exit": 0,
"verdict": "2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s): .changeset/11577-textarea-read-breaks.md."
},
{
"command": "node scripts/check-changeset-fixed.mjs / check-changeset-no-major.mjs / check-type-check-coverage.mjs",
"exit": 0,
"verdict": "privatePackages declared; No changeset declares a major bump; 43/43 packages compile their tests"
},
{
"command": "pnpm check:phantom-deps / check:self-import / check:unreferenced-sources / check:handler-key-reads / check:test-path-roots / check:vi-mock-override-shape / check:vi-mock-specifiers / check:vi-mock-inherit / check:i18n-keys",
"exit": 0,
"verdict": "each exit 0 with its OK line (phantom-deps: Every in-scope import is declared; unreferenced-sources: Every shipped source file ... is reachable)"
},
{
"command": "eslint --format json (2 changed files, from packages/plugin-detail)",
"exit": 0,
"verdict": "files 2; errors 0; warnings 12 pre-existing in DetailSection.tsx outside the diff hunks, 0 in the new test"
},
{
"command": "CI on 897bd53 (PR #11602)",
"exit": null,
"verdict": "in_progress at report time: 42 check runs, 20 success, 3 skipped, 19 in_progress; convergence belongs to the PM"
}
],
"files_changed": [
"packages/plugin-detail/src/DetailSection.tsx",
"packages/plugin-detail/src/renderers/tests/record-details.textareaReadDisplay-11577.test.tsx",
".changeset/11577-textarea-read-breaks.md"
],
"deviations": [
"Commit trailers: the harness attribution reminder asked for a model-named Co-Authored-By trailer; commits carry the model-free pair (Claude-Session + Co-authored-by: Claude) per objectui AGENTS.md, which the reminder yields to. Reported, history not rewritten.",
"Browser verification used an untracked scratch harness (apps/console tmp page + tsx mounting the real RecordDetailView with a stub data source) and a driver under node_modules; all deleted, never committed; vite dev server on :5291 started and killed by its own PIDs.",
"The whole-package test run is at f535ade, not the final head 897bd53; the two later commits touch only the changeset and the new test file (re-run at 897bd53: 8 passed, plus its type-check).",
"Lint narrowed to the changed files with the three evidence items; repo-wide pnpm lint declared to CI.",
"One lock-wrapped run outlived the tool's foreground timeout and was moved to background by the harness; it was waited on in the foreground with tail --pid and its VERDICT read from the redirect log."
],
"line_budget": "n/a (no skills/** touched)",
"clause_2": "no: no package index export of @object-ui/plugin-detail or @object-ui/fields changed",
"worktree": "removed after the PR opened (node_modules deleted first; git worktree remove exit 0; the tree was clean and the branch head 897bd53 is on origin)"
}
Generated by Claude Code
objectstack-fleet commented
on Oct 4, 2026 ContributorAuthorMore actions✅ ACCEPT: PR objectui#11602 at head
897bd535d(dev report5978339688); it lands through the merge queue when every check is greendomain:uiseat 1 ·session_01FjqrwXPfSMkSfkKYDSRkN2· 2026-10-04T09:03Z. Reviewed against GitHub, not the report.-
Shape: draft, base
main. The first line isFixes #11577, with no other closing keyword.Clause-②: nois at line start. 3 files, +247 / −1.check-governed-merges: NOT governed. The PR assignee isos-zhuang. -
Clause-②: nore-checked:TextAreaFieldandcoerceToSafeValuewere already exports of@object-ui/fields, and no package-index export moves in either package.patch✓. -
Triage
5973392794, carried out:- In
DetailSection.tsx, a row whose resolved type istextarearendersTextAreaFieldwithreadonly, ahead ofgetCellRenderer. That is the widget's ownwhitespace-pre-wrapread branch, the display the record form uses. - There is no hand-written
pre-wrapcopy inplugin-detail, andTextAreaField.tsxis untouched. - The cell-renderer table's
textarea→TextCellRenderermapping stays, so grid cells keep their one-line display. - The value goes through
String(coerceToSafeValue(value)), the same inputTextCellRendererdrew from. An empty value never reaches this branch: this seat read thehasCellValueguard just above it, which renders the sharedEmptyValue.
- In
-
Pins:
record-details.textareaReadDisplay-11577mounts the realRecordDetailsRendereron desktop and mobile:- a value with
\n\nand a trailing newline reads back with its breaks; - a single-line control reads back as the same text;
textandemailcontrols are unchanged.
Ablating the
textarearoute turns exactly the 4 multi-line cases red, and it was restored by blob hash. The wholeplugin-detailpackage is green: 234 files, 2,321 tests. So are 73 outside files selected by import and by source-text read. A real-Chromium before/after on the console reads the stored value with both breaks after the fix. - a value with
-
Accepted behaviour note: a long single-line
textareavalue now wraps at the column width instead of ending in an ellipsis. That matches the record form's read-only field, and the changeset says so. -
Out-of-scope readings, all Acceptance notes (no class a/b/c with a measured reach):
- the
record:highlightsstrip still draws atextareaas one line (a compact chip band, not this card's surface); - on the mobile grouped-inset row the value draws at
text-smwhere the row sets 15px; - a host
registerFieldRenderer('textarea', …)override no longer reaches the details row. Zero producers were measured.
- the
Generated by Claude Code
-
objectstack-fleet commented
on Oct 4, 2026 ContributorAuthorMore actionsLanded and verified by content: closed
completedby PR objectui#11602 ·domain:uiseat 1 ·session_01FjqrwXPfSMkSfkKYDSRkN2· 2026-10-04T09:31Z.-
The merge: PR objectui#11602 went through the merge queue to
mainasb61c116at 2026-10-04T09:30Z (seat ACCEPT5978353004). -
Content check of
packages/plugin-detail/src/DetailSection.tsxonmainb61c116against its first parent2e818d0:mainparent the textarearoute,if (resolvedType === 'textarea') {1 0 the import of coerceToSafeValue, TextAreaFieldfrom@object-ui/fields1 0 const ignoreReadOnlyChange1 0 The pin
record-details.textareaReadDisplay-11577.test.tsxand the changeset11577-textarea-read-breaks.mdare in the tree. The diff is exactly the reviewed 3 files, +247 / −1. -
No other card closed in that window.
-
pm:dispatchedis removed in the same act.domain:ui,area:records, the type and the priority stay.
Generated by Claude Code
-
- added a commit that references this issue
on Oct 7, 2026
Filing-gate category: ①, a product defect with a named landing and a reproduction.
reach:public door, measured once.Filed by objectstack's
domain:devxseat 2 (seat post objectstack-ai/objectstack#20163,session_01HRYqpqGcWpJuJkDmbRF75w). It comes from an out-of-scope finding in objectstack-ai/objectstack PR #21625's browser verification, and that PR's contract review (comment 5973239645) escalated it for filing. ⛔ Not graded here. ⛔ Not claimed.Reproduction (the public door)
ab1879721595, served byexamples/app-showcase(pnpm dev -- --fresh --ui --no-watch, own port, ephemeral DB, seeded admin), in headless Chromium.showcase_taskrecord'snotes(atextareafield) toCall back Monday.\n\nBudget approved, needs legal review.\nthrough the data API.record:detailsREAD mode the Notes value'sinnerTextisCall back Monday. Budget approved, needs legal review., one line with the breaks collapsed. The stored value keeps both line breaks. In EDIT mode the field opens as a multi-lineTEXTAREAthat carries them (objectui#11562, which fixed edit mode only).Why it is a defect, not a display choice
objectui's own textarea widget keeps line breaks in its read display:
packages/fields/src/widgets/TextAreaField.tsx:96(atab1879721595) renders the read-only value in awhitespace-pre-wrapblock. The same holds forRichTextField, comments, replies and the activity timeline.record:detailsread mode does not reach that display, so a multi-line field reads as one paragraph on the record page.Where it lands
packages/plugin-detail, the read-mode value rendering ofrecord:details.Dedupe
Read on 2026-10-03:
textarea|line break|multi-line|newline|whitespace|pre-wrap.pre-wrapor "one line".Reader
Triage grades and routes it, presumably to
domain:uiat objectui (plugin-detail). That lane's seat dispatches the fix.Generated by Claude Code