Skip to content

Commit 12aad14

Browse files
fix(plugin-list, app-shell): "Reset to view default" clears the stored list setting instead of copying the view into it (#29044)
<!-- claude-projects-attribution: {"github_login":"zhuangjianguo"} --> _Requested by **Steedos** · [project thread](https://claude.ai/code/project/chan_018ETKaNwMZCNRYD7D8Q9Ubb?thread=cmsg_018ETKaNwMZCNRYD7D8Q9UbbKSY5upS6CYM6fGkDbsN2GK)_ Fixes #28948 Clause-②: yes (widening) Metadata-protocol: none ## 维护者速读 - **改了什么**:列表「显示」里的「恢复视图默认设置」和排序面板里的「恢复视图默认排序」,现在清除读者自己存下的那部分偏好(行高、隐藏列、排序),不再把视图当时的默认值当成读者的选择写进去。条目清空后整条删除,⋯ 里的「重置视图」随之消失。 - **为什么改**:原来恢复默认会把旧默认值存成读者的选择,管理员之后修改视图的行高、隐藏列或排序,这位读者仍停在旧值上,⋯ 也一直显示「重置视图」。 - **风险与回滚**:只动 `ui/` 的 `plugin-list`(`ListView` 新增宿主属性 `onRestoreViewDefaults`,`viewDefaults` 多一个 `sort`)和 `app-shell`(对象页接上这两个属性,恢复时把对应部分置空)。不动 spec、服务端和元数据。读者主动的「不排序」(`sort: []`)和「显示全部列」(`hiddenFields: []`)照旧保存。不接新属性的宿主行为不变。回滚 = revert 本 PR。 - **先看**:`ui/packages/plugin-list/src/ListView.tsx` 的 `resetDisplayToViewDefault` 与 `handleResetSort`;`ui/packages/app-shell/src/views/objectViewListRenderer.tsx` 的 `restoreViewDefaults`;新测试 `ListView.restoreViewDefaults-28948.test.tsx`。 - **两分钟验证**:`pnpm --dir ui exec vitest run packages/plugin-list/src/__tests__/ListView.restoreViewDefaults-28948.test.tsx packages/app-shell/src/views/ObjectView.userListState-12418.test.tsx`,应 2 个文件全部通过(6 + 32 个用例);或在 Console 线索列表改行高后点「恢复视图默认设置」,该用户的 `sys_user_preference` 条目里不再有 `rowHeight`。 - **需要你做的**:无,席位落地。 ## What changes **`@object-ui/plugin-list` — `ListView`** - New host prop `onRestoreViewDefaults(parts)`, where `parts` is drawn from `'sort' | 'hiddenFields' | 'rowHeight'`. When a host wires it, the Display control's "Reset to view default" and the sort popover's "Reset to view default" call it with the parts they returned to the view's own, in one call, and no longer write the view's values through `onHiddenFieldsChange`, `schema.onDensityChange` or `onSortChange` for those parts. The list puts the view's sort and hidden fields back at once; the table density follows `schema.rowHeight` once the host's clear lays the view's own back there. - `viewDefaults` gains `sort` (a pick of `ListViewSchema`'s `hiddenFields`, `rowHeight` and `sort`). The sort popover's reset compares against it (enabled state, whether it is drawn) and restores it. As with the other two members, a passed `viewDefaults` is the whole baseline: an absent `sort` means the view declares none. - Without `onRestoreViewDefaults` (and without `viewDefaults`), both resets behave exactly as before. Layout, grouping, row color, row numbers, inline edit and the gallery card size reset as before in every case. **`@object-ui/app-shell` — the object page's list** - Hands `viewDefaults.sort` (the view's own sort, under the link's or user's stored sort that the `sort` rung lays over it) and wires `onRestoreViewDefaults` to `restoreViewDefaults`, which patches each named part to `null` on the user's list state (`listPrefs.patchView`). `patchViewPreference` already treats `null` as a clear and removes a view left with nothing, so "Reset view" disappears with the last part. A sort reset also takes the sort off the URL (`writeListUrlState({ sort: null })`). - `ListPreferencesProvider`: docblock only — `ListViewPreferencePatch` now names the list's per-part reset as the second way back to the view's own. ## Tests - `ui/packages/plugin-list/src/__tests__/ListView.restoreViewDefaults-28948.test.tsx` (new, 6 cases): the Display reset names `hiddenFields` and calls no `onHiddenFieldsChange`; names `rowHeight`, calls no `onDensityChange`, and follows the host's re-rendered `schema.rowHeight`; both differ ⇒ one call naming both; the sort reset returns to `viewDefaults.sort` (server `$orderby` moves from the user's to the view's), names `sort`, calls no `onSortChange`; CONTROL: removing the last sort level still emits `onSortChange([])`; CONTROL: row numbers reset without naming anything. - `ui/packages/app-shell/src/views/ObjectView.userListState-12418.test.tsx` — new describe at the host seam (real `ObjectView`, real `ListPreferencesProvider`, recorded list-state family), 6 cases: the list is wired and handed the view's sort; a Display reset leaves neither density nor hidden fields in the saved row; after it, a changed view `rowHeight` / `hiddenFields` reaches the user on the next open; a sort reset with nothing else stored saves `{}` and the ⋯ "Reset view" entry goes; after it, a changed view sort reaches the user; CONTROL: a deliberate `sort: []` and `hiddenFields: []` are saved and survive a reload. The three `viewDefaults` equality pins now include `sort`; the old "the list's reset writes through the doors" case is renamed to what it pins (a choice equal to the view's own is stored as a choice). <details><summary>Ablation (scripts/ablation-replace.mjs, source-resolved, no dist)</summary> - Sort reset back to the door (`if (onRestoreViewDefaults) onRestoreViewDefaults(['sort']); else emitSortChange(restored);` to `emitSortChange(restored);`): 1 failed (the sort reset case), 5 passed. Restored, blob equal to HEAD, `git diff HEAD` empty. - Density leg removed (the `else if (onRestoreViewDefaults)` branch dropped, so the density goes through `density.setMode`): 2 failed (the density case, the both-parts case), 4 passed. Restored, blob equal to HEAD, `git diff HEAD` empty. - Host stores the view's values instead of clearing (`patch[part] = null` to the view's value per part): 4 failed in the host file (both "leaves nothing stored" cases and both "follows the view on the next open" cases), 28 passed. Restored, blob equal to HEAD, `git diff HEAD` empty. </details> ## Acceptance notes - The sort popover's reset in the console used to return to the sort the list OPENED with (a link's sort, or the user's stored sort), per the `sort` rung's old comment. It now returns to the view's own sort, which is what its label says and what a reload shows once the stored sort is cleared; "Reset view" already did this. - A display reset under `onRestoreViewDefaults` does not set the density locally; it waits for the host to clear `rowHeight`. A host wiring the prop must clear what it is named, which the docblock says. - Not pinned: the URL `sort` param removal on a sort reset (the harness's `MemoryRouter` URL is not read by these tests). - #28950 (the 已个性化 marker) builds on these reset paths and is out of scope here. ## Verification All on the final head `eda72c06` (base `origin/main` `fed3a51b`), after `pnpm install --frozen-lockfile` and `turbo run build --filter='@object-ui/app-shell^...' --filter='@object-ui/plugin-list^...'` plus formula, sdui-parser, lint and client (64 tasks, exit 0), under `scripts/pm/os-verify-lock.sh`. - The whole `packages/plugin-list/` vitest suite plus 16 app-shell / console / plugin-view test files that name the touched modules or props (`git grep -l` of `objectViewListRenderer`, `ListPreferencesProvider`, `viewDefaults`, `listPrefs`, `patchView`, `onHiddenFieldsChange`, `onDensityChange`, the two reset test ids), `--maxWorkers=2`: 196 files, 1907 tests passed. A declared narrowing for app-shell; CI's Test UI runs the rest. - `pnpm --filter @object-ui/plugin-list run type-check` and `pnpm --filter @object-ui/app-shell run type-check` (the test programs included): exit 0. - ESLint on the five changed tsx files: exit 0, 0 errors (existing warnings only). - Derived gates (`node scripts/pm/dispatch-gates.mjs --commands`): 35 of 35 run, each exit 0 (`check:type-check-debt` and `pnpm --dir ui exec vitest run scripts/` under the lock; the latter 169 files passed, 2 skipped), including `node scripts/check-sdui-manifest.mjs` and the PR-body gates with this body as `GITHUB_EVENT_PATH`. `--ran` reconciles 35 derived, 35 run, 0 NOT-MEASURED, 0 UNRUN. `check-partof-closing-keyword` with `PR_BODY`: exit 0. `ui` `check:new-line-citations`: 0 new citations. - `origin/main` moved to `ab085a04` since the base; none of its commits touch the five changed files, so no merge was taken before opening. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01QsR2jTivKAK8N8dfW5mn9D --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent b388cfe commit 12aad14

6 files changed

Lines changed: 471 additions & 35 deletions

File tree

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
'@object-ui/plugin-list': patch
3+
'@object-ui/app-shell': patch
4+
---
5+
6+
fix(plugin-list, app-shell): "Reset to view default" clears the user's stored list setting instead of storing the view's value as their choice
7+
8+
Clause-②: yes (widening)
9+
Metadata-protocol: none
10+
11+
In the console, a list's density, hidden fields and sort are the signed-in user's own preference. The Display control's "Reset to view default" and the sort popover's "Reset to view default" used to write the view's values back through the controls, so the console stored them as the user's choice: the user stayed on the old value after the view's author changed it, and the header ⋯ kept offering "Reset view".
12+
13+
- **`ListView` gains `onRestoreViewDefaults(parts)`**, a host prop. When a host wires it, those two resets call it with the parts they returned to the view's own (`'sort'`, `'hiddenFields'`, `'rowHeight'`) and no longer call `onSortChange`, `onHiddenFieldsChange` or `schema.onDensityChange` for them. The list shows the view's sort and hidden fields at once; the density follows `schema.rowHeight` once the host clears it. Without the prop, the resets write through the controls exactly as before.
14+
- **`viewDefaults` gains `sort`.** A host that lays the user's own sort over the view's in `schema` hands the view's here, and the sort popover's reset compares against it and returns to it. As with the other two members, an absent `sort` in a passed `viewDefaults` means the view declares none.
15+
- **The console clears the stored parts.** The object page wires both: a reset removes those parts from the user's stored entry for the view (and the sort from the URL), and a view left with nothing is removed, so "Reset view" disappears with it. A deliberate choice is unchanged: removing every sort level (`sort: []`) or showing every column (`hiddenFields: []`) is still stored.
16+
- **Unchanged:** the other Display settings (layout, grouping, row color, row numbers, inline edit, gallery card size) reset as before.

‎ui/packages/app-shell/src/context/ListPreferencesProvider.tsx‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,8 +210,11 @@ export function sanitizeObjectListPreference(v: unknown): ObjectListPreference {
210210
* with no order and no width. An empty sort is NOT a clear: `sort: []` is kept,
211211
* because a user who removed every sort level (the Sort builder emits `[]`)
212212
* chose "no sort", which is not the view's authored sort. The same holds for
213-
* `hiddenFields: []` ("show every column"). "Reset view" is the way back to
214-
* the view's own. A view left with nothing is removed.
213+
* `hiddenFields: []` ("show every column"). The ways back to the view's own
214+
* are "Reset view" (the whole entry, `resetViewPreference`) and the list's
215+
* "Reset to view default" for one part, which patches that part to `null`
216+
* (objectstack#28948) instead of storing the view's value as a choice. A view
217+
* left with nothing is removed.
215218
*/
216219
export type ListViewPreferencePatch = {
217220
sort?: ReadonlyArray<{ field: string; order?: string }> | null;

‎ui/packages/app-shell/src/views/ObjectView.userListState-12418.test.tsx‎

Lines changed: 134 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@
4646
* hidden fields in `viewDefaults`, beside the user's in `schema`, so
4747
* its Display reset compares against the view; on an unprojected saved
4848
* view the view hides nothing.
49+
* 6d. objectstack#28948 — the list's "Reset to view default" (Display and
50+
* sort) is told apart from a choice: the reset parts are CLEARED from
51+
* the stored entry, so the user follows later changes to the view; a
52+
* deliberate `sort: []` / `hiddenFields: []` is still stored.
4953
* 7. CONTROL — no provider (a host without the console shell): the state is
5054
* this browser's, as the tab order and filters were before; a sort still
5155
* writes no view; the older `viewOrder:<object>` key is still honoured.
@@ -703,10 +707,10 @@ describe('the list is handed the view\'s own density and hidden fields beside th
703707

704708
expect(listProps.schema.rowHeight).toBe('compact');
705709
expect(listProps.schema.hiddenFields).toEqual(['name']);
706-
expect(listProps.viewDefaults).toEqual({ hiddenFields: AUTHORED_HIDDEN, rowHeight: AUTHORED_ROW_HEIGHT });
710+
expect(listProps.viewDefaults).toEqual({ hiddenFields: AUTHORED_HIDDEN, rowHeight: AUTHORED_ROW_HEIGHT, sort: AUTHORED_SORT });
707711
});
708712

709-
it('the list\'s reset writes the view\'s own through the controls\' doors, and it lands as the user\'s choice', async () => {
713+
it('a choice equal to the view\'s own, made through the controls\' doors, lands as the user\'s choice', async () => {
710714
const ds = makeDataSource();
711715
const { family } = makeFamily(structuredClone(STORED));
712716
mountWithFamily(ds, family);
@@ -725,7 +729,7 @@ describe('the list is handed the view\'s own density and hidden fields beside th
725729
});
726730
expect(listProps.schema.rowHeight).toBe(AUTHORED_ROW_HEIGHT);
727731
expect(listProps.schema.hiddenFields).toEqual(AUTHORED_HIDDEN);
728-
expect(listProps.viewDefaults).toEqual({ hiddenFields: AUTHORED_HIDDEN, rowHeight: AUTHORED_ROW_HEIGHT });
732+
expect(listProps.viewDefaults).toEqual({ hiddenFields: AUTHORED_HIDDEN, rowHeight: AUTHORED_ROW_HEIGHT, sort: AUTHORED_SORT });
729733
});
730734

731735
it('CONTROL: another user, with no state of their own, is handed the same value twice', async () => {
@@ -737,7 +741,9 @@ describe('the list is handed the view\'s own density and hidden fields beside th
737741

738742
expect(listProps.schema.hiddenFields).toEqual(AUTHORED_HIDDEN);
739743
expect(listProps.schema.rowHeight).toBe(AUTHORED_ROW_HEIGHT);
740-
expect(listProps.viewDefaults).toEqual({ hiddenFields: listProps.schema.hiddenFields, rowHeight: listProps.schema.rowHeight });
744+
expect(listProps.viewDefaults).toEqual({
745+
hiddenFields: listProps.schema.hiddenFields, rowHeight: listProps.schema.rowHeight, sort: listProps.schema.sort,
746+
});
741747
});
742748

743749
it('on a saved view that declares no `columns`, the view hides nothing: its authored key stays unapplied (objectui#12505)', async () => {
@@ -752,6 +758,130 @@ describe('the list is handed the view\'s own density and hidden fields beside th
752758
});
753759
});
754760

761+
describe('the list\'s "Reset to view default" clears the user\'s stored part instead of copying the view into it (objectstack#28948)', () => {
762+
/** A stored choice that differs from the view's on the sort, the density and the hidden fields. */
763+
const STORED = {
764+
[LIST_KEY]: {
765+
perView: {
766+
[SYSTEM_ID]: { sort: [{ field: 'amount', order: 'desc' }], rowHeight: 'compact', hiddenFields: ['name'] },
767+
},
768+
},
769+
};
770+
const VIEW = OBJECT.listViews[SYSTEM_ID] as Record<string, unknown>;
771+
772+
it('the list is wired to tell a reset from a choice, and is handed the view\'s own sort', async () => {
773+
const ds = makeDataSource();
774+
const { family } = makeFamily(structuredClone(STORED));
775+
mountWithFamily(ds, family);
776+
await listReady();
777+
778+
expect(typeof listProps.onRestoreViewDefaults).toBe('function');
779+
// `schema.sort` is the user's; the reset's baseline is the view's own.
780+
expect(listProps.schema.sort).toEqual([{ field: 'amount', order: 'desc' }]);
781+
expect(listProps.viewDefaults.sort).toEqual(AUTHORED_SORT);
782+
});
783+
784+
it('a Display reset of density and hidden fields leaves neither in the stored entry', async () => {
785+
const ds = makeDataSource();
786+
const { family } = makeFamily(structuredClone(STORED));
787+
mountWithFamily(ds, family);
788+
await listReady();
789+
790+
act(() => listProps.onRestoreViewDefaults(['hiddenFields', 'rowHeight']));
791+
await wait(700);
792+
793+
expect(viewWrites(ds)).toBe(0);
794+
expect(family.save).toHaveBeenCalledTimes(1);
795+
expect(family.save).toHaveBeenCalledWith(LIST_KEY, {
796+
perView: { [SYSTEM_ID]: { sort: [{ field: 'amount', order: 'desc' }] } },
797+
});
798+
// The list is handed the view's own again.
799+
expect(listProps.schema.rowHeight).toBe(AUTHORED_ROW_HEIGHT);
800+
expect(listProps.schema.hiddenFields).toEqual(AUTHORED_HIDDEN);
801+
});
802+
803+
it('after the reset the user follows the view: a changed `rowHeight` and `hiddenFields` reach them on the next open', async () => {
804+
const rows = structuredClone(STORED) as Record<string, any>;
805+
const ds = makeDataSource();
806+
const { family } = makeFamily(rows);
807+
const { unmount } = mountWithFamily(ds, family);
808+
await listReady();
809+
act(() => listProps.onRestoreViewDefaults(['hiddenFields', 'rowHeight']));
810+
await wait(700);
811+
unmount();
812+
813+
// The view's author changes its defaults.
814+
const before = { rowHeight: VIEW.rowHeight, hiddenFields: VIEW.hiddenFields };
815+
VIEW.rowHeight = 'short';
816+
VIEW.hiddenFields = ['name'];
817+
try {
818+
listProps = null;
819+
mountWithFamily(makeDataSource(), makeFamily(rows).family);
820+
await listReady();
821+
expect(listProps.schema.rowHeight).toBe('short');
822+
expect(listProps.schema.hiddenFields).toEqual(['name']);
823+
} finally {
824+
VIEW.rowHeight = before.rowHeight;
825+
VIEW.hiddenFields = before.hiddenFields;
826+
}
827+
});
828+
829+
it('a sort reset leaves no sort stored; with nothing else stored the entry and "Reset view" go', async () => {
830+
const ds = makeDataSource();
831+
const { family } = makeFamily({ [LIST_KEY]: { perView: { [SYSTEM_ID]: { sort: [{ field: 'amount', order: 'desc' }] } } } });
832+
mountWithFamily(ds, family);
833+
await listReady();
834+
expect(await overflowTestIds()).toContain('object-view-reset-view-button');
835+
836+
act(() => listProps.onRestoreViewDefaults(['sort']));
837+
await wait(700);
838+
839+
expect(viewWrites(ds)).toBe(0);
840+
expect(family.save).toHaveBeenCalledTimes(1);
841+
expect(family.save).toHaveBeenCalledWith(LIST_KEY, {});
842+
expect(await overflowTestIds()).not.toContain('object-view-reset-view-button');
843+
});
844+
845+
it('after a sort reset a changed view sort reaches the user on the next open', async () => {
846+
const rows: Record<string, any> = { [LIST_KEY]: { perView: { [SYSTEM_ID]: { sort: [{ field: 'amount', order: 'desc' }] } } } };
847+
const { unmount } = mountWithFamily(makeDataSource(), makeFamily(rows).family);
848+
await listReady();
849+
act(() => listProps.onRestoreViewDefaults(['sort']));
850+
await wait(700);
851+
unmount();
852+
853+
const before = VIEW.sort;
854+
VIEW.sort = [{ field: 'amount', order: 'asc' }];
855+
try {
856+
listProps = null;
857+
mountWithFamily(makeDataSource(), makeFamily(rows).family);
858+
await listReady();
859+
expect(listProps.schema.sort).toEqual([{ field: 'amount', order: 'asc' }]);
860+
} finally {
861+
VIEW.sort = before;
862+
}
863+
});
864+
865+
it('CONTROL: a deliberate "no sort" and "show every column" are still stored, and survive a reload', async () => {
866+
const rows: Record<string, any> = {};
867+
const { unmount } = mountWithFamily(makeDataSource(), makeFamily(rows).family);
868+
await listReady();
869+
act(() => {
870+
listProps.onSortChange([]);
871+
listProps.onHiddenFieldsChange([]);
872+
});
873+
await wait(700);
874+
expect(rows[LIST_KEY]).toEqual({ perView: { [SYSTEM_ID]: { sort: [], hiddenFields: [] } } });
875+
unmount();
876+
877+
listProps = null;
878+
mountWithFamily(makeDataSource(), makeFamily(rows).family);
879+
await listReady();
880+
expect(listProps.schema.sort).toEqual([]);
881+
expect(listProps.schema.hiddenFields).toEqual([]);
882+
});
883+
});
884+
755885
describe('CONTROL — no provider: this browser\'s state, as before (objectui#12418)', () => {
756886
function mountBare(ds: ConsoleObjectViewProps['dataSource'], viewId = SYSTEM_ID) {
757887
return render(page(ds, viewId));

‎ui/packages/app-shell/src/views/objectViewListRenderer.tsx‎

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import { useCallback, useRef, lazy, Suspense, type ReactNode } from 'react';
1212
import { DENSITY_MODE_TO_ROW_HEIGHT, normalizeListViewSchema, type FilterTokenScope } from '@object-ui/core';
1313
import { buildListFilterKey, writeListFilterState } from './listFilterStorage.js';
14-
import type { useObjectListPreferences, ListViewPreference } from '../context/ListPreferencesProvider.js';
14+
import type { useObjectListPreferences, ListViewPreference, ListViewPreferencePatch } from '../context/ListPreferencesProvider.js';
1515
const ObjectChart = lazy(() =>
1616
import('@object-ui/plugin-charts').then((m) => ({ default: m.ObjectChart })),
1717
);
@@ -391,6 +391,27 @@ export function useObjectViewListRenderer({
391391
const viewHiddenFields: ListViewSchema['hiddenFields'] = activeViewDeclaresColumns
392392
? ((viewDef as any).hiddenFields ?? listSchema.hiddenFields)
393393
: undefined;
394+
// objectstack#28948 — and the view's own sort, under the user's (the
395+
// `sort` rung below lays a link's or the user's stored sort over it).
396+
const viewSort: ListViewSchema['sort'] = (viewDef as any).sort ?? listSchema.sort;
397+
398+
/**
399+
* objectstack#28948 — a reset to the view's own is not a choice. The
400+
* list's "Reset to view default" (the Display control's, the sort
401+
* popover's) names the parts it put back, and they are CLEARED from
402+
* this user's stored state rather than stored as the view's values, so
403+
* a later change to the view reaches them. A view left with nothing is
404+
* removed (`patchViewPreference`), and with it the ⋯ "Reset view".
405+
* The sort leaves the URL too, which would otherwise seed it again.
406+
* A deliberate `sort: []` or `hiddenFields: []` still arrives through
407+
* `persistListSort` / `persistHiddenFields` and stays stored.
408+
*/
409+
const restoreViewDefaults = (parts: ReadonlyArray<'sort' | 'hiddenFields' | 'rowHeight'>) => {
410+
const patch: ListViewPreferencePatch = {};
411+
for (const part of parts) patch[part] = null;
412+
listPrefs.patchView(viewDef.id, patch);
413+
if (parts.includes('sort')) writeListUrlState({ sort: null });
414+
};
394415

395416

396417
/**
@@ -483,9 +504,9 @@ export function useObjectViewListRenderer({
483504
// objectui#11860 — a sort the URL carries is applied over the
484505
// view's: an opened link sorts as it was sorted. Without a link,
485506
// the seed carries the sort this user left the view with
486-
// (objectui#12418). `ListView` reads this as the list's declared
487-
// sort too, so its "reset to default" returns to the sort the list
488-
// opened with; "Reset view" returns to the view's own.
507+
// (objectui#12418). The view's own is `viewDefaults.sort`
508+
// (objectstack#28948): the sort popover's "reset to default"
509+
// returns there and clears the stored sort, as "Reset view" does.
489510
sort: listSeed?.sort ?? (viewDef as any).sort ?? listSchema.sort,
490511
// objectui#11860 — and a grouping the URL carries is laid over the
491512
// view's, which the caller composed into `listSchema` (the census
@@ -850,8 +871,11 @@ export function useObjectViewListRenderer({
850871
key={identityKey}
851872
schema={fullSchema}
852873
// objectui#12949 — the view's own values under the user's
853-
// preference, for the Display control's reset and count.
854-
viewDefaults={{ hiddenFields: viewHiddenFields, rowHeight: viewRowHeight }}
874+
// preference, for the Display control's reset and count, and
875+
// (objectstack#28948) the sort popover's reset.
876+
viewDefaults={{ hiddenFields: viewHiddenFields, rowHeight: viewRowHeight, sort: viewSort }}
877+
// objectstack#28948 — those resets clear the user's state.
878+
onRestoreViewDefaults={restoreViewDefaults}
855879
className={className}
856880
onEdit={editHandler}
857881
// objectui#13089 — the view's Add button is the header New:

0 commit comments

Comments
 (0)