Skip to content

Commit 6ada509

Browse files
fix(plugin-detail): the record strip edits in at most three columns so a lookup keeps its name (#29055)
<!-- claude-projects-attribution: {"github_login":"zhuangjianguo"} --> _Requested by **Steedos** · [project thread](https://claude.ai/code/project/chan_018ETKaNwMZCNRYD7D8Q9Ubb?thread=cmsg_018ETKaNwMZCNRYD7D8Q9UbbUmit7WGQvTCFouBXLMxAWN)_ Fixes #28913 Clause-②: no Metadata-protocol: none ## 维护者速读 - **改了什么**:默认记录页的要点条(highlight strip)进入编辑态时,网格最多 3 列;6 个字段排成两行、每行 3 个。阅读态不变,宽容器下仍是一行 6 列。编辑态仍画出空字段,方便就地填写。 - **为什么改**:编辑态以前也是一行 6 列,在 1440 px 的最大化窗格里每格只有 168 px,查找字段(lookup)的选择器旁边还有清除和浏览按钮,「Initech」只剩 10 px,显示成「I…」,负责人「Dev Admin」显示成「Dev A…」。 - **风险与回滚**:低。只改一个 `className` 分支,不加键、不加选项、不加 prop;保存与取消路径不动。回滚即回退本 PR。 - **先看**:`ui/packages/plugin-detail/src/HeaderHighlight.tsx`(网格 `className` 的 `!editing && '@3xl:grid-cols-6'`);`ui/packages/plugin-detail/src/__tests__/HeaderHighlight.editGridCap-28913.test.tsx`(阅读 6 列、编辑最多 3 列、取消回到 6 列)。 - **两分钟验证**:`pnpm --dir ui exec vitest run packages/plugin-detail/src/__tests__/HeaderHighlight.editGridCap-28913.test.tsx`,预期 3 个用例全部通过。 - **需要你做的**:无,席位落地。 ## What changes On the default record page the highlight strip is an equal-width grid: two columns on a narrow strip, three from `@lg`, six from `@3xl`. Edit mode draws every declared strip field, empty ones included, so they can be filled in place. With six declared fields the edit face therefore put six editors in one row. The grid now adds `@3xl:grid-cols-6` only while reading. While editing it stops at three columns, so six fields edit in two rows of three. The narrower steps (`grid-cols-2`, `@lg:grid-cols-3`) are the read face's and are unchanged, and so is the wrapping flex row used outside the default page. No key, option or prop is added. ### Three columns, measured against four Measured in the real Console (`examples/app-crm` backend, Vite console on this branch's source), on a lead with six strip fields declared locally for the shot only (`company`, `email`, `phone`, `source`, `assigned_to`, `account`; not committed). Text widths are the rendered name span; "full" means the span's `scrollWidth` fits its `clientWidth`. | Layout | Grid width | main (6 max) | cap 4 | cap 3 | | --- | --- | --- | --- | --- | | Maximized pane, 1440 px viewport | 1088 px | 6 cols, cell 168 px: `assigned_to` "Dev Admin" cut to 52 px, `account` "Initech" cut to 10 px | 4 cols, cell 260 px: both names full | 3 cols, cell 352 px: both names full | | Same, account renamed "Initech Global Holdings" (149 px) | 1088 px | not measured | name cut to 102 px | name full (149 px) | | Pane, 1440 px viewport (the pane is about 720 px wide) | 623 px | 3 cols, cell 197 px | 3 cols (same) | 3 cols (same) | At four columns the account picker has about 102 px for the name beside its clear and browse buttons; at three it has about 194 px. Both caps clear the seed data, but only three shows a 23-letter company name whole, so the cap is **3**. In every reading the buttons neither overlap the trigger nor spill out of the cell, and every name span carries the full name as its `title`. Screenshots (before and after, both layouts, plus the long-name pair) are on the card's dispatch thread; the seat links them. ## Tests - `HeaderHighlight.editGridCap-28913.test.tsx` (new), on the default page inside an `InlineEditProvider`: the read face resolves to at most six columns and draws only the four fields with a value; after entering edit, every `grid-cols-N` class on the grid resolves to at most three, `grid-cols-6` is absent, the 2 and 3 steps remain, and all six fields draw; cancelling returns to six. - The existing read-face pin in `recordPageV3Face-27055.test.tsx` (`@3xl:grid-cols-6`) is unchanged and green. ## Acceptance notes - **Save and cancel behave as today**, checked in the real Console in the maximized pane: read 6 columns, edit 3, cancel back to 6; editing the company and pressing Save persisted the value (read back through the data API) and the strip returned to 6 columns. - **The pane (about 720 px) is unchanged by this PR.** Its 623 px strip is below `@3xl`, so main, cap 4 and cap 3 all draw three columns there. In that layout the `account` picker shows "Init…" (39 of 43 px) with "Initech" as the span's `title`, which meets the card's "truncates with the name available on hover"; `assigned_to` shows "Dev Admin" whole. A wider cell there would need fewer than three columns, outside the 3-or-4 choice this card measured. - A 720 px viewport is the phone layout, whose strip carries a different field set (no `account`); `assigned_to` showed whole there before and after. - Whether a double-click should put the whole record into edit mode is out of scope, per the card. ## Verification Branch base `135054eb`; readings on `3e7948b2` unless named. Head `4ea64784` merges `origin/main` (16 commits, none touching `HeaderHighlight.tsx`); there the four strip test files (`HeaderHighlight.editGridCap-28913`, `recordPageV3Face-27055`, `HeaderHighlight.editable`, and main's new `RecordMetaFooter.drawnStrip-28909`) re-ran 31/31 passed, and `check-sdui-manifest` exit 0. <details><summary>Tests</summary> - `pnpm --dir ui exec vitest run packages/plugin-detail/` (under the verify lock, `--maxWorkers=2`): 363 files passed, 1 skipped; 3209 tests passed, 18 skipped. - Ablation (one-time, not kept): `scripts/ablation-replace.mjs` replaced `!editing && '@3xl:grid-cols-6',` with `(editing || true) && '@3xl:grid-cols-6',`; the new file went 1 failed / 2 passed (the edit-face case), and the restore leg read blob == HEAD with an empty `git diff HEAD`. </details> <details><summary>Type-check and lint</summary> - `pnpm --filter @object-ui/plugin-detail run type-check`: exit 0. - eslint (`--no-inline-config`, from `ui/`) on both changed source and test files: exit 0. </details> <details><summary>Derived gates</summary> `node scripts/pm/dispatch-gates.mjs --ran`: 34 derived families accounted for, 34 run, 0 NOT MEASURED, every one exit 0. That includes `node scripts/check-sdui-manifest.mjs` and its `--self-test`, `pnpm --dir ui exec vitest run scripts/` (169 files passed, 2 skipped; 4900 tests passed) and `pnpm check:type-check-debt` under the verify lock. The PR-body gates (`check-changeset-no-major`, `check-closing-keyword-parity`, `check-adr-0087-registration`, `check-empty-changeset`) ran on this body. `pnpm check:new-line-citations` under `ui/`: 0 new citations. </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01QsR2jU1A8SyDJ4Lxe7A9Kd --- _Generated by [Claude Code](https://claude.ai/code/session_01QsR2jU1A8SyDJ4Lxe7A9Kd)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent b889c40 commit 6ada509

3 files changed

Lines changed: 103 additions & 1 deletion

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@object-ui/plugin-detail': patch
3+
---
4+
5+
fix(plugin-detail): the record page's highlight strip edits in at most three columns, so a lookup keeps its chosen name
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
On the default record page, entering edit mode draws every highlight field, empty ones included, so they can be filled in place. With six declared fields the strip put all six editors in one row, and a lookup picker was left showing one letter of the chosen name beside its buttons. The edit face now caps the grid at three columns, so six fields wrap to two rows of three. The read face is unchanged and still shows up to six in a row. No key, option or prop changes.

‎ui/packages/plugin-detail/src/HeaderHighlight.tsx‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,15 @@ export const HeaderHighlight: React.FC<HeaderHighlightProps> = ({
124124
>
125125
<div
126126
data-highlights-face={gridFace ? 'grid' : undefined}
127-
className={gridFace ? 'grid grid-cols-2 gap-x-4 gap-y-3 @lg:grid-cols-3 @3xl:grid-cols-6' : cn(
127+
// The edit face stops at three columns: an editor needs the room a
128+
// value does not, a lookup's picker drawing the chosen name beside
129+
// its clear and browse buttons, and six cells in one row leave it a
130+
// letter of the name. Six fields wrap to two rows of three instead.
131+
// Only the read face widens to six in one row.
132+
className={gridFace ? cn(
133+
'grid grid-cols-2 gap-x-4 gap-y-3 @lg:grid-cols-3',
134+
!editing && '@3xl:grid-cols-6',
135+
) : cn(
128136
'flex flex-wrap gap-y-4',
129137
// A chip's left border separates it from the chip BEFORE it on its
130138
// line (objectui#12523). The first chip of every WRAPPED line used
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
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#28913 — on the default record page the highlight strip's grid
11+
* caps its EDIT face at three columns, so an inline editor (a lookup picker
12+
* above all, which draws the chosen name beside its own buttons) keeps a
13+
* usable width and six fields wrap to two rows of three. The read face keeps its
14+
* six columns, and the edit face still draws the empty fields, which must
15+
* stay fillable.
16+
*/
17+
18+
import React from 'react';
19+
import { describe, it, expect, afterEach } from 'vitest';
20+
import { render, screen, cleanup, fireEvent } from '@testing-library/react';
21+
import { InlineEditProvider, useInlineEdit } from '@object-ui/react';
22+
import { HeaderHighlight } from '../HeaderHighlight';
23+
import { DefaultRecordPageContext } from '../defaultRecordPage';
24+
25+
afterEach(cleanup);
26+
27+
const fields = ['a', 'b', 'c', 'd', 'e', 'f'].map((name) => ({ name, label: name.toUpperCase() }));
28+
// Two of the six are empty: the read face hides them, the edit face draws them.
29+
const data = { a: 'one', b: 'two', c: 'three', d: 'four', e: '', f: null };
30+
const objectSchema = { fields: Object.fromEntries(fields.map((f) => [f.name, { type: 'text' }])) };
31+
32+
function EditSession() {
33+
const inline = useInlineEdit();
34+
return (
35+
<>
36+
<button type="button" onClick={() => inline!.enter('a')}>enter-edit</button>
37+
<button type="button" onClick={() => inline!.cancel()}>cancel-edit</button>
38+
</>
39+
);
40+
}
41+
42+
const renderStrip = () =>
43+
render(
44+
<DefaultRecordPageContext.Provider value>
45+
<InlineEditProvider canEdit>
46+
<HeaderHighlight fields={fields} data={data} objectSchema={objectSchema} />
47+
<EditSession />
48+
</InlineEditProvider>
49+
</DefaultRecordPageContext.Provider>,
50+
);
51+
52+
const grid = () => document.querySelector('[data-highlights-face="grid"]') as HTMLElement;
53+
54+
/** Every column count the grid's classes can resolve to, at any container width. */
55+
const columnCounts = (el: HTMLElement) =>
56+
[...el.className.matchAll(/(?:^|\s)(?:@[a-z0-9]+:)?grid-cols-(\d+)(?=\s|$)/g)].map((m) => Number(m[1]));
57+
58+
describe('HeaderHighlight — the edit face caps the grid at three columns (objectstack#28913)', () => {
59+
it('reads in up to six columns, drawing only the fields with a value', () => {
60+
renderStrip();
61+
expect(grid().className).toContain('@3xl:grid-cols-6');
62+
expect(Math.max(...columnCounts(grid()))).toBe(6);
63+
expect(grid().children).toHaveLength(4);
64+
});
65+
66+
it('edits in at most three columns, drawing all six fields so they wrap to a second row', () => {
67+
renderStrip();
68+
fireEvent.click(screen.getByText('enter-edit'));
69+
const counts = columnCounts(grid());
70+
expect(Math.max(...counts)).toBe(3);
71+
expect(grid().className).not.toContain('grid-cols-6');
72+
// The narrower steps are the read face's, unchanged.
73+
expect(counts).toEqual(expect.arrayContaining([2, 3]));
74+
expect(grid().children).toHaveLength(6);
75+
});
76+
77+
it('goes back to six columns when the edit is cancelled', () => {
78+
renderStrip();
79+
fireEvent.click(screen.getByText('enter-edit'));
80+
fireEvent.click(screen.getByText('cancel-edit'));
81+
expect(Math.max(...columnCounts(grid()))).toBe(6);
82+
expect(grid().children).toHaveLength(4);
83+
});
84+
});

0 commit comments

Comments
 (0)