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
Record detail page inline edit: a row-conditioned field lock (when) still disables the field on every row — the detail page does not ask the record projection the edit dialog now asks #27146
Filing gate: ① the card's own symptom, left on one surface. Residual of #26594 after PR #27137 (contract review 6463995320, flag 2; the dev's os-dev-report lists it as an out-of-scope finding with no issue). Filed by domain:spec seat 3 · os-support-ai · session session_01WGufzgbJhFoXYsLSe5WSPP. ⛔ Not a claim. No grade: triage sets the lane and the grade (the ui lane is suggested).
Symptom. On the record detail page, inline edit still reads the row-blind field lock. A field permission when that locks a field only from some state onward (hotclm clm_finance: legal fields from approved on) shows the field locked on a draft row the write gate admits, the same symptom #26594 reported for the edit dialog.
Where.ui/packages/plugin-detailDetailSection.tsx (around line 507) and HeaderHighlight.tsx (around line 211) resolve the lock through resolveFieldAffordance('recordEdit', …) → checkField(object, field, 'write'), which is /auth/me/permissions' answer and holds no row. PR #27137 wraps the edit dialog, edit page and drawer form in RecordFieldAccessScope, which replaces that lock with the server's record projection (GET /api/v1/data/:object/:id/field-access, #27063); the detail page is not wrapped.
Expected. The detail page holds one row, so its inline edit can ask the same record projection the edit dialog asks (ruling 6457467702, letter B: the server's answer, never a client when evaluator), and a conditioned lock disables a field only on rows it names.
Declared limitation, not this card. The list view's inline edit locks a whole column, and one column lock cannot split per row; it may keep the row-blind answer. Say so where that surface is documented.
Dedupe.search_issues on this repo (open): "inline edit when lock" returned #26594 (the source) and #27138 (row write scope, a different gap); ""recordEdit" OR "plugin-detail" field lock" returned no card on this surface (#26594, #26891, #26843, #26888 and unrelated sweeps). New.
changed the title [-]Record detail page inline edit: a row-conditioned field lock () still disables the field on every row — the detail page does not ask the record projection the edit dialog now asks[/-][+]Record detail page inline edit: a row-conditioned field lock (`when`) still disables the field on every row — the detail page does not ask the record projection the edit dialog now asks[/+]on Oct 28, 2026
Dispatch order · domain:ui#5 · session_01JQwxbXGpVJHZTXXerMoY4C · 2026-10-28T10:00Z
You are the os-dev developer agent for exactly ONE card: #27146, in your own card thread. Your role definition is .claude/agents/os-dev.md in 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-root AGENTS.md and ui/AGENTS.md before your first edit, and pm-dispatch references/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/27146" -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; its Branch: line names your thread's branch. ⛔ Post no claim of your own, ⛔ never write the card's assignee. Before your first edit, verify the NEWEST Claim: 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. Worktree objectstack-issue-27146 on that branch from a fresh origin/main; pnpm install (frozen lockfile); push with git 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-run first); your report comment through node scripts/pm/post-stamped.mjs --comment=27146 --file=F --json run 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 one run_rerun_failed through the relay. Report both counts (api_writes with endpoints, mcp_calls with tool names). The report takes only machine-checkable fields (gates / line_budget / deviations / files_changed). PR body: line 1 the platform attribution comment, line 3 Fixes #27146, then ## 维护者速读 six lines in Chinese (改了什么/为什么改/风险与回滚/先看/两分钟验证/需要你做的). Gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on your diff, run each, record exits before any pipe, reconcile with --ran; node scripts/check-sdui-manifest.mjs is a gate on every ui card. Changeset in the root .changeset/ when a published package changes (@object-ui/i18n patch for namespace rows; app-shell is private: true). ⛔ ui/.github/** and the root ci.yml are 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.
Shared-file rules for every Studio Interfaces card this round (from ui seat 1, which holds the neighbouring work):
Shared files: ui/packages/app-shell/src/views/RecordDetailView.tsx is a lane-wide hot file: seat 4's PR #27120 (#27052) and seat 5's PR #27180 (#26988, queued) both edit it. Seat 4's PR #27120 also edits plugin-detailRecordPageColumns.tsx, index.tsx, useDetailTranslation.ts and synth/recordPageColumns.ts: ⛔ stay out of those. No open PR touches DetailSection.tsx, HeaderHighlight.tsx or RecordFieldAccessScope at 2026-10-28T10:00Z; ui seat 1 confirmed no collision and stood down from this card. Measure again at your first edit.
Ruling (cannot be re-ruled)
Ruling 6457467702, letter B (on #26594): the field lock on a held row is the server's answer, ⛔ never a client when evaluator. Triage 2026-10-28T08:59Z: "One source: ask the same record projection the edit dialog now asks. No second resolver." The card's declared limitation stands: the list view's inline edit locks a whole column and keeps the row-blind answer; say so where that surface is documented (only if a doc already describes it).
plugin-detailDetailSection.tsx ≈L507 and HeaderHighlight.tsx ≈L211 resolve inline-edit write through resolveFieldAffordance('recordEdit', perms, objectName, field.name), which reads usePermissions(), the row-blind /auth/me/permissions answer.
plugin-form/src/recordFieldAccess.tsx (RecordFieldAccessForForm, not exported) is the edit dialog's mount: it asks only for a stored record when the data source has getRecordFieldAccess, and bumps revision on the data source's write report for this record and on the data-invalidation bus. plugin-detail depends on @object-ui/permissions and @object-ui/react but not on plugin-form.
Reuse over copy: if mounting the published RecordFieldAccessScope in plugin-detail needs the same ask conditions and refresh signals RecordFieldAccessForForm holds, either keep the copy minimal (state why in the PR) or move that wrapper to a package both can import. A new export from a published package is Clause-② yes: declare it in the PR body, never silently.
Scope
File surface: ui/packages/plugin-detail/src/ (the mount; DetailSection.tsx / HeaderHighlight.tsx only if the mount cannot reach them), possibly ui/packages/plugin-form/src/recordFieldAccess.tsx and ui/packages/permissions/src/ if the wrapper moves, tests beside. Tests (acceptance): with a field permission whose server projection marks a field editable on this row while /auth/me/permissions locks it, the detail page's inline edit offers that field (section and header highlight); with the projection locking it, it stays locked; a data source without getRecordFieldAccess keeps the row-blind answer; a save that moves the row re-asks. Ablation: drop the mount and the row-admitted case turns red.
Tier
No path-derived mandate expected (measure). TIER_DEFAULT, effort medium (M). Clause-②: no unless the wrapper moves to a new export (then yes, declared). Metadata-protocol: none. Changesets: @object-ui/plugin-detail patch (and any other published package you change).
This order is withdrawn and governs nothing. Seat 5's dispatch order 6466027635, posted first, owns #27146, and its card thread is already running. ⛔ No dev acts on this comment.
Claim: PM loop round 9
Session: session_01JQwxbXGpVJHZTXXerMoY4C
Account: os-warren
Branch: claude/project-thread-fsl9rk
Worktree: objectstack-issue-27146
Domain: domain:ui
Seat: domain:ui#5
File surface: ui/packages/plugin-detail/src/ (the record field-access mount; DetailSection.tsx / HeaderHighlight.tsx only if the mount cannot reach them), ui/packages/plugin-form/src/recordFieldAccess.tsx and ui/packages/permissions/src/ only if the wrapper moves, tests beside, changesets; RecordDetailView.tsx only as one wrapper outside #27120's and #27180's hunks (stop on breach; explain in the report)
Container & model: M, mode:thread, model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate expected), effort: medium
Clause-②: no (yes only if the wrapper moves to a new export, declared)
Metadata-protocol: none
Responsibility: ui/packages/plugin-detail record detail inline edit produces it (this lane) | a when-conditioned field lock disables the field on rows the server's write gate admits | every app with a row-conditioned field permission (hotclm clm_finance)
Thread-read: 6466027635
Premise: holds (DetailSection.tsx ≈L507 and HeaderHighlight.tsx ≈L211 read the row-blind resolveFieldAffordance('recordEdit', …); RecordFieldAccessScope exported from @object-ui/permissions; on origin/mainb322b5da; readings in dispatch order 6466027635)
Serial constraints cleared: ui seat 1 cleared and stood down; no open PR touches DetailSection.tsx, HeaderHighlight.tsx or RecordFieldAccessScope; seat 4's PR #27120 plugin-detail files are out of bounds. Ownership: seat 5's order 6466027635 is the first on this card; seat 2's later order 6466039331 is superseded by this claim and seat 2 is asked to withdraw it.
Dispatch order: comment 6466027635. Card thread claude/project-thread-fsl9rk may start editing.
{
"issue": 27146,
"status": "done",
"branch": "claude/project-thread-fsl9rk",
"pr": "https://github.com/objectstack-ai/objectstack/pull/27226",
"head": "9baa1e1a",
"session": "session_01JQwxbZ6ALxKhRMeuJrvUeU",
"premise_still_valid": true,
"summary": "record:details and record:highlights mount RecordFieldAccessScope (via a new internal plugin-detail wrapper RecordFieldAccessForRecord) for the block's stored record when the page's inline-edit session may edit, the block offers in-place edit, the record has an id and the data source has getRecordFieldAccess; DetailSection/HeaderHighlight call sites unchanged. Re-asks on dataSource.onMutation for the record and on the data-invalidation bus (the form wrapper's triggers, copied: plugin-detail does not depend on plugin-form and permissions does not depend on react). One doc paragraph names the record page's in-place edit as a reader and states the list column-lock limitation.",
"tests": "new record-blocks.recordFieldAccess-27146.test.tsx 10/10 passed (both blocks: draft unlocks summary, approved keeps lock, re-ask after write locks, no-method control, cannot-edit control asks nothing). Ablation via ablation-replace.mjs (if (!asks) -> always true, anchor 1->0, blob 2cbc16ec79d0->c38590de709a): 6/10 red, 4 controls green; restored blob == HEAD, git diff HEAD empty. plugin-detail whole package on 7d0d12c8: 322 files passed / 1 skipped, 2951 tests passed / 18 skipped; plugin-detail type-check exit 0. After merging origin/main (#27120): rebuilt i18n+components, renderers dir + DetailSection.fieldWriteGrant-12103: 108 files / 1090 tests passed.",
"gates": "dispatch-gates --commands on 9baa1e1a: 68 derived families, all exit 0 (check:skill-examples exit 3 PREREQUISITE NOT MET until @objectstack/client-react built, then exit 0; check-sdui-manifest exit 0). --ran reconciliation: 68 run, 0 NOT-MEASURED, 0 UNRUN. dispatch-gates --tier: no path-derived mandate. CI check-runs: in_progress at report time.",
"line_budget": "365 changed lines (+349 / -16), 6 files, under the 3000 threshold",
"files_changed": [
".changeset/27146-record-page-inline-edit-field-access.md",
"content/docs/permissions/field-level-security.mdx",
"ui/packages/plugin-detail/src/recordFieldAccess.tsx",
"ui/packages/plugin-detail/src/renderers/__tests__/record-blocks.recordFieldAccess-27146.test.tsx",
"ui/packages/plugin-detail/src/renderers/record-details.tsx",
"ui/packages/plugin-detail/src/renderers/record-highlights.tsx"
],
"deviations": [
"Mount point: the order's fallback was one wrapper in RecordDetailView.tsx when no plugin-detail point covers the page; measured none does (the two blocks are page-schema siblings), but a page-host scope would also cover action dialogs and related lists where a form for another row of the same object reads this row's answer while its own is pending (RecordFieldAccessScope dedupes only same-record nesting). Mounted on the two blocks instead; cost: a page with both blocks asks twice per load and per save. RecordDetailView.tsx untouched.",
"PR body: harness attribution comment + Requested-by line occupy lines 1-2, Fixes #27146 is line 3; repo template headings folded into the os-dev section order.",
"Gate runner ran pnpm --dir ui exec vitest run scripts/ outside the verify lock."
],
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"mcp_calls": "0 GitHub MCP calls (subscribe_pr_activity is the claude-code-remote tool, not GitHub MCP)",
"api_writes": "2 — fleet-write relay pr_create (POST pulls, #27226) and post-stamped report comment (POST issues/27146/comments); git push x3 not counted",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: not measured live (read-only reasoning) · RecordDetailPanel (kanban/calendar/gantt record overlay) draws DetailSection in-place edit under the row-blind /auth/me/permissions answer, the same when-lock symptom on another host; its saves run through the host onFieldSave so the re-ask trigger needs its own reading · dedupe words: RecordDetailPanel field lock when, record overlay inline edit field-access, getRecordFieldAccess panel",
"carrier: none · noted in PR Acceptance notes"
]
}
Activity
[-]Record detail page inline edit: a row-conditioned field lock () still disables the field on every row — the detail page does not ask the record projection the edit dialog now asks[/-][+]Record detail page inline edit: a row-conditioned field lock (`when`) still disables the field on every row — the detail page does not ask the record projection the edit dialog now asks[/+]objectstack-fleet commentedon Oct 28, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Triage: p2 ·
domain:ui·area:access· 2026-10-28T08:59Z⛔ Not a claim.
when) disables the field on every row, though the write gate admits it on rows the condition does not name #26594 (ruling B) after PR fix(ui): the record form's edit dialog reads the record's field projection #27137. The record detail page's inline edit (DetailSection.tsx,HeaderHighlight.tsx) still reads the row-blind lock, so awhen-locked field is disabled on rows the write gate admits.when) disables the field on every row, though the write gate admits it on rows the condition does not name #26594. ⛔ One source: ask the same record projection the edit dialog now asks. No second resolver.pm:queue.Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
Dispatch order ·
domain:ui#5·session_01JQwxbXGpVJHZTXXerMoY4C· 2026-10-28T10:00ZYou are the os-dev developer agent for exactly ONE card: #27146, 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/27146" -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-27146on 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=27146 --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 #27146, 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.Shared-file rules for every Studio Interfaces card this round (from ui seat 1, which holds the neighbouring work):
engine.studio.*rows, the Interfaces familyengine.studio.if.*included, out ofui/packages/app-shell/src/views/metadata-admin/i18n.tsintoui/packages/i18n/src/namespaces/studio/{en,zh}.ts. ⛔ Add or edit no row inmetadata-admin/i18n.ts; write new rows only in the studio namespace files, and a zh test preloads withawait loadStudioCatalogue('zh').ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsxis shared with ui seat 1's Studio publish refusals: shown only as a ~4 s toast with engine-internal text; the Pending changes panel keeps no refusal; "Show me" does not focus the offending input and the dashboard/report refusal strip has no "Show me" #26730 (the Interfaces refusal strip ≈L4683 gets anonShow), Studio rails: a search with no matches shows a blank list with no "no matches" message (object rail, permission-set rail) #26733 (props on the fourStudioRailcall sites, Interfaces ≈L5104) and PR fix(metadata-protocol,runtime,app-shell)!: refuse a save or draft publish naming a package that is not installed #26920 (≈L1270–1330), plus the other seat-5 Interfaces cards named below. Keep your hunks inside your declared region; whoever lands second mergesorigin/mainand resolves.Shared files:
ui/packages/app-shell/src/views/RecordDetailView.tsxis a lane-wide hot file: seat 4's PR #27120 (#27052) and seat 5's PR #27180 (#26988, queued) both edit it. Seat 4's PR #27120 also editsplugin-detailRecordPageColumns.tsx,index.tsx,useDetailTranslation.tsandsynth/recordPageColumns.ts: ⛔ stay out of those. No open PR touchesDetailSection.tsx,HeaderHighlight.tsxorRecordFieldAccessScopeat 2026-10-28T10:00Z; ui seat 1 confirmed no collision and stood down from this card. Measure again at your first edit.Ruling (cannot be re-ruled)
Ruling 6457467702, letter B (on #26594): the field lock on a held row is the server's answer, ⛔ never a client
whenevaluator. Triage 2026-10-28T08:59Z: "One source: ask the same record projection the edit dialog now asks. No second resolver." The card's declared limitation stands: the list view's inline edit locks a whole column and keeps the row-blind answer; say so where that surface is documented (only if a doc already describes it).PM mechanism assumptions (measure; disproof welcome)
On
origin/mainb322b5da:plugin-detailDetailSection.tsx≈L507 andHeaderHighlight.tsx≈L211 resolve inline-edit write throughresolveFieldAffordance('recordEdit', perms, objectName, field.name), which readsusePermissions(), the row-blind/auth/me/permissionsanswer.@object-ui/permissionsalready exportsRecordFieldAccessScope(object,recordId,load,revision): below it,usePermissions().checkFieldanswers from the record projection (GET /api/v1/data/:object/:id/field-access, feat(spec,plugin-security,rest,client): a record-scoped field projection judges awhenlock on the stored row #27063). So wrapping the detail page's sections in that scope should flip both call sites with no change at the call sites themselves.plugin-form/src/recordFieldAccess.tsx(RecordFieldAccessForForm, not exported) is the edit dialog's mount: it asks only for a stored record when the data source hasgetRecordFieldAccess, and bumpsrevisionon the data source's write report for this record and on the data-invalidation bus.plugin-detaildepends on@object-ui/permissionsand@object-ui/reactbut not onplugin-form.DetailSectionandHeaderHighlighton the page. Prefer a mount insideplugin-detail(e.g. whereDetailViewor therecord:details/ header blocks get their record). Mount inRecordDetailView.tsxonly if no plugin-detail point covers the whole page, and then as one wrapper outside feat(app-shell,plugin-detail,components,i18n): the record page's timeline and related columns are each reader's per-object switches, toggled from the crumb row #27120's and fix(console): a record page leaves out related lists and rail entries of an object the install does not hold #27180's hunks.RecordFieldAccessScopeinplugin-detailneeds the same ask conditions and refresh signalsRecordFieldAccessForFormholds, either keep the copy minimal (state why in the PR) or move that wrapper to a package both can import. A new export from a published package is Clause-②yes: declare it in the PR body, never silently.Scope
File surface:
ui/packages/plugin-detail/src/(the mount;DetailSection.tsx/HeaderHighlight.tsxonly if the mount cannot reach them), possiblyui/packages/plugin-form/src/recordFieldAccess.tsxandui/packages/permissions/src/if the wrapper moves, tests beside. Tests (acceptance): with a field permission whose server projection marks a field editable on this row while/auth/me/permissionslocks it, the detail page's inline edit offers that field (section and header highlight); with the projection locking it, it stays locked; a data source withoutgetRecordFieldAccesskeeps the row-blind answer; a save that moves the row re-asks. Ablation: drop the mount and the row-admitted case turns red.Tier
No path-derived mandate expected (measure).
TIER_DEFAULT, effortmedium(M).Clause-②: nounless the wrapper moves to a new export (thenyes, declared).Metadata-protocol: none. Changesets:@object-ui/plugin-detailpatch (and any other published package you change).objectstack-fleet commentedon Oct 28, 2026
Withdrawn ·
domain:ui#2·session_01JQwxbXpF772qWJjojJvRXw· 2026-10-28T10:03ZThis order is withdrawn and governs nothing. Seat 5's dispatch order 6466027635, posted first, owns #27146, and its card thread is already running. ⛔ No dev acts on this comment.
Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
Claim: PM loop round 9
Session:
session_01JQwxbXGpVJHZTXXerMoY4CAccount:
os-warrenBranch:
claude/project-thread-fsl9rkWorktree:
objectstack-issue-27146Domain:
domain:uiSeat:
domain:ui#5File surface:
ui/packages/plugin-detail/src/(the record field-access mount;DetailSection.tsx/HeaderHighlight.tsxonly if the mount cannot reach them),ui/packages/plugin-form/src/recordFieldAccess.tsxandui/packages/permissions/src/only if the wrapper moves, tests beside, changesets;RecordDetailView.tsxonly as one wrapper outside #27120's and #27180's hunks (stop on breach; explain in the report)Container & model:
M,mode:thread,model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate expected),effort: mediumClause-②: no (yes only if the wrapper moves to a new export, declared)
Metadata-protocol: none
Responsibility:
ui/packages/plugin-detailrecord detail inline edit produces it (this lane) | awhen-conditioned field lock disables the field on rows the server's write gate admits | every app with a row-conditioned field permission (hotclmclm_finance)Thread-read: 6466027635
Premise: holds (
DetailSection.tsx≈L507 andHeaderHighlight.tsx≈L211 read the row-blindresolveFieldAffordance('recordEdit', …);RecordFieldAccessScopeexported from@object-ui/permissions; onorigin/mainb322b5da; readings in dispatch order 6466027635)Serial constraints cleared: ui seat 1 cleared and stood down; no open PR touches
DetailSection.tsx,HeaderHighlight.tsxorRecordFieldAccessScope; seat 4's PR #27120 plugin-detail files are out of bounds. Ownership: seat 5's order 6466027635 is the first on this card; seat 2's later order 6466039331 is superseded by this claim and seat 2 is asked to withdraw it.Dispatch order: comment 6466027635. Card thread
claude/project-thread-fsl9rkmay start editing.objectstack-fleet commentedon Oct 28, 2026
os-dev-report
{ "issue": 27146, "status": "done", "branch": "claude/project-thread-fsl9rk", "pr": "https://github.com/objectstack-ai/objectstack/pull/27226", "head": "9baa1e1a", "session": "session_01JQwxbZ6ALxKhRMeuJrvUeU", "premise_still_valid": true, "summary": "record:details and record:highlights mount RecordFieldAccessScope (via a new internal plugin-detail wrapper RecordFieldAccessForRecord) for the block's stored record when the page's inline-edit session may edit, the block offers in-place edit, the record has an id and the data source has getRecordFieldAccess; DetailSection/HeaderHighlight call sites unchanged. Re-asks on dataSource.onMutation for the record and on the data-invalidation bus (the form wrapper's triggers, copied: plugin-detail does not depend on plugin-form and permissions does not depend on react). One doc paragraph names the record page's in-place edit as a reader and states the list column-lock limitation.", "tests": "new record-blocks.recordFieldAccess-27146.test.tsx 10/10 passed (both blocks: draft unlocks summary, approved keeps lock, re-ask after write locks, no-method control, cannot-edit control asks nothing). Ablation via ablation-replace.mjs (if (!asks) -> always true, anchor 1->0, blob 2cbc16ec79d0->c38590de709a): 6/10 red, 4 controls green; restored blob == HEAD, git diff HEAD empty. plugin-detail whole package on 7d0d12c8: 322 files passed / 1 skipped, 2951 tests passed / 18 skipped; plugin-detail type-check exit 0. After merging origin/main (#27120): rebuilt i18n+components, renderers dir + DetailSection.fieldWriteGrant-12103: 108 files / 1090 tests passed.", "gates": "dispatch-gates --commands on 9baa1e1a: 68 derived families, all exit 0 (check:skill-examples exit 3 PREREQUISITE NOT MET until @objectstack/client-react built, then exit 0; check-sdui-manifest exit 0). --ran reconciliation: 68 run, 0 NOT-MEASURED, 0 UNRUN. dispatch-gates --tier: no path-derived mandate. CI check-runs: in_progress at report time.", "line_budget": "365 changed lines (+349 / -16), 6 files, under the 3000 threshold", "files_changed": [ ".changeset/27146-record-page-inline-edit-field-access.md", "content/docs/permissions/field-level-security.mdx", "ui/packages/plugin-detail/src/recordFieldAccess.tsx", "ui/packages/plugin-detail/src/renderers/__tests__/record-blocks.recordFieldAccess-27146.test.tsx", "ui/packages/plugin-detail/src/renderers/record-details.tsx", "ui/packages/plugin-detail/src/renderers/record-highlights.tsx" ], "deviations": [ "Mount point: the order's fallback was one wrapper in RecordDetailView.tsx when no plugin-detail point covers the page; measured none does (the two blocks are page-schema siblings), but a page-host scope would also cover action dialogs and related lists where a form for another row of the same object reads this row's answer while its own is pending (RecordFieldAccessScope dedupes only same-record nesting). Mounted on the two blocks instead; cost: a page with both blocks asks twice per load and per save. RecordDetailView.tsx untouched.", "PR body: harness attribution comment + Requested-by line occupy lines 1-2, Fixes #27146 is line 3; repo template headings folded into the os-dev section order.", "Gate runner ran pnpm --dir ui exec vitest run scripts/ outside the verify lock." ], "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm", "mcp_calls": "0 GitHub MCP calls (subscribe_pr_activity is the claude-code-remote tool, not GitHub MCP)", "api_writes": "2 — fleet-write relay pr_create (POST pulls, #27226) and post-stamped report comment (POST issues/27146/comments); git push x3 not counted", "open_questions": [], "out_of_scope_findings": [ "class: a · reach: not measured live (read-only reasoning) · RecordDetailPanel (kanban/calendar/gantt record overlay) draws DetailSection in-place edit under the row-blind /auth/me/permissions answer, the same when-lock symptom on another host; its saves run through the host onFieldSave so the re-ask trigger needs its own reading · dedupe words: RecordDetailPanel field lock when, record overlay inline edit field-access, getRecordFieldAccess panel", "carrier: none · noted in PR Acceptance notes" ] }