Skip to content

feat(plugin-list): a reader can add any field they may read to their own list (#30077) - #30162

Merged
objectstack-fleet[bot] merged 10 commits into
mainfrom
claude/issue-30077-list-add-columns
Nov 5, 2026
Merged

objectstack-fleet[bot] merged 10 commits into
mainfrom
claude/issue-30077-list-add-columns

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

domain:ui#2 · os-justin · session_01QSeKYnE9YNkSZen7sijLA3

Fixes #30077

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:列表的「隐藏字段」面板(工具栏的和「显示」面板里的同一节)在视图自带的列之后,列出该对象里读者有权读取的其余字段,默认不勾选;勾选就把它加成读者自己列表里的一列,取消勾选就拿掉。
  • 为什么改:按本卡裁决 A(「其他全部同意」),业务员想在自己的列表里看负责人、折扣等字段时,不必再请管理员替全公司改视图。
  • 风险与回滚:只存在读者个人偏好里(sys_user_preference 的 ui.list.OBJECT 行新增 addedFields),不写视图、不改元数据和 spec;回滚即 revert,旧版本的清洗函数会丢掉 addedFields,没有残留副作用。
  • 先看:ui/packages/plugin-list/src/ListView.tsx(addableFields 的取数与 updateListedFields 里「取消勾选已加列」的分支);ui/packages/app-shell/src/views/objectViewListRenderer.tsx(withAddedListColumns 与 columns 那一级的拼接);ui/packages/app-shell/src/context/ListPreferencesProvider.tsx(addedFields 的清洗)。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/plugin-list/src/__tests__/ListView.fieldListRead-11984.test.tsx packages/app-shell/src/views/ObjectView.userListState-12418.test.tsx,预期两个文件全部通过。
  • 需要你做的:无,席位落地。

What changes

  • The offer (@object-ui/plugin-list). Two new ListView host props: addedFields (the names among schema.columns the reader added) and onAddedFieldsChange (the door that stores them). When the door is wired, both hide-fields lists — the wide toolbar's Hide fields popover and the Hide Fields section of the Display panel (ViewSettingsPopover, the same door: it writes through the same updateHiddenFields callback) — list, after the view's columns, every other field of the object's definition that the caller may read (canReadField, the predicate every toolbar field list asks), that the definition does not mark hidden: true, and that is not already a column; business fields first, then system fields (isSystemManagedField), unticked. Ticking one calls onAddedFieldsChange with the whole new list; unticking an added column calls it without that name instead of hiding it; adding a name a stale hidden entry carries clears that entry too. A reader-added column is labelled from the definition in the popover. Without the door the popover is byte-for-byte today's.
  • The store (@object-ui/app-shell). ListViewPreference gains addedFields beside hiddenFields in the reader's ui.list.OBJECT row: sanitized to unique names, and an empty list is dropped (nothing added is the view's own). "Reset to view" removes it with the rest of the view's entry (resetViewPreference), unchanged code path.
  • The host (objectViewListRenderer). withAddedListColumns appends the stored added names to the columns rung in the view's own column shape (bare names beside bare names, { field } beside column objects), skipping any name the view itself declares; the list therefore fetches ($select), expands ($expand, through buildExpandFields over schema.columns) and draws an added column exactly as a declared one. The door is wired where a Hide Fields choice is stored (a view that declares columns, or a saved view); on a system view or fallback tab that declares no columns it is not wired, matching the session-only hide there.
  • The marker (objectViewListUrlState). A stored addedFields reports the existing columns part, so the Personalized marker draws and names "Column layout" with no new copy.
  • ui/packages/plugin-list/README.md documents the two host props (ui/AGENTS.md ✨ Set up Copilot instructions #2).

Tests

All runs on this branch's worktree, under scripts/pm/os-verify-lock.sh; head at the final run: 916241ae1.

  • New pins, ListView.fieldListRead-11984.test.tsx (top-level describe, objectstack#30077): wide and compact, restricted caller is offered Title, Status, Priority and not the two unreadable fields; full-read control offers them; door absent offers the view's columns alone; tick adds through the door and leaves hide/show of a view column unchanged; untick of an added column removes it and writes no hidden list; a stale hidden entry is cleared on add; the Display panel adds through the same door; a hidden: true field is not offered and system fields follow business fields; an added lookup is in the next $select and $expand.
  • New pins, ObjectView.userListState-12418.test.tsx (top-level describe): adding writes no view and lands as one save of addedFields; the list is handed the column appended; a fresh browser opens with it; another user does not see it; "Reset to view" drops it (the stored addition alone draws the marker); a name the view declares is not duplicated; a saved view that declares columns stores it the same way.
  • New pins, ListPreferencesProvider.slot-12418.test.ts: sanitize, patch/clear and reset of addedFields.
Readings
  • Three touched test files on 916241ae1: see Verification.
  • pnpm --dir ui exec vitest run --maxWorkers=2 packages/plugin-list/ (pre-merge head): Test Files 1 failed | 188 passed (189), Tests 1 failed | 1805 passed (1806); the one failure was this PR's own new lookup pin, which asserted a grid header this harness does not render (the grid registers lazily here). The pin was narrowed to the $select/$expand and popover assertions and re-run: ListView.fieldListRead-11984.test.tsx Tests 31 passed (31).
  • app-shell, 101 files touching list preferences, the relay or the marker (every ObjectView* and InterfaceListPage* test plus the git grep hits for ListPreferences/listPrefs/personaliz/onHiddenFieldsChange): Test Files 101 passed (101), Tests 880 passed (880). The full app-shell suite (2172 files) is declared to CI's sharded Test UI.
  • pnpm --filter @object-ui/plugin-list type-check exit 0; pnpm --filter @object-ui/app-shell type-check exit 0 (pre-merge, and again post-merge, see Verification).
  • Reverse verification (one-shot, no permanent file): addedFields={listColumns.addedFields} replaced by addedFields={42} with scripts/ablation-replace.mjs (anchor 1 to 0, blob 91185df42a4e to 8287f4f432e4), tsc --noEmit in app-shell: TSC_EXIT=2, error TS2322: Type 'number' is not assignable to type 'readonly string[]'; restored, blob equal to HEAD (91185df42a4e) and git diff HEAD empty. The rebuilt dist/ListView.d.ts carries both props and ListViewProps declares no index signature.

Acceptance notes

  • File surface. The claim named the store path under ui/packages/app-shell/src/views/**; the store is ui/packages/app-shell/src/context/ListPreferencesProvider.tsx (its sanitizer drops any undeclared part, so an added list cannot persist without it). objectViewListUrlState.ts (under views/**) carries the marker part. ui/packages/plugin-list/README.md is added for ui/AGENTS.md ✨ Set up Copilot instructions #2.
  • Lookup columns (card "Not measured"). No server change needed: $expand is built from schema.columns by buildExpandFields, so an added lookup is expanded as a declared one; pinned. No field type is excluded from the offer.
  • Where it is not offered. A system view or fallback tab that declares no columns (its hide is session-only by ruling 5839344270 on objectui#10694), and every host that does not wire onAddedFieldsChange (InterfaceListPage, page blocks).
  • Copy unchanged. The trigger still reads "Hide fields" and the section "Hide Fields"; the marker reports added fields as "Column layout", and the reset sentence already names "column layout". The Display trigger's count does not count added fields. Renaming the control would touch ten locale packs and is left as a note, not filed.
  • Shared list. The Group editor and Row color select read the same allFields, so they also list a column the reader added (it is a column of their list); they never list a field not on the list.
  • Query width. An added column widens $select by one field; the page size and existing caps are unchanged.
  • Grid header of an added column was not measured in a browser: this harness does not render the lazily registered grid. The popover label is pinned.

Verification

Post-merge head and gates

Head 916241ae1 (this branch merged with origin/main; the incoming commits touch none of this diff's files):

  • pnpm turbo run build --filter='@object-ui/app-shell^...' --concurrency=2: exit 0, Tasks: 67 successful, 67 total.
  • pnpm --filter @object-ui/app-shell type-check: exit 0.
  • The three touched test files: Test Files 3 passed (3), Tests 84 passed (84).
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 36 commands on this head; 35 re-run here on 916241ae1, each exit 0; pnpm --dir ui exec vitest run scripts/ exit 0 (Test Files 170 passed | 2 skipped (172)) on the pre-merge head, and the merge brings no ui/scripts change. --ran reconciliation: 36 derived, 36 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero, every line with its exit code).
  • Also run: pnpm check:changeset-fixed exit 0; pnpm check:new-line-citations (under ui/) exit 0, 0 new citation(s); check:doc-types, check:doc-json, check:doc-fences, check:doc-example-readers exit 0 over the README change. NOT MEASURED: check:doc-snippets / check:doc-examples, reason: each needs its own scoped 33-package build first (exit 2, prerequisite), left to CI.
  • Not run locally, declared to CI: the full app-shell suite (2172 files, sharded Test UI), Lint UI, Type Check UI, and the lint/type-check lanes the derivation lists as CI-only.

Browser check

Real Chromium (Playwright, real clicks and real typing), head 916241ae1. Example CRM backend (objectstack dev --seed-admin --fresh -p 4377 in examples/app-crm), Console built from this branch's ui/ source (vite --port 5377 --strictPort, DEV_PROXY_TARGET=http://localhost:4377), locale en-US, signed in as the seeded admin, on Opportunities / All Opportunities (crm_opportunity.all). The organization's first-run "Set the default currency" dialog was answered once. 14 of 14 assertions passed. The screenshots are kept in the seat's scratchpad, not uploaded; the seat viewed 04-grid-added.png and 11-labels.png.

  1. List opens on the view's columns. Headers: Opportunity Name, Account, Stage, Deal Value, Win Probability (%), Expected Revenue, Expected Close. PASS
  2. Offered unticked. In Display, Hide Fields, Owner (owner_id, a lookup) and Days to Close (a formula field) are listed after the view's columns, unticked. Discount (%), Approval Status, Renewal Of and Products Total are offered too. PASS
  3. Tick both. The grid header gains Owner and Days to Close. Owner cells read "Dev Admin" (the lookup's display name, not the id) and Days to Close cells carry values. The reader's preference ui.list.crm_opportunity reads perView.crm_opportunity.all.addedFields = ["owner_id","days_to_close"]. PASS
  4. Reload. Both columns are still there. PASS
  5. Reset to view. The Personalized marker names "Column layout". After the reset both columns are gone and the stored row is {}. PASS
  6. Untick an added column. Days to Close goes back to the add list unticked; the stored row is addedFields = ["owner_id"] with no hiddenFields. After a reload it is still absent and still offered, so it was removed, not hidden. PASS

Rework round 2: labels (head cfcc48813). The first run listed two of the view's own label-less columns under their machine names (account, expected_revenue) while the grid header read Account and Expected Revenue. allFields in ListView.tsx now falls back to the object definition's field label for every column with no label of its own. Order: translation, then the column's own label, then the definition's label, then the field name.

  1. Labels. Hide Fields lists Opportunity Name, Account, Stage, Deal Value, Win Probability (%), Expected Revenue, Expected Close, equal item for item to the grid header row. 3 of 3 assertions passed. PASS

Pin: ListView.fieldListRead-11984.test.tsx, "a column with no label of its own is listed under the definition's label, not its machine name"; ablated red (expected [ 'title', 'status', 'Priority ] to include 'Status'), restored green. The Group and Row color pickers share this list; the one pin that spelled the old raw name (ListView.rowColorSelect-12376.test.tsx, subject → Subject) was updated with its intent unchanged.

Not changed here: the list puts a translation before a column's own label, while the grid header puts the column's own label first and prettifies a bare name. They agree whenever a column has no label, which is the case fixed here.


Generated by Claude Code

@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 4, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#2 · os-justin · session_01QSeKYnE9YNkSZen7sijLA3

维护者速读

  • 改了什么:列表「显示 → 隐藏字段」面板里,读者可以勾选自己有权读、视图里没有的字段,加成自己的列;存进个人偏好,「重置为视图」清掉。顺带修了面板里无标签列显示机器名的问题。
  • 为什么改:裁决 A(6631126993):读者能加自己能读的字段,不用找管理员改全公司的视图。
  • 风险与回滚:只存个人偏好,不写元数据、不动 spec 或服务端;关联字段无需服务端改动即可展开。回滚 = 撤本 PR,已存的 addedFields 会被旧代码的清洗器丢弃。
  • 先看:plugin-list/src/ListView.tsx 的 allFields,app-shell/src/views/objectViewListRenderer.tsx 的 withAddedListColumns,ListPreferencesProvider.tsx 的 addedFields。
  • 两分钟验证:示例 CRM「商机」列表 → 显示 → 隐藏字段,勾选「负责人」,表格多出该列且显示人名;刷新仍在;重置后消失。
  • 需要你做的:无,席位落地(非受管面,620 行)。

席位意见:ACCEPT。真实浏览器 14+3 项断言通过,席位亲看截图 04、11。

席位核对

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#2 · os-justin · session_01QSeKYnE9YNkSZen7sijLA3

席位核对 · head moved by a merge of main only · 2026-11-05T01:25Z

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 5, 2026 01:26
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 5, 2026
Merged via the queue into main with commit dfafae8 Nov 5, 2026
56 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-30077-list-add-columns branch November 5, 2026 01:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Decision] Readers can hide list columns but not add one: should a reader be able to add an object's field to their list?

1 participant