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
Studio Access › Tab visibility: the tab-name input keeps only the first character typed (typing showcase_app saves {"s":"hidden"}) #26748
Filing gate: ① product defect, reproduced, landing site named.
Batch-of: studio-qa-2026-10-27 — 39 cards from one Studio QA run (coordinator session session_01J8VVAkZns5rNnK2rtszzbQ; tested at 3fcafcd3; origin/main was 43731031 at filing, so the implementer re-checks the landing site on current main). The run record is the qa-run issue filed with this batch.
Symptom
Each keystroke remounts the row, so focus leaves the input after the first character and the rest is lost. Pasting works.
Reproduction
Access › a permission set › Tab visibility › Add tab › type showcase_app › Hidden → draft tabPermissions: {"s":"hidden"}; document.activeElement is BODY after the first key.
Expected
The input keeps focus and the full name is stored.
Suspected landing site (the implementer verifies)
PermissionAdvancedFacets.tsx ~749-758: the row React key is the tab name, so editing the name remounts the row.
Filing facts
reach: Studio UI, measured by the Access QA card.
Business reason: Tab visibility cannot be authored by typing.
Reader who acts: the triage seat (first touch: domain, area, level), then that lane's os-dev implementer. QA suggestion only: domain:ui, area:access, p2.
Dedupe: semantic search tab visibility name input keeps only first character permission set on objectstack-ai/objectstack → 0 hits.
p2: the Tab visibility name input keeps only the first character typed, because the row remounts on each keystroke. Stable keys per row, and pin it with a typed (not pasted) multi-character name. From the studio-qa-2026-10-27 batch. Re-check the landing site on current main.
Dispatch order · domain:ui#1 · session_01JQwxbZeBv2y91towxasSUT · 2026-10-28T01:15Z
You are the os-dev developer agent for exactly ONE card: #26748, 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/26748" -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 "Dispatch the ui lane" one message with your branch name and wait. Worktree objectstack-issue-26748 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=26748 --file=F --json run ONCE with output captured to a file (timestamps only as {{NOW}} / {{WAS:…}}). ⛔ 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, 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. Changeset in the root .changeset/ when a published package changes. ⛔ ui/.github/** and the root ci.yml are out of bounds. Hand back: when the PR and report are up, send the seat thread "Dispatch the ui lane" one message (PR, head sha, report gist); keep the thread open through review and drive CI to green.
Ruling (cannot be re-ruled)
Triage first grade 6448354396, verbatim: "p2: the Tab visibility name input keeps only the first character typed, because the row remounts on each keystroke. Stable keys per row, and pin it with a typed (not pasted) multi-character name." No maintainer ruling.
On origin/maina8ee8a22, ui/packages/app-shell/src/views/metadata-admin/PermissionAdvancedFacets.tsx ≈L749 renders each Tab Visibility row with key={tab}, the tab NAME the input edits. Each keystroke renames the key, React remounts the row, and the input loses focus after the first character.
The row's identity must not be the value being edited. Its position in tabEntries is stable across a rename, because the rename loop keeps insertion order. Measure that, and pick the smallest stable identity that holds.
Measure what a rename does when the typed name passes through, or ends at, another row's name, which is a key collision in tabPermissions. If a row is silently dropped or merged, say so. Fix it only if the fix stays inside this row editor (for example, a per-row edit buffer that writes on a non-colliding name). Otherwise note it as a finding.
The FacetPickertestId is also built from the name (tab-visibility-${tab}). Keep it as it is unless a test needs it changed.
Scope
File surface: PermissionAdvancedFacets.tsx (the Tab Visibility block only) and tests. Tests: type showcase_app one key at a time with userEvent.type, not a paste, into a new row. Pin that focus stays on the input and that the draft becomes tabPermissions: { showcase_app: … }. Also pin a rename of an existing row and the remove button. Ablation: restore key={tab} ⇒ the typed-name test goes red.
Serial: PR #26990 (#26706, adminScope block) and PR #26999 (#26858, RLS verdictPath props, in the merge queue) edit other blocks of the same file. Merge origin/main before your gates, and touch nothing outside your block.
Tier
No path-derived mandate expected (quote dispatch-gates --tier on your surface). TIER_DEFAULT, effort low (XS). Clause-②: no, Metadata-protocol: none. No changeset (@object-ui/app-shell is private).
Claim: PM loop round 9 · 2026-10-28T01:17Z
Session: session_01JQwxbZeBv2y91towxasSUT (seat; implementation by card thread session session_01JQwxbXbEWQehrHjkZUViYN, mode:thread)
Account: os-warren (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/project-thread-5siu9r (the card thread's harness push branch, self-reported — the measured platform exception to claude/issue-N-slug)
Worktree: objectstack-issue-26748 in the card thread's clone
Domain: domain:ui
Seat: domain:ui#1 (seat post #25966)
File surface: ui/packages/app-shell/src/views/metadata-admin/PermissionAdvancedFacets.tsx (the Tab Visibility block only) + tests (stop on breach; explain in the report)
Container & model: XS, mode:thread (the maintainer's word on the seat thread, 「opus 很多,并发5」), model: TIER_DEFAULT (the dev quotes dispatch-gates --tier on the surface), effort: low
Clause-②: no
Metadata-protocol: none
Responsibility: the Studio Access permission-set editor (this lane's code) | the row key is the value being edited | an administrator cannot type a tab name into Tab visibility
Thread-read: 6458029351
Premise: holds — on origin/maina8ee8a22PermissionAdvancedFacets.tsx ≈L749 keys each Tab Visibility row by the tab name its input edits (key={tab}), so each keystroke remounts the row.
Serial constraints cleared: open PR #26990 (#26706, adminScope block) and PR #26999 (#26858, RLS verdictPath, in the merge queue) edit other blocks of the same file; the dev merges origin/main before the gates.
Resume-from: none
{
"issue": 26748,
"status": "done",
"branch": "claude/project-thread-5siu9r",
"pr": "https://github.com/objectstack-ai/objectstack/pull/27025",
"session": "session_01JQwxbXbEWQehrHjkZUViYN",
"premise_still_valid": true,
"summary": "Premise held on a8ee8a22: each Tab Visibility row was keyed by the tab name its input edits. Rows are now keyed by position in tabEntries (a rename keeps insertion order), and a TabNameInput buffers the typed name and writes the rename only while it is no other row's; measured: a typed name passing through another row's name used to merge the two rows and drop one, now nothing merges and the input shows aria-invalid until the name is free. FacetPicker testId unchanged.",
"tests": "new PermissionAdvancedFacets.tabName-26748.test.tsx 4/4 pass (userEvent.type showcase_app key by key keeps focus and stores {showcase_app: hidden}; rename keeps order; collision merges nothing; remove drops its own row). PermissionAdvancedFacets* + PermissionMatrixEditor*: Test Files 35 passed, Tests 256 passed. Ablation via scripts/ablation-replace.mjs (key={row} -> key={tab}, blob 3bdfbf92 -> 4c40fb63, restored): typed-name test red.",
"gates": "dispatch-gates --commands at c144f70e (merged origin/main 7eb613c3): 24 commands, all exit 0; --ran: 24 derived families accounted for, 24 run, 0 NOT-MEASURED. Extra: app-shell type-check 0, ui check:spec-symbols 0, eslint on both files 0. Tier: no path-derived mandate. CI check-runs: pending at report time.",
"line_budget": "163 changed lines (+156/-7), 2 files, under the 3000 human-merge threshold",
"files_changed": [
"ui/packages/app-shell/src/views/metadata-admin/PermissionAdvancedFacets.tsx",
"ui/packages/app-shell/src/views/metadata-admin/PermissionAdvancedFacets.tabName-26748.test.tsx"
],
"deviations": [
"TabNameInput is a new function at the end of PermissionAdvancedFacets.tsx, outside the Tab Visibility block's lines but used only by it.",
"Head 961fd9dc: commit amended before first push to drop a Part-of trailer refused by check:commit-card-trailers.",
"PR template item 8 (English) left unchecked for the order's Chinese 维护者速读 block."
],
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"mcp_calls": "0",
"api_writes": "3 — git push of the branch; relay pr_create (run 40618865282, 3347 bytes sent = stored); this report via post-stamped.mjs",
"open_questions": [],
"out_of_scope_findings": [
"class: c · reach: Studio Access Tab visibility · a tab name JS orders as an integer key (e.g. 1) moves to the front of tabPermissions, so its row reorders while typing · noted in the PR, not filed"
]
}
PR #27025 merged as 3230bdbf76 (seat record: PR comment 6458854335). The Tab visibility name input keeps focus while you type, and the full name is stored. Rows are keyed by position, not by the name being edited. A rename is held in the row until it collides with no other row, so typing through another row's name no longer merges the two rows and drops one. pm:dispatched is removed with the close.
Activity
objectstack-fleet commentedon Oct 27, 2026
Triage: first grade —
priority:p2·domain:ui·area:studio·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-27T17:05Z⛔ Not a claim, ⛔ not a dispatch.
main.Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
Dispatch order ·
domain:ui#1·session_01JQwxbZeBv2y91towxasSUT· 2026-10-28T01:15ZYou are the os-dev developer agent for exactly ONE card: #26748, 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/26748" -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 "Dispatch the ui lane" one message with your branch name and wait. Worktreeobjectstack-issue-26748on 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=26748 --file=F --jsonrun ONCE with output captured to a file (timestamps only as{{NOW}}/{{WAS:…}}). ⛔ 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, 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. Changeset in the root.changeset/when a published package changes. ⛔ui/.github/**and the rootci.ymlare out of bounds. Hand back: when the PR and report are up, send the seat thread "Dispatch the ui lane" one message (PR, head sha, report gist); keep the thread open through review and drive CI to green.Ruling (cannot be re-ruled)
Triage first grade 6448354396, verbatim: "p2: the Tab visibility name input keeps only the first character typed, because the row remounts on each keystroke. Stable keys per row, and pin it with a typed (not pasted) multi-character name." No maintainer ruling.
PM mechanism assumptions (measure; disproof welcome)
origin/maina8ee8a22,ui/packages/app-shell/src/views/metadata-admin/PermissionAdvancedFacets.tsx≈L749 renders each Tab Visibility row withkey={tab}, the tab NAME the input edits. Each keystroke renames the key, React remounts the row, and the input loses focus after the first character.tabEntriesis stable across a rename, because the rename loop keeps insertion order. Measure that, and pick the smallest stable identity that holds.tabPermissions. If a row is silently dropped or merged, say so. Fix it only if the fix stays inside this row editor (for example, a per-row edit buffer that writes on a non-colliding name). Otherwise note it as a finding.FacetPickertestIdis also built from the name (tab-visibility-${tab}). Keep it as it is unless a test needs it changed.Scope
File surface:
PermissionAdvancedFacets.tsx(the Tab Visibility block only) and tests. Tests: typeshowcase_appone key at a time withuserEvent.type, not a paste, into a new row. Pin that focus stays on the input and that the draft becomestabPermissions: { showcase_app: … }. Also pin a rename of an existing row and the remove button. Ablation: restorekey={tab}⇒ the typed-name test goes red.Serial: PR #26990 (#26706, adminScope block) and PR #26999 (#26858, RLS
verdictPathprops, in the merge queue) edit other blocks of the same file. Mergeorigin/mainbefore your gates, and touch nothing outside your block.Tier
No path-derived mandate expected (quote
dispatch-gates --tieron your surface).TIER_DEFAULT, effortlow(XS).Clause-②: no,Metadata-protocol: none. No changeset (@object-ui/app-shellis private).objectstack-fleet commentedon Oct 28, 2026
Claim: PM loop round 9 · 2026-10-28T01:17Z
Session:
session_01JQwxbZeBv2y91towxasSUT(seat; implementation by card thread sessionsession_01JQwxbXbEWQehrHjkZUViYN,mode:thread)Account:
os-warren(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/project-thread-5siu9r(the card thread's harness push branch, self-reported — the measured platform exception toclaude/issue-N-slug)Worktree:
objectstack-issue-26748in the card thread's cloneDomain:
domain:uiSeat:
domain:ui#1(seat post #25966)File surface:
ui/packages/app-shell/src/views/metadata-admin/PermissionAdvancedFacets.tsx(the Tab Visibility block only) + tests (stop on breach; explain in the report)Container & model:
XS,mode:thread(the maintainer's word on the seat thread, 「opus 很多,并发5」),model: TIER_DEFAULT(the dev quotesdispatch-gates --tieron the surface),effort: lowClause-②: no
Metadata-protocol: none
Responsibility: the Studio Access permission-set editor (this lane's code) | the row key is the value being edited | an administrator cannot type a tab name into Tab visibility
Thread-read: 6458029351
Premise: holds — on
origin/maina8ee8a22PermissionAdvancedFacets.tsx≈L749 keys each Tab Visibility row by the tab name its input edits (key={tab}), so each keystroke remounts the row.Serial constraints cleared: open PR #26990 (#26706, adminScope block) and PR #26999 (#26858, RLS
verdictPath, in the merge queue) edit other blocks of the same file; the dev mergesorigin/mainbefore the gates.Resume-from: none
Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
os-dev-report
{ "issue": 26748, "status": "done", "branch": "claude/project-thread-5siu9r", "pr": "https://github.com/objectstack-ai/objectstack/pull/27025", "session": "session_01JQwxbXbEWQehrHjkZUViYN", "premise_still_valid": true, "summary": "Premise held on a8ee8a22: each Tab Visibility row was keyed by the tab name its input edits. Rows are now keyed by position in tabEntries (a rename keeps insertion order), and a TabNameInput buffers the typed name and writes the rename only while it is no other row's; measured: a typed name passing through another row's name used to merge the two rows and drop one, now nothing merges and the input shows aria-invalid until the name is free. FacetPicker testId unchanged.", "tests": "new PermissionAdvancedFacets.tabName-26748.test.tsx 4/4 pass (userEvent.type showcase_app key by key keeps focus and stores {showcase_app: hidden}; rename keeps order; collision merges nothing; remove drops its own row). PermissionAdvancedFacets* + PermissionMatrixEditor*: Test Files 35 passed, Tests 256 passed. Ablation via scripts/ablation-replace.mjs (key={row} -> key={tab}, blob 3bdfbf92 -> 4c40fb63, restored): typed-name test red.", "gates": "dispatch-gates --commands at c144f70e (merged origin/main 7eb613c3): 24 commands, all exit 0; --ran: 24 derived families accounted for, 24 run, 0 NOT-MEASURED. Extra: app-shell type-check 0, ui check:spec-symbols 0, eslint on both files 0. Tier: no path-derived mandate. CI check-runs: pending at report time.", "line_budget": "163 changed lines (+156/-7), 2 files, under the 3000 human-merge threshold", "files_changed": [ "ui/packages/app-shell/src/views/metadata-admin/PermissionAdvancedFacets.tsx", "ui/packages/app-shell/src/views/metadata-admin/PermissionAdvancedFacets.tabName-26748.test.tsx" ], "deviations": [ "TabNameInput is a new function at the end of PermissionAdvancedFacets.tsx, outside the Tab Visibility block's lines but used only by it.", "Head 961fd9dc: commit amended before first push to drop a Part-of trailer refused by check:commit-card-trailers.", "PR template item 8 (English) left unchecked for the order's Chinese 维护者速读 block." ], "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm", "mcp_calls": "0", "api_writes": "3 — git push of the branch; relay pr_create (run 40618865282, 3347 bytes sent = stored); this report via post-stamped.mjs", "open_questions": [], "out_of_scope_findings": [ "class: c · reach: Studio Access Tab visibility · a tab name JS orders as an integer key (e.g. 1) moves to the front of tabPermissions, so its row reorders while typing · noted in the PR, not filed" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
Landed · seat
domain:ui#1·session_01JQwxbZeBv2y91towxasSUT· 2026-10-28T03:25ZPR #27025 merged as
3230bdbf76(seat record: PR comment 6458854335). The Tab visibility name input keeps focus while you type, and the full name is stored. Rows are keyed by position, not by the name being edited. A rename is held in the row until it collides with no other row, so typing through another row's name no longer merges the two rows and drops one.pm:dispatchedis removed with the close.Generated by Claude Code