Skip to content

Commit 04148ac

Browse files
fix(app-shell): show the position's description in the Setup → Positions list (#26100)
Fixes #25909 Clause-②: no Metadata-protocol: none ## What Setup → Positions now has a **Description** column. The position list in `ui/packages/app-shell/src/services/builtinComponents.tsx` declared only Name, Label, Holders and Permission sets, so a position's `description`, which `GET /meta/position` already serves, was never shown. The column is `{ key: 'description', label: 'Description' }`, placed right after Label, with no width (it takes the remaining width, like the permission-set list's Description column) and no custom render. The header localizes through the existing `engine.list.col.description` key (en "Description", zh "描述"). A position with no description renders the shared empty cell (`defaultCell` gives `EmptyValue`, an em dash). The text is the served one. The server overlays `positions.NAME.description` from the translation bundle onto every served position document (`translatePosition`, `packages/spec/src/system/i18n-resolver.ts`), so a translated description shows translated, like the label. The console adds no lookup of its own. ## Tests (head `7a62f956`) `ResourceListPage.positionGrants-25333.test.tsx` gives `sales_manager` a description and leaves `support_agent` and `auditor` without one. The new case asserts: - the localized Description header, with Label right before it and Holders right after it; - the served text in `sales_manager`'s Description cell; - the shared empty cell (`data-slot="empty-value"`, text exactly the em dash) for the two positions without one. Red first: the test commit `18603827` ran against the unchanged column list and failed with `Unable to find role="columnheader" and name "Description"` (Tests 1 failed, 5 passed). With the fix (`7a62f956`): Test Files 1 passed, Tests 6 passed. - `cd ui && pnpm exec vitest run --maxWorkers=2` over the 17 app-shell test files that import `builtinComponents` or render a ResourceListPage with positions: Test Files 17 passed, Tests 135 passed. - `pnpm --filter @object-ui/app-shell run type-check` (both `tsconfig.json` and `tsconfig.test.json`, so the test file is compiled): exit 0. - `node scripts/check-sdui-manifest.mjs`: exit 0, manifest intact; `scripts/sdui-manifest.record.json` untouched. - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at `7a62f956`: 34 commands, all exit 0; `--ran` reconciliation: 34 derived, 34 run, 0 NOT-MEASURED (a derived zero, every exit code recorded). `node scripts/check-changeset-fixed.mjs` (the roster under `.changeset`): exit 0. - The full app-shell suite (1730 test files) was not run locally; CI's `Test Core` / UI lanes run it. ## Acceptance notes - NOT MEASURED in a browser: the column was checked in the jsdom render only. The translated description path is the server's overlay, read from source, not measured end to end here. - No width or truncation on the column, matching the permission-set list. A very long description wraps inside the row, as it already does on that list. --- _Generated by [Claude Code](https://claude.ai/code/session_01SMps7wq7rBTk53e8KTSEup)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 31f9b87 commit 04148ac

3 files changed

Lines changed: 42 additions & 2 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
---
4+
5+
fix(app-shell): the Setup → Positions list shows each position's description
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The position list in Setup declared only Name, Label, Holders and Permission sets, so a position's `description`, which `GET /meta/position` already serves, was never shown. The list now has a Description column right after Label, the same column the permission-set list next to it has. A position without a description shows the empty cell. The text is the served one, so a description the translation bundle's `positions` namespace translates shows translated, like the label.
11+
12+
What to do: nothing.

‎ui/packages/app-shell/src/services/builtinComponents.tsx‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,14 +211,16 @@ registerMetadataResource({
211211

212212
// objectstack#25333 — the position list says who holds each position and which
213213
// permission sets it grants, both served by the server (`?include=grants`) from
214-
// the reader the authorization resolver enforces with.
214+
// the reader the authorization resolver enforces with. objectstack#25909 — and
215+
// the position's served `description`, the permission-set list's column.
215216
registerMetadataResource({
216217
type: 'position',
217218
domain: 'security',
218219
listInclude: 'grants',
219220
listColumns: [
220221
{ key: 'name', label: 'Name', width: '20%' },
221222
{ key: 'label', label: 'Label', width: '20%' },
223+
{ key: 'description', label: 'Description' },
222224
{
223225
key: '_holderCount',
224226
label: 'Holders',

‎ui/packages/app-shell/src/views/metadata-admin/ResourceListPage.positionGrants-25333.test.tsx‎

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@
1818
* - the granted sets in served order, the same-named set the position-name
1919
* fold grants marked "by name" (objectstack#24757), a declared set unmarked;
2020
* - a click anywhere on the row opens the position; a click on the Active
21-
* switch is the switch's, and a modified click is the browser's.
21+
* switch is the switch's, and a modified click is the browser's;
22+
* - the served `description` shows in its own column right after Label, under
23+
* the localized header, and a position without one shows the empty cell
24+
* (objectstack#25909).
2225
*/
2326
import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest';
2427
import { render, cleanup, screen, waitFor, fireEvent, within } from '@testing-library/react';
@@ -33,6 +36,7 @@ const POSITIONS: Record<string, unknown>[] = [
3336
{
3437
name: 'sales_manager',
3538
label: 'Sales Manager',
39+
description: 'Leads the regional sales team and approves its forecasts.',
3640
_holderCount: 3,
3741
_grantedPermissionSets: [
3842
{ name: 'crm_sales_user', via: 'definition' },
@@ -180,6 +184,28 @@ describe('the position list reads its two columns from the server (objectstack#2
180184
});
181185
});
182186

187+
describe('the position list shows the served description (objectstack#25909)', () => {
188+
it('a Description column right after Label shows the served text, and the empty cell where none is served', async () => {
189+
mount();
190+
const header = await screen.findByRole('columnheader', { name: t('engine.list.col.description', LOCALE) });
191+
const headers = screen.getAllByRole('columnheader');
192+
const col = headers.indexOf(header);
193+
expect(headers[col - 1]).toHaveTextContent(t('engine.list.col.label', LOCALE));
194+
expect(headers[col + 1]).toHaveTextContent(t('engine.list.col._holderCount', LOCALE));
195+
196+
const cell = (name: string) => screen.getByTestId(`metadata-row-${name}`).querySelectorAll('td')[col] as HTMLElement;
197+
await waitFor(() =>
198+
expect(cell('sales_manager')).toHaveTextContent('Leads the regional sales team and approves its forecasts.'),
199+
);
200+
// No description served: the shared empty cell, not a blank and not the
201+
// neighbour's text.
202+
for (const name of ['support_agent', 'auditor']) {
203+
expect(cell(name).querySelector('[data-slot="empty-value"]')).not.toBeNull();
204+
expect(cell(name)).toHaveTextContent(/^—$/);
205+
}
206+
});
207+
});
208+
183209
describe('the whole row opens the position (objectstack#25333)', () => {
184210
it('a click on a plain cell opens the position, keeping the scope', async () => {
185211
mount();

0 commit comments

Comments
 (0)