Skip to content

Commit ad030d8

Browse files
feat(plugin-detail)!: the record page's related panels lose collapse; the header opens the whole list and empty panels fold into one line (#27920)
domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC <!-- claude-projects-attribution: {"github_login":"os-bill"} --> _Requested by **Bill** · [project thread](https://claude.ai/code/project/chan_016iMXWSZDzqWRWvWzdNo8wg?thread=cmsg_016iMXWSZDzqWRWvWzdNo8wgQSrPxYRF6Q9RRD48Snj7Ks)_ Part of #27524 Clause-②: yes (narrowing) Metadata-protocol: none Skills-docs: none — no file under `skills/**` or `content/docs/**` teaches the related card's collapse, its `⋯` items or the stored arrangement (a `git grep` for each names nothing); what an author writes is unchanged. ## 维护者速读 - 改了什么:记录页右栏的关联列表变成同形面板。表头是「标题 + 数量」,点表头或数量打开整张列表;悬停时右端出现 ⠿ + ⋯,⋯ 只剩「隐藏」;没有折叠、没有 ⤢。0 行的面板收成一行「还有 N 个空关联 · 名字 · … · 展开」。存储的排列去掉 `collapsed`(版本升到 2,旧记录读作「没选过」)。行内首列跳过指回本记录的外键。 - 为什么改:#27524 的 Target 1、2、10、11(画布 60 定稿),是四个串行 PR 中的第一个;3、12 在 main 上已成立,本 PR 只加钉子。 - 风险与回滚:只动 `@object-ui/plugin-detail`、十个语言包和 app-shell 的偏好编码;已拖过顺序的读者会回到默认顺序一次(无兼容读取,按裁定)。回滚即 revert 本 PR。 - 先看:`ui/packages/plugin-detail/src/RelatedList.tsx`(紧凑面表头与首列);`RelatedListEmptyFold.tsx`(面板行的折叠规则);`synth/relatedArrangement.ts`(去掉 collapsed)。 - 两分钟验证:`pnpm --dir ui exec vitest run packages/plugin-detail/src/__tests__/RelatedPanel.header-27524.test.tsx packages/plugin-detail/src/renderers/__tests__/record-related-list.emptyPanels-27524.test.tsx`,预期 2 个文件、14 个测试全绿。 - 需要你做的:无,席位落地。 ## What changes The record page's related column becomes a stack of same-shaped panels (record page v4, canvas 60). This is PR ① of four serial PRs for #27524, approved by the seat as a split; each leaves `main` coherent on its own. | Target item | Where | |---|---| | 1 panel header | **this PR** (interim: the header opens the existing `?related=` face; PR ③ makes it the panel's tab) | | 2 empty panels fold into one line | **this PR** | | 3 column foot | already on `main` (`RelatedCardStack-27055` pins it); unchanged here | | 10 stored arrangement without `collapsed` | **this PR** | | 11 compact row's first column | **this PR** | | 12 highlights = declared minus the name, max 6 | already on `main`; this PR adds the pin `highlights-27524.test.ts` | | 4, 5, 6 ⊟ switch, 动态 as the first panel, tiers by container width | PR ② | | 8, 9 the tab row, enlarged = active tab | PR ③ | | 7 the top bar | PR ④ (with #27545's ⤢ / ⤡); the last PR, the one that closes the card | **1. The panel header** (`RelatedList` compact face, `RelatedCardMenu.tsx`). Title, then the count chip, inside one `<button data-related-panel-open>` whose `title` reads 「查看全部 N 条」 (`detail.relatedPanelViewAll`); › shows at the title's end on hover or focus. The header row itself is the target too: the button's `::after` stretches over the positioned header, so a click on its free space opens the list while the row stays one control. The controls sit above it (`relative z-10`). On hover or focus-within the right end shows ⠿ (`RelatedPanelHandle`: the drag source on desktop; focused, ⌥↑ / ⌥↓ move the panel and keep focus on it), + and ⋯, which holds 隐藏 only. No collapse, no ⤢, nothing reserved on the left. On a touch screen (`hover: none`) the controls stay drawn. The full face of an arranged list (the Related tab of an authored page) draws the same ⠿ and ⋯ and no longer collapses from its header. The header has no click handler of its own, so a click inside the ⋯ menu's portaled content cannot open the list, even though it bubbles through React. The test `⋯ holds 隐藏 and nothing else` pins this. The first version, a clickable header `div` guarded by `event.currentTarget.contains(event.target)`, failed `clickableNonControlCensus-13461` in CI on `b8c4395be2`. `1d1d2d6e6f` replaces it with the stretched button. The census is red on `b8c4395be2` and green after the fix, locally. **2. Empty panels** (`RelatedListEmptyFold.tsx`). In the panel column (`RelatedListCompactContext`) a stack of two or more folds its 0-row panels into one line after the others: 「还有 N 个空关联 · 名字 · … · 展开」 (`detail.relatedEmptyFold`, plural per pack; 展开 reuses `detail.relatedCardExpand`). Unlike the Related tab's line (#25895, unchanged), the panel line folds a panel with an `add` picker too, folds a column whose panels are all empty, and carries no New: 展开 draws the panels again in their places, each with its own +. Non-empty panels keep their order. 展开 holds for the record it was pressed on (`Stack.expandedFor`, `object:id` from the record context). A view that steps to another record in place, such as the pane's ↓ ↑, keeps its column element, and the next record folds again. **10. The stored arrangement** (`synth/relatedArrangement.ts`, `useRelatedArrangement.ts`, app-shell `workspacePreference.ts`). `record.related.arrangement` is `{ v, order, hidden, basedOn, seen }`. `collapsed`, the `collapse` edit and the `defaultCollapsed` argument are removed; the codec's version is `RELATED_ARRANGEMENT_RECORD_VERSION` (2), so a version-1 record reads as no choice (no compatibility read, per the ruling). `CollapsedRelatedCard` and the stack's collapsed branch go. **11. The compact row's first column** (`RelatedList`). When the child's name field is the FK pointing back at this record, the row is named by the first column that is not that FK, not redacted, not a platform column and readable. The authored columns are read first, then the declared fields. The FK is skipped even when it is the only authored column. So a product's line items name the opportunity. **i18n.** `detail.relatedCardMoveUp`, `detail.relatedCardMoveDown`, `detail.relatedCardCollapse` and `detail.relatedCardCount` leave all ten packs; `detail.relatedPanelViewAll`, `detail.relatedPanelReorder`, `detail.relatedPanelReorderHint` and `detail.relatedEmptyFold` arrive in all ten, with each pack's plural set. **Declared.** `Clause-②: yes (narrowing)`: `RelatedArrangement`, `ResolvedRelatedArrangement` and `RelatedArrangementEdit` are exported from the plugin-detail entry and lose members. `@object-ui/i18n`'s exported `TranslationKeys` (`typeof en`) also loses the four removed keys. A `t()` call naming one still compiles, because its key parameter is a `string`. The changeset's BREAKING banner names both packages. Its ADR-0087 marker, `not-required (runtime-interface-only …)`, names all four symbols, and the gate verifies it. The changeset also carries the one-line fix. `RELATED_ARRANGEMENT_RECORD_VERSION` is a new export. **Source re-measured (main `273137d227` → this branch's base).** S1, S3 and S4 hold. S2 drifted, description only: the page's crumb row carries ‹ back and 「收为抽屉」, with no ↑ ↓. The card names `synth/recordTimelineStream.ts`; the file is `src/recordTimelineStream.ts`, and the promotion logic it means lives in `synth/recordPageColumns.ts` (PR ② retires it there). Neither drift changes what a Target item asks. **File surface.** One file outside the card's list: `ui/packages/app-shell/src/utils/workspacePreference.ts`, the codec of `record.related.arrangement` (item 10 cannot land without its version). `RelatedList.tsx`, `RelatedCardMenu.tsx`, `relatedListCompact.ts`, `renderers/record-related-list.tsx` and `index.tsx` are the files the header and fold are drawn in, beside the listed ones. ## Tests New: - `__tests__/RelatedPanel.header-27524.test.tsx` (10): the real `RelatedList` compact face with the stack's chrome. Header anatomy (title, chip, no toggle, no ⤢, 「View all 2」); row, title and chip each open the list; ⠿ + ⋯ at the right end, hover/focus classes, their clicks do not open the list; ⋯ holds Hide only and Hide does not open the list; ⌥↑ / ⌥↓ on the handle, a bare arrow does nothing; the handle is the drag source, the header row is not; the first column on the product page (`Acme renewal`, `Globex pilot`), also when the FK is the only authored column, and on the opportunity page (`Widget`); the arranged full face (the Related tab) draws ⠿ and ⋯, no chevron, and a header click leaves its rows drawn. - `renderers/__tests__/record-related-list.emptyPanels-27524.test.tsx` (4): the line's text and order; 展开 restores the panels in place; stepping to a sibling record in the same view (same column element) folds again; an all-empty column folds. - `synth/__tests__/highlights-27524.test.ts` (3): lead and contact sets minus `full_name`, the cut at six. - `app-shell/src/utils/workspacePreference.relatedArrangement-27524.test.ts` (2): the v2 shape round-trips; a v1 record carrying `collapsed` reads as no choice. Updated, not deleted: `RelatedCardStack-27055` (the collapse case becomes "a panel has no collapse"; moves via the handle), `relatedArrangement-27055` (collapse cases become order + hidden), `RelatedList.compactColumn-26678` (the new header anatomy). <details><summary>Runs and ablation</summary> - Pre-merge head `96652743f2`, under the verify lock: `pnpm --dir ui exec vitest run packages/plugin-detail packages/app-shell/src/utils packages/i18n` → `Test Files 479 passed | 2 skipped (481)`, `Tests 5209 passed | 39 skipped (5248)`. - Merged head `fbf43d1dfa` (main `04740b7c9f` merged once, no conflict; `pnpm install --frozen-lockfile && pnpm build` → 115/115 tasks): the same command → `Tests 1 failed | 5241 passed | 39 skipped (5281)`. The one red was `⋯ holds 隐藏 and nothing else` reading a `RelatedList.tsx` transformed before the commit below landed mid-run (it asserts that commit's class). On the final head `b8c4395be2`: `RelatedPanel.header-27524`, `record-related-list.emptyPanels-27524`, `RelatedList.compactColumn-26678`, `RelatedCardStack-27055` → `Test Files 4 passed (4)`, `Tests 30 passed (30)`. - `type-check` (`tsc --noEmit` + `tsc -p tsconfig.test.json`) for `@object-ui/plugin-detail`, `@object-ui/i18n`, `@object-ui/app-shell` on the merged head: 0 errors each, lock VERDICT command-exit 0. - A bug the screenshots found, fixed in `b8c4395be2`: with ⋯ open the controls faded out, because the pointer and the focus are both inside the portaled menu. The controls now stay drawn while a control holds `data-state=open`; the menu test pins it. - Ablation (`scripts/ablation-replace.mjs`, wrap mode, on `e8a0e9f9fd`): in `RelatedListEmptyFold.tsx`, `if (!panels && !ordered.some((m) => m.empty === false)) return NO_FOLD;` → `if (!ordered.some((m) => m.empty === false)) return NO_FOLD; /* ABLATED_27524 */` (anchor 1 → 0, blob `cfb2543146ea` → `e4a7af3d6923`, marker count 1). Red: `a column whose panels are all empty folds them too` (`Tests 1 failed | 2 passed (3)`); the two cases with a non-empty panel stay green, as they should. Restored: blob equal to HEAD's, `git diff HEAD` empty. The suite imports the renderer from source, so no `dist` is involved. - Contract review 6514756946 (FAIL on `b8c4395be2`), fixed in `8b20d10d4c`. Each pin was ablated (`ablation-replace.mjs`, wrap mode) and restored: - ④-1: 展开 scoped to the record. Ablation `stack?.expandedFor === recordKey` → `stack?.expandedFor != null`. Result: the sibling case is red, `Tests 1 failed | 3 passed (4)`. - ④-2: the first column falls through to the declared fields. Ablation `[...authored, ...declared]` → `(authored.length > 0 ? authored : declared)`. Result: the authored-only case is red, `Tests 1 failed | 9 passed (10)`. - ④-3: the Related tab list no longer collapses. Ablation: `arrangedFull` put back into `isCollapsible`. Result: the full-face case is red, `Tests 1 failed | 9 passed (10)`. - ②-1: the changeset banner names `@object-ui/i18n`, and the marker adds `ui/packages/i18n/src/locales/en.ts#TranslationKeys`. `check-adr-0087-registration --base origin/main`: exit 0, four symbols verified. - On `8b20d10d4c`: `pnpm --dir ui exec vitest run packages/plugin-detail packages/app-shell/src/views/RecordDetailView packages/components/src/__tests__/clickableNonControlCensus-13461.test.ts` under the lock → `Test Files 425 passed | 1 skipped (426)`, `Tests 3635 passed | 18 skipped (3653)`; `@object-ui/plugin-detail` type-check VERDICT command-exit 0; eslint on the four touched files 0 errors. - Proxy preview review 6521198256 (REWORK on `8b20d10d4c`, Target 1: a press on the header row's free space opened nothing), fixed in `e6436ddc12`. The open button carries `transform: none`, so the console's pressed-button `scale(0.96)` no longer makes it the containing block of its stretched `::after`. On `e6436ddc12`, under the lock: `RelatedPanel.header-27524`, `RelatedList.compactColumn-26678`, `RelatedCardStack-27055`, `clickableNonControlCensus-13461` → `Test Files 4 passed (4)`, `Tests 33 passed (33)`; `@object-ui/plugin-detail` type-check VERDICT command-exit 0; eslint on the two touched files 0 errors. Ablation (the `style={{ transform: 'none' }}` line replaced by `/* ABLATED_27920 */` with `sed`, marker count 1): the pin is red (`expected '' to be 'none'`, `Tests 1 failed | 9 passed (10)`), and a live press on the free space (Playwright, 120 ms hold, an app-crm account at 1440) opens nothing. Restored with `git checkout`: `git diff HEAD` empty, and the same press opens `?related=crm_activity.account`. </details> ## Acceptance notes - Screenshots: on `examples/app-crm`, not HotCRM (deviation, as the order says); six shots in `/mnt/project-files/ui-seat2/card-27524/shots/` (project files, not a repository branch), zh, 1440 × 900, on the final head through the console's dev server: `01-account-1440-panels` (three panels with rows, then 「还有 1 个空关联 · 线索列表 · 展开」), `02-panel-header-hover` (›, ⠿ + ⋯), `03-panel-menu-hide-only`, `04-empty-panels-expanded`, `05-header-click-opens-list` (the interim `?related=` face), `06-opportunity-1440` (all three panels empty → one line). The tier, the 时间线 | 关联 control and the folded 动态 card in them are `main`'s and change in PR ② and ④. app-crm has no product object, so Target 11 is shown by its test only. - The panel fold line needs a stack of at least two panels; a single empty panel stays drawn. - The epic seat verifies in the console preview against the boards before ACCEPT. ## Verification <details><summary>Gates (34 derived, all exit 0) and the dogfood run</summary> `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`, each run with its exit code captured before any pipe; `--ran` → `34 derived famil(ies) accounted for`. The run started on the merged head `fbf43d1dfa`; `b8c4395be2` (one class and one comment in `RelatedList.tsx`, one test assertion) landed while it ran, so the gates that read sources after it read the final tree, and the two that could see the change (`check-comment-mask-corpus`, `check-adr-0087-registration --base origin/main`) were run again on `b8c4395be2`: exit 0 both. ``` node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0 node scripts/check-adr-0087-registration.mjs --self-test :: exit 0 node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0 node scripts/check-closing-keyword-parity.mjs :: exit 0 node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0 node scripts/check-comment-mask-corpus.mjs :: exit 0 node scripts/check-empty-changeset.mjs --base origin/main :: exit 0 node scripts/check-empty-changeset.mjs --self-test :: exit 0 node scripts/check-sdui-manifest.mjs :: exit 0 node scripts/check-sdui-manifest.mjs --self-test :: exit 0 node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0 node scripts/release-pending-publish.mjs --self-test :: exit 0 pnpm --dir ui exec vitest run scripts/ :: exit 0 pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0 pnpm check:changeset-gate-self-tests :: exit 0 pnpm check:closed-ceremony-text :: exit 0 pnpm check:cross-package-test-inputs :: exit 0 pnpm check:dispatcher-error-vocabulary :: exit 0 pnpm check:driver-memory-census :: exit 0 pnpm check:engine-double-contract :: exit 0 pnpm check:gitlink-declared :: exit 0 pnpm check:nul-bytes :: exit 0 pnpm check:pm-changeset-deadline-census :: exit 0 pnpm check:published-files :: exit 0 pnpm check:query-options-erasure :: exit 0 pnpm check:refd-timer-probe :: exit 0 pnpm check:skill-identifier-liveness :: exit 0 pnpm check:test-source-alias :: exit 0 pnpm check:tier-file-adoption :: exit 0 pnpm check:type-check-coverage :: exit 0 pnpm check:type-check-debt :: exit 0 (re-run under the verify lock; the first run queued behind the dogfood hold and exited 3, NOT MEASURED) pnpm check:undeclared-dep-imports :: exit 0 pnpm check:watch-hint-literal :: exit 0 pnpm check:where-matcher :: exit 0 pnpm --dir ui run check:i18n-drift :: exit 0 ``` Full dogfood once on the final head, because the title carries `!` (`pnpm --filter @objectstack/dogfood test` under the verify lock, after `pnpm build`): `b8c4395be2` · `VERDICT command-exit 1` · `Test Files 3 failed | 353 passed | 3 skipped (359)` · `Tests 4 failed | 2867 passed | 20 skipped (2891)`. All four are `main`'s, red at this branch's merge base `04740b7c9f` and repaired on `main` after it; this diff touches nothing they read (only `ui/**` and the changeset): - `error-sentences-live.dogfood.test.ts` › `POST /data/OBJECT` and `POST /data/OBJECT/createMany` (expect a code-less hook refusal; the server answers `HOOK_ABORTED` since #27667), and `object-hook-lifecycle-scratch.dogfood.test.ts` › acceptance[6] (same cause). `main` carries the repair: #27878. - `platform-app-object-entry-views.test.ts` › `every object an entry names is declared…` (`sys_exchange_rate`, since #27246). `main` carries the repair: #27873. </details> <sub>Body refreshed 2026-10-30T11:19Z: the runs line for `e6436ddc12` (contract review 6521350798, should-fix).</sub> --- _Generated by [Claude Code](https://claude.ai/code/session_01Utp6CaA9ZdTcj97mzGtHYt)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 4793aa4 commit ad030d8

29 files changed

Lines changed: 1046 additions & 451 deletions
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
'@object-ui/plugin-detail': patch
3+
'@object-ui/i18n': patch
4+
'@object-ui/app-shell': patch
5+
---
6+
7+
feat(plugin-detail)!: the record page's related panels lose collapse — the header opens the whole list, ⠿ + ⋯ on hover, ⋯ holds Hide alone, empty panels fold into one line, and the stored arrangement drops `collapsed`
8+
9+
Clause-②: yes (narrowing)
10+
Metadata-protocol: none
11+
12+
<!-- adr-0087: not-required (runtime-interface-only ui/packages/plugin-detail/src/synth/relatedArrangement.ts#RelatedArrangement, ui/packages/plugin-detail/src/synth/relatedArrangement.ts#ResolvedRelatedArrangement, ui/packages/plugin-detail/src/synth/relatedArrangement.ts#RelatedArrangementEdit, ui/packages/i18n/src/locales/en.ts#TranslationKeys) a reader's per-browser layout preference and its edit vocabulary, and the key type of the console's own translation catalogue: no Zod schema, no spec declaration and no stored metadata reads them, so the compiler is the channel that reaches every caller; the stored preference moves to version 2 and a version-1 record reads as no choice -->
13+
14+
The record page's related column is a stack of same-shaped panels (record page v4).
15+
16+
- **The panel header** is the title, then the count chip. The header row (or the chip) opens the panel's whole list; on hover the title's end shows › and its tooltip reads "View all N". On hover or focus the right end shows ⠿ (drag to reorder; focused, ⌥↑ / ⌥↓ move the panel), + and ⋯, which now holds Hide and nothing else. There is no collapse and no ⤢, and nothing is reserved on the left.
17+
- **Empty panels** (0 rows) fold into one line after the others: "N more empty lists · name · … · Expand". Expand draws them again in their places. The Related tab of an authored page keeps its own line.
18+
- **The compact row's first column** skips the foreign key that points back at the record on the page: a product's line items, whose name field is the product, are named by their next column.
19+
- **The stored arrangement** (`record.related.arrangement`, per reader × object) is `{ v, order, hidden, basedOn, seen }`. `collapsed` is removed with no compatibility read: a reader's earlier record reads as no choice, and the panels show in the default order.
20+
21+
**BREAKING** (`@object-ui/plugin-detail` and `@object-ui/i18n`, TypeScript only): `@object-ui/i18n`'s exported `TranslationKeys` loses the four `detail.relatedCard*` keys named below (a `t()` call with one of them still compiles, its key being a `string`, and reads the key back); and `RelatedArrangement` and `ResolvedRelatedArrangement` no longer carry `collapsed`, `RelatedArrangementEdit` no longer has the `collapse` kind, and `resolveRelatedArrangement` / `editRelatedArrangement` no longer take a last `defaultCollapsed` argument. The only caller is the console's own arranged column. A caller still passing one gets a compile error naming it; the fix is to drop it, since a panel has no collapsed state to set. `RELATED_ARRANGEMENT_RECORD_VERSION` (2) is new.
22+
23+
- The i18n keys `detail.relatedCardMoveUp`, `detail.relatedCardMoveDown`, `detail.relatedCardCollapse` and `detail.relatedCardCount` leave every pack; `detail.relatedPanelViewAll`, `detail.relatedPanelReorder`, `detail.relatedPanelReorderHint` and `detail.relatedEmptyFold` arrive in all ten.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/**
2+
* ObjectUI
3+
* Copyright (c) 2024-present ObjectStack Inc.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
/**
10+
* objectstack#27524, Target 10 — the stored arrangement of the record page's
11+
* related panels is `{ v, order, hidden, basedOn, seen }`. `collapsed` left
12+
* the shape with no compatibility read: a record of the earlier version (the
13+
* one that carried it) is "no choice", and the reader gets the default order
14+
* again. This file runs in the node project (no `window`).
15+
*/
16+
17+
import { describe, it, expect } from 'vitest';
18+
import {
19+
RECORD_RELATED_ARRANGEMENT_PREFERENCE,
20+
objectPreferenceScope,
21+
readWorkspacePreference,
22+
writeWorkspacePreference,
23+
} from './workspacePreference';
24+
25+
const lead = objectPreferenceScope('crm_lead');
26+
const KEY = 'os:pref:record.related.arrangement@object:crm_lead';
27+
28+
const memoryStorage = () => {
29+
const kept = new Map<string, string>();
30+
return {
31+
kept,
32+
getItem: (key: string) => kept.get(key) ?? null,
33+
setItem: (key: string, value: string) => void kept.set(key, value),
34+
};
35+
};
36+
37+
describe('record.related.arrangement — no `collapsed`, no compatibility read (objectstack#27524)', () => {
38+
it('writes { order, hidden, basedOn, seen } and the version, and reads it back', () => {
39+
const storage = memoryStorage();
40+
const value = { order: ['task.lead_id', 'note.parent_id'], hidden: ['call.lead_id'], basedOn: 'abcd1234', seen: { 'task.lead_id': 1 } };
41+
writeWorkspacePreference(RECORD_RELATED_ARRANGEMENT_PREFERENCE, lead, value, storage);
42+
const stored = JSON.parse(storage.kept.get(KEY)!);
43+
expect(Object.keys(stored).sort()).toEqual(['basedOn', 'hidden', 'order', 'seen', 'v']);
44+
expect(stored.v).toBe(2);
45+
expect(readWorkspacePreference(RECORD_RELATED_ARRANGEMENT_PREFERENCE, lead, storage)).toEqual(value);
46+
});
47+
48+
it('reads no choice from a record of the shape that carried `collapsed`', () => {
49+
const earlier = JSON.stringify({ order: ['note.parent_id'], collapsed: ['task.lead_id'], hidden: [], basedOn: 'abcd1234', v: 1 });
50+
const storage = { getItem: (key: string) => (key === KEY ? earlier : null) };
51+
expect(readWorkspacePreference(RECORD_RELATED_ARRANGEMENT_PREFERENCE, lead, storage)).toBeUndefined();
52+
});
53+
});

‎ui/packages/app-shell/src/utils/workspacePreference.ts‎

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import { useCallback, useSyncExternalStore } from 'react';
1010
import {
11+
RELATED_ARRANGEMENT_RECORD_VERSION,
1112
relatedArrangementFromRecord,
1213
relatedArrangementToRecord,
1314
type RelatedArrangement,
@@ -157,18 +158,20 @@ export const RECORD_RELATED_COLUMN_PREFERENCE = defineWorkspacePreference<Prefer
157158
});
158159

159160
/**
160-
* How the reader arranged the record page's related cards, per object
161-
* (scope `object:OBJECT_NAME`, objectstack#27055): `{ v, order, collapsed,
162-
* hidden, basedOn }` — plus `seen`, the first-seen stamps of relationships new
163-
* since the last drag (the 48 h 「新」 badge needs a clock). The value is a
164-
* DIFF against the default order; `resolveRelatedArrangement` in
165-
* `@object-ui/plugin-detail` lays it over the default. With no choice the
166-
* host passes `EMPTY_RELATED_ARRANGEMENT`: the default order, every card open.
161+
* How the reader arranged the record page's related panels, per object
162+
* (scope `object:OBJECT_NAME`, objectstack#27055, objectstack#27524):
163+
* `{ v, order, hidden, basedOn }` — plus `seen`, the first-seen stamps of
164+
* relationships new since the last drag (the 48 h 「新」 badge needs a clock).
165+
* The value is a DIFF against the default order; `resolveRelatedArrangement`
166+
* in `@object-ui/plugin-detail` lays it over the default. With no choice the
167+
* host passes `EMPTY_RELATED_ARRANGEMENT`: the default order, every panel
168+
* shown. A record of an earlier version (version 1 carried `collapsed`) is
169+
* "no choice": no compatibility read.
167170
*/
168171
export const RECORD_RELATED_ARRANGEMENT_PREFERENCE = defineWorkspacePreference<RelatedArrangement>({
169172
name: 'record.related.arrangement',
170173
codec: versionedPreferenceCodec<RelatedArrangement>({
171-
version: 1,
174+
version: RELATED_ARRANGEMENT_RECORD_VERSION,
172175
toRecord: relatedArrangementToRecord,
173176
fromRecord: relatedArrangementFromRecord,
174177
}),

‎ui/packages/i18n/src/locales/ar.ts‎

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1490,19 +1490,25 @@ const ar = {
14901490
owner: "المالك",
14911491
relatedCardMenu: "المزيد لـ {{title}}",
14921492
relatedCardEnlarge: "تكبير",
1493-
relatedCardMoveUp: "نقل لأعلى",
1494-
relatedCardMoveDown: "نقل لأسفل",
14951493
relatedCardExpand: "توسيع",
1496-
relatedCardCollapse: "طي",
14971494
relatedCardHide: "إخفاء",
14981495
relatedCardNew: "جديد",
1499-
relatedCardCount: "السجلات: {{count}} · تكبير",
1500-
relatedCardCount_zero: "السجلات: {{count}} · تكبير",
1501-
relatedCardCount_one: "السجلات: {{count}} · تكبير",
1502-
relatedCardCount_two: "السجلات: {{count}} · تكبير",
1503-
relatedCardCount_few: "السجلات: {{count}} · تكبير",
1504-
relatedCardCount_many: "السجلات: {{count}} · تكبير",
1505-
relatedCardCount_other: "السجلات: {{count}} · تكبير",
1496+
relatedPanelViewAll: "عرض الكل ({{count}})",
1497+
relatedPanelViewAll_zero: "عرض الكل ({{count}})",
1498+
relatedPanelViewAll_one: "عرض الكل ({{count}})",
1499+
relatedPanelViewAll_two: "عرض الكل ({{count}})",
1500+
relatedPanelViewAll_few: "عرض الكل ({{count}})",
1501+
relatedPanelViewAll_many: "عرض الكل ({{count}})",
1502+
relatedPanelViewAll_other: "عرض الكل ({{count}})",
1503+
relatedPanelReorder: "نقل {{title}}",
1504+
relatedPanelReorderHint: "اسحب لإعادة الترتيب · ⌥↑ ⌥↓",
1505+
relatedEmptyFold: "قوائم فارغة أخرى: {{count}}",
1506+
relatedEmptyFold_zero: "قوائم فارغة أخرى: {{count}}",
1507+
relatedEmptyFold_one: "قوائم فارغة أخرى: {{count}}",
1508+
relatedEmptyFold_two: "قوائم فارغة أخرى: {{count}}",
1509+
relatedEmptyFold_few: "قوائم فارغة أخرى: {{count}}",
1510+
relatedEmptyFold_many: "قوائم فارغة أخرى: {{count}}",
1511+
relatedEmptyFold_other: "قوائم فارغة أخرى: {{count}}",
15061512
relatedDefaultChanged: "تم تحديث الترتيب الافتراضي",
15071513
relatedRestoreDefault: "استعادة الافتراضي",
15081514
relatedRestoreDefaultOrder: "استعادة الترتيب الافتراضي",

‎ui/packages/i18n/src/locales/de.ts‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1332,15 +1332,17 @@ const de = {
13321332
owner: "Inhaber",
13331333
relatedCardMenu: "Mehr für {{title}}",
13341334
relatedCardEnlarge: "Vergrößern",
1335-
relatedCardMoveUp: "Nach oben",
1336-
relatedCardMoveDown: "Nach unten",
13371335
relatedCardExpand: "Aufklappen",
1338-
relatedCardCollapse: "Zuklappen",
13391336
relatedCardHide: "Ausblenden",
13401337
relatedCardNew: "Neu",
1341-
relatedCardCount: "{{count}} Datensätze – vergrößern",
1342-
relatedCardCount_one: "{{count}} Datensatz – vergrößern",
1343-
relatedCardCount_other: "{{count}} Datensätze – vergrößern",
1338+
relatedPanelViewAll: "Alle {{count}} anzeigen",
1339+
relatedPanelViewAll_one: "Alle {{count}} anzeigen",
1340+
relatedPanelViewAll_other: "Alle {{count}} anzeigen",
1341+
relatedPanelReorder: "{{title}} verschieben",
1342+
relatedPanelReorderHint: "Zum Sortieren ziehen · ⌥↑ ⌥↓",
1343+
relatedEmptyFold: "{{count}} weitere leere Listen",
1344+
relatedEmptyFold_one: "{{count}} weitere leere Liste",
1345+
relatedEmptyFold_other: "{{count}} weitere leere Listen",
13441346
relatedDefaultChanged: "Standardreihenfolge aktualisiert",
13451347
relatedRestoreDefault: "Standard wiederherstellen",
13461348
relatedRestoreDefaultOrder: "Standardreihenfolge wiederherstellen",

‎ui/packages/i18n/src/locales/en.ts‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1520,15 +1520,17 @@ const en = {
15201520
owner: 'Owner',
15211521
relatedCardMenu: 'More for {{title}}',
15221522
relatedCardEnlarge: 'Enlarge',
1523-
relatedCardMoveUp: 'Move up',
1524-
relatedCardMoveDown: 'Move down',
15251523
relatedCardExpand: 'Expand',
1526-
relatedCardCollapse: 'Collapse',
15271524
relatedCardHide: 'Hide',
15281525
relatedCardNew: 'New',
1529-
relatedCardCount: '{{count}} records — enlarge',
1530-
relatedCardCount_one: '{{count}} record — enlarge',
1531-
relatedCardCount_other: '{{count}} records — enlarge',
1526+
relatedPanelViewAll: 'View all {{count}}',
1527+
relatedPanelViewAll_one: 'View all {{count}}',
1528+
relatedPanelViewAll_other: 'View all {{count}}',
1529+
relatedPanelReorder: 'Reorder {{title}}',
1530+
relatedPanelReorderHint: 'Drag to reorder · ⌥↑ ⌥↓',
1531+
relatedEmptyFold: '{{count}} more empty lists',
1532+
relatedEmptyFold_one: '{{count}} more empty list',
1533+
relatedEmptyFold_other: '{{count}} more empty lists',
15321534
relatedDefaultChanged: 'Default order updated',
15331535
relatedRestoreDefault: 'Restore default',
15341536
relatedRestoreDefaultOrder: 'Restore default order',

‎ui/packages/i18n/src/locales/es.ts‎

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1373,16 +1373,19 @@ const es = {
13731373
owner: "Propietario",
13741374
relatedCardMenu: "Más opciones de {{title}}",
13751375
relatedCardEnlarge: "Ampliar",
1376-
relatedCardMoveUp: "Subir",
1377-
relatedCardMoveDown: "Bajar",
13781376
relatedCardExpand: "Desplegar",
1379-
relatedCardCollapse: "Contraer",
13801377
relatedCardHide: "Ocultar",
13811378
relatedCardNew: "Nuevo",
1382-
relatedCardCount: "{{count}} registros · ampliar",
1383-
relatedCardCount_one: "{{count}} registro · ampliar",
1384-
relatedCardCount_many: "{{count}} de registros · ampliar",
1385-
relatedCardCount_other: "{{count}} registros · ampliar",
1379+
relatedPanelViewAll: "Ver todos ({{count}})",
1380+
relatedPanelViewAll_one: "Ver todos ({{count}})",
1381+
relatedPanelViewAll_many: "Ver todos ({{count}})",
1382+
relatedPanelViewAll_other: "Ver todos ({{count}})",
1383+
relatedPanelReorder: "Mover {{title}}",
1384+
relatedPanelReorderHint: "Arrastra para reordenar · ⌥↑ ⌥↓",
1385+
relatedEmptyFold: "{{count}} listas vacías más",
1386+
relatedEmptyFold_one: "{{count}} lista vacía más",
1387+
relatedEmptyFold_many: "{{count}} listas vacías más",
1388+
relatedEmptyFold_other: "{{count}} listas vacías más",
13861389
relatedDefaultChanged: "Orden predeterminado actualizado",
13871390
relatedRestoreDefault: "Restaurar predeterminado",
13881391
relatedRestoreDefaultOrder: "Restaurar orden predeterminado",

‎ui/packages/i18n/src/locales/fr.ts‎

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1370,16 +1370,19 @@ const fr = {
13701370
owner: "Propriétaire",
13711371
relatedCardMenu: "Plus pour {{title}}",
13721372
relatedCardEnlarge: "Agrandir",
1373-
relatedCardMoveUp: "Monter",
1374-
relatedCardMoveDown: "Descendre",
13751373
relatedCardExpand: "Déplier",
1376-
relatedCardCollapse: "Replier",
13771374
relatedCardHide: "Masquer",
13781375
relatedCardNew: "Nouveau",
1379-
relatedCardCount: "{{count}} enregistrements · agrandir",
1380-
relatedCardCount_one: "{{count}} enregistrement · agrandir",
1381-
relatedCardCount_many: "{{count}} enregistrements · agrandir",
1382-
relatedCardCount_other: "{{count}} enregistrements · agrandir",
1376+
relatedPanelViewAll: "Tout voir ({{count}})",
1377+
relatedPanelViewAll_one: "Tout voir ({{count}})",
1378+
relatedPanelViewAll_many: "Tout voir ({{count}})",
1379+
relatedPanelViewAll_other: "Tout voir ({{count}})",
1380+
relatedPanelReorder: "Déplacer {{title}}",
1381+
relatedPanelReorderHint: "Glisser pour réordonner · ⌥↑ ⌥↓",
1382+
relatedEmptyFold: "{{count}} autres listes vides",
1383+
relatedEmptyFold_one: "{{count}} autre liste vide",
1384+
relatedEmptyFold_many: "{{count}} autres listes vides",
1385+
relatedEmptyFold_other: "{{count}} autres listes vides",
13831386
relatedDefaultChanged: "Ordre par défaut mis à jour",
13841387
relatedRestoreDefault: "Rétablir par défaut",
13851388
relatedRestoreDefaultOrder: "Rétablir l'ordre par défaut",

‎ui/packages/i18n/src/locales/ja.ts‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1175,15 +1175,17 @@ const ja = {
11751175
owner: "所有者",
11761176
relatedCardMenu: "{{title}} のその他の操作",
11771177
relatedCardEnlarge: "拡大",
1178-
relatedCardMoveUp: "上へ移動",
1179-
relatedCardMoveDown: "下へ移動",
11801178
relatedCardExpand: "展開",
1181-
relatedCardCollapse: "折りたたむ",
11821179
relatedCardHide: "非表示",
11831180
relatedCardNew: "新規",
1184-
relatedCardCount: "{{count}} 件 · 拡大",
1185-
relatedCardCount_one: "{{count}} 件 · 拡大",
1186-
relatedCardCount_other: "{{count}} 件 · 拡大",
1181+
relatedPanelViewAll: "{{count}} 件をすべて表示",
1182+
relatedPanelViewAll_one: "{{count}} 件をすべて表示",
1183+
relatedPanelViewAll_other: "{{count}} 件をすべて表示",
1184+
relatedPanelReorder: "{{title}} を並べ替え",
1185+
relatedPanelReorderHint: "ドラッグで並べ替え · ⌥↑ ⌥↓",
1186+
relatedEmptyFold: "空の関連 {{count}} 件",
1187+
relatedEmptyFold_one: "空の関連 {{count}} 件",
1188+
relatedEmptyFold_other: "空の関連 {{count}} 件",
11871189
relatedDefaultChanged: "既定の順序が更新されました",
11881190
relatedRestoreDefault: "既定に戻す",
11891191
relatedRestoreDefaultOrder: "既定の順序に戻す",

‎ui/packages/i18n/src/locales/ko.ts‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1332,15 +1332,17 @@ const ko = {
13321332
owner: "소유자",
13331333
relatedCardMenu: "{{title}} 추가 작업",
13341334
relatedCardEnlarge: "확대",
1335-
relatedCardMoveUp: "위로 이동",
1336-
relatedCardMoveDown: "아래로 이동",
13371335
relatedCardExpand: "펼치기",
1338-
relatedCardCollapse: "접기",
13391336
relatedCardHide: "숨기기",
13401337
relatedCardNew: "새 항목",
1341-
relatedCardCount: "{{count}}개 레코드 · 확대",
1342-
relatedCardCount_one: "{{count}}개 레코드 · 확대",
1343-
relatedCardCount_other: "{{count}}개 레코드 · 확대",
1338+
relatedPanelViewAll: "{{count}}개 모두 보기",
1339+
relatedPanelViewAll_one: "{{count}}개 모두 보기",
1340+
relatedPanelViewAll_other: "{{count}}개 모두 보기",
1341+
relatedPanelReorder: "{{title}} 순서 변경",
1342+
relatedPanelReorderHint: "드래그하여 순서 변경 · ⌥↑ ⌥↓",
1343+
relatedEmptyFold: "빈 관련 항목 {{count}}개",
1344+
relatedEmptyFold_one: "빈 관련 항목 {{count}}개",
1345+
relatedEmptyFold_other: "빈 관련 항목 {{count}}개",
13441346
relatedDefaultChanged: "기본 순서가 업데이트됨",
13451347
relatedRestoreDefault: "기본값 복원",
13461348
relatedRestoreDefaultOrder: "기본 순서 복원",

0 commit comments

Comments
 (0)