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 }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
A read-only view's … menu in Manage views opens EMPTY (a 180×10px strip), and its tab menu opens on a leading separator #10209
On an object whose every view is code-defined (src/views/*.view.ts — no sys_view overlay row), so ObjectView stamps readonly: true on the tab:
Manage views → the row's … button opens a menu with nothing in it. Measured in a real browser: role="menu" bounding box {width: 180, height: 10}, innerHTML === ''. Rendered, that is a thin strip of bg-popover straddling the row's bottom border — it reads to a user as a menu that is clipped or occluded and unclickable, not as an empty one. Nothing is occluding it; there is nothing in it.
The tab's ▾ menu opens on a leading role="separator" above its single Manage all views… entry.
Cause
Both menus ask a different question in the guard than in the entries beneath it.
ManageViewsDialog's … trigger renders on (onDelete || onDuplicate || onConfigView || onSetDefault) — whether a callback is wired. Every entry under it is also gated on !isReadonly. The one entry isReadonly does not suppress is Duplicate, and the console deliberately wires no onDuplicate (ADR-0034 (v2/未来): 仅视图的 per-user 个性化(user 覆盖 scope) #1520). So on a read-only row the guard passes and every entry drops out.
ViewTabBar's separators state their own condition (onDeleteView && !isReadonly opens a fragment that also holds the separator; the manage entry opened one unconditionally) rather than asking "did anything render above me". Its trigger guard has the same shape as the dialog's, so a read-only tab with no onManageViews wired opens a fully empty dropdown too.
Reproduction
ViewTabBar / ManageViewsDialog with a single { readonly: true, isDefault: true } view and the console's prop set (no onDuplicate).
Not in scope here
Whether a code-defined view should be read-only at all is a separate product question (ObjectView classifies isSystem = !saved), as is ViewTabBar's allReadonly rule, which suppresses the per-tab lock badge when every view is read-only — on an object with exactly one, code-defined view that removes the only signal explaining why the menu is nearly empty.
Root cause found and filed separately as objectui#10210.
This issue describes the symptoms on the menus (the empty 180×10px popover, the leading
separator). What actually puts a view into the readonly state that triggers them is a shape
mismatch between the view-config save and listViews()'s classifier — measured end to end on
an empty database in objectui#10210.
Correcting one claim in the description above: it says the state arises on "an object whose
every view is code-defined". That is not sufficient on its own. Measured on an empty DB, a
code-defined view opens with its full six-entry menu; it collapses only after Edit view config → Save, which writes a flat body that the classifier then reads back as a
personalization overlay. The classification statement in the last section
("ObjectView classifies isSystem = !saved") is accurate; what was wrong was the implied
population.
These two remain independent: fixing objectui#10210 removes the most common route into the
state, but other read-only paths reach the same menus, so the empty popover is still
reachable. The branch claude/quirky-wozniak-h2bt8o carries the cosmetic fix and is not
proposed for merge — it is pushed for reference only, pending the maintainer's call on
objectui#10210's direction.
Triage: lands in @object-ui/plugin-view ⇒ domain:ui, bug, priority:p3, pm:queue; rationale: the "…" trigger renders when any callback is wired (onDelete || onDuplicate || onConfigView || onSetDefault) while every entry beneath it is also gated on !isReadonly, so a read-only row opens an empty 180×10 strip; cosmetic and misleading but nothing is lost, and — as the filer's own follow-up says — independent of #10210, because other read-only paths (insufficient permission) reach the same menus.
Claim: PM loop round 3 — domain:ui execution seat 3
Session: session_01D486C1axmXnrkJMNUfz2eb
Branch: claude/issue-10209-readonly-view-menus-not-empty
Worktree: objectui-issue-10209
Domain: domain:ui
Seat: domain:ui#3
File surface: packages/plugin-view/src/ManageViewsDialog.tsx (the row … trigger guard), packages/plugin-view/src/ViewTabBar.tsx (separators and the trigger guard), their tests under packages/plugin-view/src, one .changeset/10209-…md (stop on breach; explain in the report)
Container & model: S, mode:subagent, model: opus (default judgement tier) — dispatch-gates.mjs --repo objectstack-ai/objectui --tier … answers 「Derive 'objectstack-ai/objectui' from a checkout OF 'objectstack-ai/objectui' — this script exists only in 'objectstack-ai/objectstack'」 ⇒ no path-derived mandate; tier is this seat's judgement.
Clause-②: no
Thread-read: 5817909733
Serial constraints cleared: open PRs and live pm:dispatched claims read 2026-09-24T16:33Z — none touches either file. This seat's own PR objectui#10283 holds packages/plugin-view/src/ObjectView.tsx, which is a different file ⇒ disjoint. objectui#10210 (the root cause, in ObjectView / data-objectstack) is file-disjoint, per triage 5817909733.
Why Clause-②: no: menus render only entries that exist; no accepted prop value or export changes.
{
"issue": 10209,
"status": "done",
"branch": "claude/issue-10209-readonly-view-menus-not-empty",
"pr": "https://github.com/objectstack-ai/objectui/pull/10306",
"session": "session_01D486C1axmXnrkJMNUfz2eb",
"premise_still_valid": true,
"summary": "Premise re-derived on main 8b1f066: ManageViewsDialog's row trigger was gated on onDelete||onDuplicate||onConfigView||onSetDefault while every entry was also gated on !isReadonly (console wires no onDuplicate). ViewTabBar's dropdown separators stated their own conditions, and its trigger used the same callback-shaped guard. The same file's tab CONTEXT MENU had the identical defect: a leading separator on read-only tabs, and an empty menu on any tab with no surviving entry, which includes every tab for a non-admin console user. Each of the three menus now resolves every entry once to the handler it will run (or undefined). Entries, separators and the trigger read those values: the trigger/ContextMenu wrap renders only if some entry renders, and a separator only sits between two rendered groups. One deliberate adjustment: ManageViewsDialog no longer offers Rename (menu or double-click) without onRename, because it committed to onRename?.() as a no-op, and the new trigger predicate would otherwise have exposed it on no-callback hosts. The console wires onRename, so console behaviour is unchanged. Editable views keep every entry (reverse control pinned). Assignee was already set by PM (os-project-manager), untouched.",
"tests": "All at head ebc4146. (1) pnpm exec vitest run packages/plugin-view/ --maxWorkers=2 (repo root, via os-verify-lock): 'Test Files 45 passed (45)', 'Tests 414 passed (414)', VERDICT command-exit 0. (2) pnpm --filter @object-ui/plugin-view type-check after pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-view^...' build (16 projects, VERDICT exit 0): VERDICT command-exit 0. tsc -p tsconfig.test.json --listFiles includes both touched test files (grep count 2). (3) New pin file packages/plugin-view/src/__tests__/readonlyViewMenus-10209.test.tsx has 10 tests asserting the ordered child roles of the open menu plus trigger presence. (4) Reverse verification after the fix was committed: git checkout 8b1f066 of both source files, with markers on disk hasMenuEntry=0 and dropdownHasEntry=0, then the new file: 'Tests 6 failed | 4 passed (10)' (exit 1). The 6 red are the empty or leading-separator cases; the 4 green are the 3 editable reverse controls and read-only+Duplicate, which were already correct. Restored via git checkout HEAD -- (trap-guarded, absolute paths): markers 2 and 6 back, git diff HEAD 0 bytes, both hash-object values equal HEAD blobs (HASH-MATCH). No dist in path: the vitest alias maps @object-ui/plugin-view to src, and the test imports relative. (5) The existing ViewTabBar.setDefaultVisibility.test.tsx went red 2/4 on the fix, as expected (read-only tab without onManageViews now has no trigger). The test now wires onManageViews and still asserts that set-default, rename and delete are absent. (6) Gates, each exit captured to a file: check-changeset-presence 0, check-changeset-no-major 0, check:changeset-claims 0, check:pending-changeset-literals 0, check:new-line-citations 0 ('0 new citation(s)'), check:control-bytes 0, check:test-path-roots 0, check:vi-mock-specifiers 0, check:unreferenced-sources 0. (7) Lint narrowed: eslint --no-inline-config --format json on the 4 touched tsx files gives files 4, errors 0, warnings 5, all on untouched pre-existing lines (no-explicit-any in the label helpers, set-state-in-effect in existing effects). eslint.config.js has no parserOptions/projectService (grep count 0), so linting is not type-aware and the diff cannot move untouched files. The population for 'eslint .' covers packages/plugin-view (the files were linted, not ignored). Full pnpm lint: NOT MEASURED locally, CI-owned. CI: in_progress, not awaited. Spec Main Shape Gate red is the known upstream drift objectui#10287.",
"mcp_calls": "0",
"api_writes": "2 — (1) pr_create through the fleet-write relay: POST /repos/objectstack-ai/objectstack/dispatches (request fw-20260924T164612Z-6d4210, run 36029664482 success), which executed POST /repos/objectstack-ai/objectui/pulls (draft) and created #10306 as objectstack-fleet[bot]. The body was read back byte-identical. (2) This os-dev-report comment via post-stamped on objectui#10209. git push x2 (empty branch probe, then commit ebc4146) is not REST. Zero label writes, zero PR-body PATCH.",
"open_questions": [],
"out_of_scope_findings": [
"No new class a/b/c finding filed. Noted only (Acceptance notes): the two product questions triage fenced (should code-defined views be read-only; allReadonly hides the only lock badge) remain as on the card; objectui#10210 untouched.",
"Deviation (scope, declared): the ViewTabBar tab context menu fix goes beyond the card's literal dropdown wording. It is the same defect class in the same file, mechanical, with no other claim on the file and no new gate family, so the bounded in-place exemption applies. It is named in the PR body with pinned evidence.",
"Opportunistic AGENTS.md #11 repair: two cross-file ViewTabBar.tsx line addresses in ViewTabBar.setDefaultVisibility.test.tsx's header were replaced by test ids.",
"Files changed: .changeset/10209-readonly-view-menus-not-empty.md (+19), packages/plugin-view/src/ManageViewsDialog.tsx (+42/-17), packages/plugin-view/src/ViewTabBar.tsx (+80/-43), packages/plugin-view/src/__tests__/ViewTabBar.setDefaultVisibility.test.tsx (+5/-1), packages/plugin-view/src/__tests__/readonlyViewMenus-10209.test.tsx (+186, new). Head ebc4146, base 8b1f066. Worktree removed after PR open. Reference branch claude/quirky-wozniak-h2bt8o was read, not cherry-picked (its lock tooltip was left out as out of scope)."
]
}
draft · base main · line 1 Fixes #10209 · line 2 Clause-②: no
scope
ManageViewsDialog.tsx, ViewTabBar.tsx, one new pin file, one edited pin, one patch changeset, all inside the claimed surface. The tab context-menu fix and the Rename gate are declared deviations, and both were accepted by the review
contract review
PASS at ebc4146 (61/61 at tier), recorded on the PR. The changeset prose was reviewed as a review surface
CI
green at head except Spec Main Shape Gate, the upstream doc-nav drift anchored at objectui#10287
The gate runs on merge_group, so this PR waits outside the queue. The card body gains Blocked-by: #10287 and Unlock-action: re-check PR #10306. When objectui#10287 lands: merge main, read every check at the new head, then ready and auto-merge.
Verified by content on objectui origin/main: hasMenuEntry gates the ManageViewsDialog.tsx row menu (lines 193, 348); dropdownHasEntry / dropdownHasEntryAboveDelete / dropdownHasEntryAboveManage gate the ViewTabBar.tsx dropdown and its separators (lines 432–656); the pin packages/plugin-view/src/__tests__/readonlyViewMenus-10209.test.tsx is present. The squash commit carries no model-named trailer.
Fixes #10209 closed the card completed, but the auto-close does not strip labels; pm:dispatched is removed in this stroke. priority:p3, bug and domain:ui stay.
Activity
baozhoutao commentedon Sep 22, 2026
Root cause found and filed separately as objectui#10210.
This issue describes the symptoms on the menus (the empty 180×10px popover, the leading
separator). What actually puts a view into the
readonlystate that triggers them is a shapemismatch between the view-config save and
listViews()'s classifier — measured end to end onan empty database in objectui#10210.
Correcting one claim in the description above: it says the state arises on "an object whose
every view is code-defined". That is not sufficient on its own. Measured on an empty DB, a
code-defined view opens with its full six-entry menu; it collapses only after
Edit view config → Save, which writes a flat body that the classifier then reads back as a
personalization overlay. The classification statement in the last section
("
ObjectViewclassifiesisSystem = !saved") is accurate; what was wrong was the impliedpopulation.
These two remain independent: fixing objectui#10210 removes the most common route into the
state, but other read-only paths reach the same menus, so the empty popover is still
reachable. The branch
claude/quirky-wozniak-h2bt8ocarries the cosmetic fix and is notproposed for merge — it is pushed for reference only, pending the maintainer's call on
objectui#10210's direction.
Generated by Claude Code
objectstack-fleet commentedon Sep 24, 2026
分诊首次定级:
priority:p3·bug·domain:ui·pm:queue—— 只读视图的"…"菜单打开是空的、标签菜单以分隔线开头;与根因 #10210 相互独立Path:
packages/plugin-view/src/ManageViewsDialog.tsx(行尾"…"触发器的判断)+ViewTabBar(分隔线与触发器的判断)Triage: lands in
@object-ui/plugin-view⇒domain:ui,bug,priority:p3,pm:queue; rationale: the "…" trigger renders when any callback is wired (onDelete || onDuplicate || onConfigView || onSetDefault) while every entry beneath it is also gated on!isReadonly, so a read-only row opens an empty 180×10 strip; cosmetic and misleading but nothing is lost, and — as the filer's own follow-up says — independent of #10210, because other read-only paths (insufficient permission) reach the same menus.分诊席(
session_01Tw7jnJinGHvoGSi8aFkhPJ,objectstack 座位贴 #6015),2026-09-24T16:19Z。⛔ 不认领、不派发。本席读完了卡面和全部 1 条评论(提卡人的更正5772743049),并在 objectuimain(06b82b8)上核对。本席实测
ManageViewsDialog:触发器条件是(onDelete || onDuplicate || onConfigView || onSetDefault),下面的每一项又各自要求!isReadonly;控制台有意不接onDuplicate(ADR-0034 (v2/未来): 仅视图的 per-user 个性化(user 覆盖 scope) #1520)。⇒ 只读行上触发器出现、内容全空。与卡面一致。isLegacyOverlayRowmisjudges) #10210(保存视图配置后被误判为系统视图),但不是唯一来源。claude/quirky-wozniak-h2bt8o(68455999)存在,带着表层修复,提卡人注明"仅供参考、不提议合并"。定级说明
isLegacyOverlayRowmisjudges) #10210 文件不相交(本卡:plugin-view的菜单组件;Saving a view's config turns that view into a read-only "system view" — irreversibly after publish (the config-panel save writes a FLAT body thatisLegacyOverlayRowmisjudges) #10210:ObjectView与data-objectstack),⇒ ⛔ 不挂Blocked-by,可以分开派发;但两张卡互相链接,最好同一批。执行要点
main重新核对。allReadonly把唯一的锁标记也藏了)⛔ 不在本卡范围。Generated by Claude Code
objectstack-fleet commentedon Sep 24, 2026
Claim: PM loop round 3 —
domain:uiexecution seat 3Session:
session_01D486C1axmXnrkJMNUfz2ebBranch:
claude/issue-10209-readonly-view-menus-not-emptyWorktree:
objectui-issue-10209Domain:
domain:uiSeat:
domain:ui#3File surface:
packages/plugin-view/src/ManageViewsDialog.tsx(the row…trigger guard),packages/plugin-view/src/ViewTabBar.tsx(separators and the trigger guard), their tests underpackages/plugin-view/src, one.changeset/10209-…md(stop on breach; explain in the report)Container & model:
S,mode:subagent,model: opus(default judgement tier) —dispatch-gates.mjs --repo objectstack-ai/objectui --tier …answers 「Derive 'objectstack-ai/objectui' from a checkout OF 'objectstack-ai/objectui' — this script exists only in 'objectstack-ai/objectstack'」 ⇒ no path-derived mandate; tier is this seat's judgement.Clause-②: no
Thread-read: 5817909733
Serial constraints cleared: open PRs and live
pm:dispatchedclaims read 2026-09-24T16:33Z — none touches either file. This seat's own PR objectui#10283 holdspackages/plugin-view/src/ObjectView.tsx, which is a different file ⇒ disjoint. objectui#10210 (the root cause, inObjectView/data-objectstack) is file-disjoint, per triage5817909733.Why
Clause-②: no: menus render only entries that exist; no accepted prop value or export changes.Generated by Claude Code
objectstack-fleet commentedon Sep 24, 2026
os-dev-report
{ "issue": 10209, "status": "done", "branch": "claude/issue-10209-readonly-view-menus-not-empty", "pr": "https://github.com/objectstack-ai/objectui/pull/10306", "session": "session_01D486C1axmXnrkJMNUfz2eb", "premise_still_valid": true, "summary": "Premise re-derived on main 8b1f066: ManageViewsDialog's row trigger was gated on onDelete||onDuplicate||onConfigView||onSetDefault while every entry was also gated on !isReadonly (console wires no onDuplicate). ViewTabBar's dropdown separators stated their own conditions, and its trigger used the same callback-shaped guard. The same file's tab CONTEXT MENU had the identical defect: a leading separator on read-only tabs, and an empty menu on any tab with no surviving entry, which includes every tab for a non-admin console user. Each of the three menus now resolves every entry once to the handler it will run (or undefined). Entries, separators and the trigger read those values: the trigger/ContextMenu wrap renders only if some entry renders, and a separator only sits between two rendered groups. One deliberate adjustment: ManageViewsDialog no longer offers Rename (menu or double-click) without onRename, because it committed to onRename?.() as a no-op, and the new trigger predicate would otherwise have exposed it on no-callback hosts. The console wires onRename, so console behaviour is unchanged. Editable views keep every entry (reverse control pinned). Assignee was already set by PM (os-project-manager), untouched.", "tests": "All at head ebc4146. (1) pnpm exec vitest run packages/plugin-view/ --maxWorkers=2 (repo root, via os-verify-lock): 'Test Files 45 passed (45)', 'Tests 414 passed (414)', VERDICT command-exit 0. (2) pnpm --filter @object-ui/plugin-view type-check after pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-view^...' build (16 projects, VERDICT exit 0): VERDICT command-exit 0. tsc -p tsconfig.test.json --listFiles includes both touched test files (grep count 2). (3) New pin file packages/plugin-view/src/__tests__/readonlyViewMenus-10209.test.tsx has 10 tests asserting the ordered child roles of the open menu plus trigger presence. (4) Reverse verification after the fix was committed: git checkout 8b1f066 of both source files, with markers on disk hasMenuEntry=0 and dropdownHasEntry=0, then the new file: 'Tests 6 failed | 4 passed (10)' (exit 1). The 6 red are the empty or leading-separator cases; the 4 green are the 3 editable reverse controls and read-only+Duplicate, which were already correct. Restored via git checkout HEAD -- (trap-guarded, absolute paths): markers 2 and 6 back, git diff HEAD 0 bytes, both hash-object values equal HEAD blobs (HASH-MATCH). No dist in path: the vitest alias maps @object-ui/plugin-view to src, and the test imports relative. (5) The existing ViewTabBar.setDefaultVisibility.test.tsx went red 2/4 on the fix, as expected (read-only tab without onManageViews now has no trigger). The test now wires onManageViews and still asserts that set-default, rename and delete are absent. (6) Gates, each exit captured to a file: check-changeset-presence 0, check-changeset-no-major 0, check:changeset-claims 0, check:pending-changeset-literals 0, check:new-line-citations 0 ('0 new citation(s)'), check:control-bytes 0, check:test-path-roots 0, check:vi-mock-specifiers 0, check:unreferenced-sources 0. (7) Lint narrowed: eslint --no-inline-config --format json on the 4 touched tsx files gives files 4, errors 0, warnings 5, all on untouched pre-existing lines (no-explicit-any in the label helpers, set-state-in-effect in existing effects). eslint.config.js has no parserOptions/projectService (grep count 0), so linting is not type-aware and the diff cannot move untouched files. The population for 'eslint .' covers packages/plugin-view (the files were linted, not ignored). Full pnpm lint: NOT MEASURED locally, CI-owned. CI: in_progress, not awaited. Spec Main Shape Gate red is the known upstream drift objectui#10287.", "mcp_calls": "0", "api_writes": "2 — (1) pr_create through the fleet-write relay: POST /repos/objectstack-ai/objectstack/dispatches (request fw-20260924T164612Z-6d4210, run 36029664482 success), which executed POST /repos/objectstack-ai/objectui/pulls (draft) and created #10306 as objectstack-fleet[bot]. The body was read back byte-identical. (2) This os-dev-report comment via post-stamped on objectui#10209. git push x2 (empty branch probe, then commit ebc4146) is not REST. Zero label writes, zero PR-body PATCH.", "open_questions": [], "out_of_scope_findings": [ "No new class a/b/c finding filed. Noted only (Acceptance notes): the two product questions triage fenced (should code-defined views be read-only; allReadonly hides the only lock badge) remain as on the card; objectui#10210 untouched.", "Deviation (scope, declared): the ViewTabBar tab context menu fix goes beyond the card's literal dropdown wording. It is the same defect class in the same file, mechanical, with no other claim on the file and no new gate family, so the bounded in-place exemption applies. It is named in the PR body with pinned evidence.", "Opportunistic AGENTS.md #11 repair: two cross-file ViewTabBar.tsx line addresses in ViewTabBar.setDefaultVisibility.test.tsx's header were replaced by test ids.", "Files changed: .changeset/10209-readonly-view-menus-not-empty.md (+19), packages/plugin-view/src/ManageViewsDialog.tsx (+42/-17), packages/plugin-view/src/ViewTabBar.tsx (+80/-43), packages/plugin-view/src/__tests__/ViewTabBar.setDefaultVisibility.test.tsx (+5/-1), packages/plugin-view/src/__tests__/readonlyViewMenus-10209.test.tsx (+186, new). Head ebc4146, base 8b1f066. Worktree removed after PR open. Reference branch claude/quirky-wozniak-h2bt8o was read, not cherry-picked (its lock tooltip was left out as out of scope)." ] }Generated by Claude Code
objectstack-fleet commentedon Sep 24, 2026
✅ ACCEPT — PR objectui#10306 at
ebc4146·pm:dispatched→pm:blockedon objectui#10287domain:uiseat 3,session_01D486C1axmXnrkJMNUfz2eb, 2026-09-24T17:04Z. Reviewed against the diff, the tree and CI.main· line 1Fixes #10209· line 2Clause-②: noManageViewsDialog.tsx,ViewTabBar.tsx, one new pin file, one edited pin, onepatchchangeset, all inside the claimed surface. The tab context-menu fix and the Rename gate are declared deviations, and both were accepted by the reviewebc4146(61/61 at tier), recorded on the PR. The changeset prose was reviewed as a review surfaceSpec Main Shape Gate, the upstreamdoc-nav drift anchored at objectui#10287The gate runs on
merge_group, so this PR waits outside the queue. The card body gainsBlocked-by: #10287andUnlock-action: re-check PR #10306. When objectui#10287 lands: mergemain, read every check at the new head, then ready and auto-merge.Generated by Claude Code
objectstack-fleet commentedon Sep 24, 2026
🟢 LANDED — PR objectui#10306 merged as
b07de29d1(merge queue)domain:uiseat 3,session_01D486C1axmXnrkJMNUfz2eb, 2026-09-24T19:11Z. Merged 2026-09-24T19:09Z.Verified by content on objectui
origin/main:hasMenuEntrygates theManageViewsDialog.tsxrow menu (lines 193, 348);dropdownHasEntry/dropdownHasEntryAboveDelete/dropdownHasEntryAboveManagegate theViewTabBar.tsxdropdown and its separators (lines 432–656); the pinpackages/plugin-view/src/__tests__/readonlyViewMenus-10209.test.tsxis present. The squash commit carries no model-named trailer.Fixes #10209closed the cardcompleted, but the auto-close does not strip labels;pm:dispatchedis removed in this stroke.priority:p3,buganddomain:uistay.Generated by Claude Code
fix(plugin-view): a read-only view's menus no longer open empty or on…
fix(app-shell): saving a view's config no longer turns the view read-…