Skip to content

Commit cc378e9

Browse files
fix(plugin-list,plugin-grid): list empty state tells no-access and "mine" apart from no records (#25987)
<!-- claude-projects-attribution: {"github_login":"os-steve"} --> _Requested by **Steve** · [project thread](https://claude.ai/code/project/chan_01FdL1XeUyvVNxLreh3F4Qi9?thread=cmsg_01FdL1XeUyvVNxLreh3F4Qi96DHp4dBaqUvVq8hBV3bCkw)_ Fixes #25898 Clause-②: no Metadata-protocol: none ## What changes An empty list chose its copy from the user's query and the view's declared filter alone. Two cases from the card's table were wrong: - **No filter, caller may not create.** A member who may read none of an object's records and holds no create grant was told "Nothing here yet / Create your first record to get started." beside no Create button. Once the permission answer has loaded, a caller without the `listNew` verdict of the affordance-to-grant map (managed-object policy, effective API operations and the caller's create grant, the same verdict the page's own New reads) now gets "No records you can see / There are no records here that you have access to. If you expected to see some, ask your administrator for access." The add-record action is not drawn for that caller, even on a view whose `addRecord` is enabled. - **A "my" view.** When the view's declared filter names the signed-in user (a comparand equal to the caller's id, which is what a host-resolved `{current_user_id}` becomes, or the placeholder itself), the empty state reads "Nothing of yours yet / This view lists only records that are yours, and you don't have any yet." instead of "No records match this view's filter.", with the add-record action only under the create grant. Unchanged: the first-run copy and action for a caller who may create, the user-query copy, the plain view-filter copy, an authored `emptyState` (it still wins), the 403 error panel (its own state, untouched), and every surface with no permission provider mounted (fail-open, as the affordance map is). Where it lands, following the card's measured producers: - `ListEmptyState` (plugin-list, module-internal) makes the decision: two new optional props, `baseFilterNamesCurrentUser` and `mayCreate` (`false` is a known denial, absent is "not known"), and the exported helper `filterNamesCurrentUser`. - `ListView` (grid and gallery visualizations) passes both from the empty-state call site: the resolved declared filter against the session scope's `currentUserId`, and `resolveAffordance('listNew', …)` once `usePermissions().isLoaded`. - `ObjectGallery` (standalone) passes the same two facts. - `ObjectGrid`'s default empty state makes the same decision in its own empty-state region (it draws no add-record action, as before). plugin-grid and plugin-list do not depend on each other, so the filter walk is restated there in a few lines. - `@object-ui/i18n`: `list.ownedNoneTitle`, `list.ownedNoneMessage`, `list.noneVisibleTitle`, `list.noneVisibleMessage` in all ten packs, and in the two in-code English fallback tables (`LIST_DEFAULT_TRANSLATIONS`, ObjectGrid's). - `scripts/sdui-manifest.record.json`: the registry-source hash re-recorded by `node scripts/gen-sdui-manifest-node.mjs`, which every `ui/packages/**` source change requires (`check-sdui-manifest`); the manifest itself is byte-identical. The copy is generic rather than naming the object ("你没有可查看的工单" in the card) so it needs no plural forms across ten locales; the zh pack reads 「没有你可查看的记录」 and 「你还没有负责的记录」. ## Tests At head `8878ebe8` (implementation unchanged since `be788ede`; later commits are the main merge and the manifest record): - New: `ListView.emptyStateAccess-25898.test.tsx` (9), `ObjectGallery.emptyStateAccess-25898.test.tsx` (4), `ObjectGrid.emptyStateAccess-25898.test.tsx` (5), through the real `MePermissionsProvider` and `FilterScopeProvider`: 3 files, 18 tests passed. - Reverse verification: the four source files and the locale packs restored to the base commit `38050ced` with the tests kept: 11 failed / 7 passed, exactly the predicted split (every no-access, "mine" and zh row red; the create-grant, no-provider and plain-filter controls green). Restored from `HEAD` afterwards, `git diff HEAD` empty. - `vitest run packages/plugin-list/ packages/plugin-grid/ packages/i18n/ packages/plugin-form/src/affordanceGrantMap-12082.test.tsx` (the affordance census included): 498 files passed, 5253 tests passed, 13 skipped. - `type-check` for `@object-ui/plugin-list`, `@object-ui/plugin-grid`, `@object-ui/i18n`: clean. - `node scripts/pm/dispatch-gates.mjs --commands`: 47 derived families run locally, all exit 0 (`check-sdui-manifest` exited 1 before the record was regenerated and 0 after); `--ran` reconciliation: 47 accounted, 0 not measured. ## Acceptance notes - The `{current_user_id}` walk is shape-agnostic: any string comparand equal to the caller's id counts, so a filter like `owner = me OR status = open` also reads "Nothing of yours yet". Views of that shape are rare; the copy is still true of the caller's own records. - The toolbar's add-record buttons (top and bottom) still follow `addRecord.enabled` alone inside `ListView`; the console host already withholds `addRecord` without the create grant, so only a non-console host could show them to a caller without the grant. Left out of this card's empty-state scope; noted, not filed. - A shared home for the decision in `@object-ui/core` would let `ObjectGrid` and `ListEmptyState` share one function; core is outside this card's claimed surface, so the grid restates it. --- _Generated by [Claude Code](https://claude.ai/code/session_01RYq63NT6L4eGmQNvE9e7D8)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 6444288 commit cc378e9

19 files changed

Lines changed: 578 additions & 15 deletions
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
'@object-ui/plugin-list': patch
3+
'@object-ui/plugin-grid': patch
4+
'@object-ui/i18n': patch
5+
---
6+
7+
fix(plugin-list,plugin-grid): an empty list tells "no records you can see" and "nothing of yours yet" apart from "no records"
8+
9+
Clause-②: no
10+
Metadata-protocol: none
11+
12+
An empty list chose its copy from the user's query and the view's filter alone. A member who may read none of an object's records and may not create one was told "Nothing here yet / Create your first record to get started." beside no Create button, and every "my …" view (`owner` equals the current user) told someone who owns nothing "No records match this view's filter."
13+
14+
- **No filter, no create grant.** When the permission answer has loaded and the caller does not hold the create grant (the `listNew` row of the affordance-to-grant map, the verdict the page's own New reads), the empty state reads "No records you can see" and asks the reader to contact an administrator. The add-record action is not drawn for that caller, even on a view that enables `addRecord`.
15+
- **A filter naming the signed-in user.** When the view's declared filter compares a field to the caller's own id (or still carries the `{current_user_id}` placeholder), the empty state reads "Nothing of yours yet", with the add-record action only under the create grant.
16+
- **Unchanged.** The first-run copy and action for a caller who may create, the user-query and view-filter copy, an authored `emptyState`, the 403 error panel, and every surface with no permission provider mounted.
17+
18+
`ListView` (grid and gallery), the standalone `ObjectGallery` and `ObjectGrid`'s default empty state decide the same way. `@object-ui/i18n` gains `list.ownedNoneTitle`, `list.ownedNoneMessage`, `list.noneVisibleTitle` and `list.noneVisibleMessage` in all ten locale packs.
19+
20+
What to do: nothing. A view that authors its own `emptyState` keeps it.

‎scripts/sdui-manifest.record.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,6 @@
4949
"ui/packages/types"
5050
],
5151
"registrySources": 922,
52-
"registrySourcesSha256": "0e127be18a1104d857822dc5c71a6380e4f00b0db729cd8b8a230a11c120fe78"
52+
"registrySourcesSha256": "7a4b546825cb499c56c72e11df824357786d9619b78c8835591fbe91a6ae6110"
5353
}
5454
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -843,6 +843,10 @@ const ar = {
843843
noMatches: "لا توجد سجلات مطابقة",
844844
noMatchesMessage: "لا توجد سجلات تطابق عوامل التصفية أو البحث الحالية. حاول تعديلها أو مسحها.",
845845
viewFilterNoMatchesMessage: "لا توجد سجلات تطابق عامل التصفية لطريقة العرض هذه.",
846+
ownedNoneTitle: "لا يوجد شيء يخصك بعد",
847+
ownedNoneMessage: "تعرض طريقة العرض هذه سجلاتك فقط، وليس لديك أي سجل بعد.",
848+
noneVisibleTitle: "لا توجد سجلات يمكنك رؤيتها",
849+
noneVisibleMessage: "لا توجد هنا سجلات لديك صلاحية الوصول إليها. إذا كنت تتوقع رؤية بعضها، فاطلب من المسؤول منحك الوصول.",
846850
loadErrorTitle: "تعذّر تحميل السجلات",
847851
loadErrorMessage: "حدث خطأ أثناء تحميل هذه البيانات. تحقق من اتصالك وحاول مرة أخرى.",
848852
loadErrorForbiddenTitle: "ليس لديك صلاحية الوصول",

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,10 @@ const de = {
753753
noMatches: "Keine passenden Datensätze",
754754
noMatchesMessage: "Keine Datensätze entsprechen Ihren aktuellen Filtern oder Ihrer Suche. Passen Sie sie an oder setzen Sie sie zurück.",
755755
viewFilterNoMatchesMessage: "Keine Datensätze entsprechen dem Filter dieser Ansicht.",
756+
ownedNoneTitle: "Noch nichts von Ihnen",
757+
ownedNoneMessage: "Diese Ansicht listet nur Ihre eigenen Datensätze auf, und Sie haben noch keine.",
758+
noneVisibleTitle: "Keine sichtbaren Datensätze",
759+
noneVisibleMessage: "Hier gibt es keine Datensätze, auf die Sie Zugriff haben. Wenn Sie welche erwarten, bitten Sie Ihren Administrator um Zugriff.",
756760
loadErrorTitle: "Datensätze konnten nicht geladen werden",
757761
loadErrorMessage: "Beim Laden dieser Daten ist etwas schiefgelaufen. Prüfen Sie Ihre Verbindung und versuchen Sie es erneut.",
758762
loadErrorForbiddenTitle: "Kein Zugriff",

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,6 +1036,10 @@ const en = {
10361036
noMatches: 'No matching records',
10371037
noMatchesMessage: 'No records match your current filters or search. Try adjusting or clearing them.',
10381038
viewFilterNoMatchesMessage: 'No records match this view’s filter.',
1039+
ownedNoneTitle: 'Nothing of yours yet',
1040+
ownedNoneMessage: 'This view lists only records that are yours, and you don’t have any yet.',
1041+
noneVisibleTitle: 'No records you can see',
1042+
noneVisibleMessage: 'There are no records here that you have access to. If you expected to see some, ask your administrator for access.',
10391043
loadErrorTitle: 'Couldn\u2019t load records',
10401044
loadErrorMessage: 'Something went wrong while loading this data. Check your connection and try again.',
10411045
loadErrorForbiddenTitle: 'You don\u2019t have access',

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,10 @@ const es = {
779779
noMatches: "No hay registros coincidentes",
780780
noMatchesMessage: "Ningún registro coincide con sus filtros o su búsqueda actuales. Pruebe a ajustarlos o borrarlos.",
781781
viewFilterNoMatchesMessage: "Ningún registro coincide con el filtro de esta vista.",
782+
ownedNoneTitle: "Aún no tiene nada propio",
783+
ownedNoneMessage: "Esta vista solo muestra sus propios registros, y todavía no tiene ninguno.",
784+
noneVisibleTitle: "No hay registros que pueda ver",
785+
noneVisibleMessage: "Aquí no hay registros a los que tenga acceso. Si esperaba ver alguno, pida acceso a su administrador.",
782786
loadErrorTitle: "No se pudieron cargar los registros",
783787
loadErrorMessage: "Algo salió mal al cargar estos datos. Compruebe su conexión y vuelva a intentarlo.",
784788
loadErrorForbiddenTitle: "No tiene acceso",

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -774,6 +774,10 @@ const fr = {
774774
noMatches: "Aucun enregistrement correspondant",
775775
noMatchesMessage: "Aucun enregistrement ne correspond à vos filtres ou à votre recherche. Essayez de les ajuster ou de les effacer.",
776776
viewFilterNoMatchesMessage: "Aucun enregistrement ne correspond au filtre de cette vue.",
777+
ownedNoneTitle: "Rien à vous pour l'instant",
778+
ownedNoneMessage: "Cette vue ne liste que vos propres enregistrements, et vous n'en avez encore aucun.",
779+
noneVisibleTitle: "Aucun enregistrement visible",
780+
noneVisibleMessage: "Il n'y a ici aucun enregistrement auquel vous avez accès. Si vous pensiez en voir, demandez l'accès à votre administrateur.",
777781
loadErrorTitle: "Impossible de charger les enregistrements",
778782
loadErrorMessage: "Une erreur s'est produite lors du chargement de ces données. Vérifiez votre connexion et réessayez.",
779783
loadErrorForbiddenTitle: "Accès refusé",

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,10 @@ const ja = {
753753
noMatches: "該当するレコードがありません",
754754
noMatchesMessage: "現在のフィルターや検索条件に一致するレコードはありません。条件を調整するか解除してください。",
755755
viewFilterNoMatchesMessage: "このビューのフィルターに一致するレコードはありません。",
756+
ownedNoneTitle: "あなたのレコードはまだありません",
757+
ownedNoneMessage: "このビューにはあなたのレコードだけが表示されますが、まだ1件もありません。",
758+
noneVisibleTitle: "表示できるレコードがありません",
759+
noneVisibleMessage: "ここにはあなたがアクセスできるレコードがありません。表示されるはずの場合は、管理者にアクセス権を依頼してください。",
756760
loadErrorTitle: "レコードを読み込めませんでした",
757761
loadErrorMessage: "データの読み込み中に問題が発生しました。接続を確認して再試行してください。",
758762
loadErrorForbiddenTitle: "アクセス権がありません",

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,10 @@ const ko = {
753753
noMatches: "일치하는 레코드가 없습니다",
754754
noMatchesMessage: "현재 필터나 검색어와 일치하는 레코드가 없습니다. 조건을 조정하거나 지워 보세요.",
755755
viewFilterNoMatchesMessage: "이 뷰의 필터와 일치하는 레코드가 없습니다.",
756+
ownedNoneTitle: "아직 내 레코드가 없습니다",
757+
ownedNoneMessage: "이 뷰에는 내 레코드만 표시되며, 아직 하나도 없습니다.",
758+
noneVisibleTitle: "볼 수 있는 레코드가 없습니다",
759+
noneVisibleMessage: "여기에는 접근 권한이 있는 레코드가 없습니다. 레코드가 보여야 한다면 관리자에게 접근 권한을 요청하세요.",
756760
loadErrorTitle: "레코드를 불러오지 못했습니다",
757761
loadErrorMessage: "이 데이터를 불러오는 중 문제가 발생했습니다. 연결을 확인하고 다시 시도하세요.",
758762
loadErrorForbiddenTitle: "접근 권한이 없습니다",

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -774,6 +774,10 @@ const pt = {
774774
noMatches: "Nenhum registro correspondente",
775775
noMatchesMessage: "Nenhum registro corresponde aos seus filtros ou à sua busca atuais. Tente ajustá-los ou limpá-los.",
776776
viewFilterNoMatchesMessage: "Nenhum registro corresponde ao filtro desta visualização.",
777+
ownedNoneTitle: "Ainda não há nada seu",
778+
ownedNoneMessage: "Esta visualização lista apenas os seus registros, e você ainda não tem nenhum.",
779+
noneVisibleTitle: "Nenhum registro que você possa ver",
780+
noneVisibleMessage: "Não há registros aqui aos quais você tenha acesso. Se esperava ver algum, peça acesso ao seu administrador.",
777781
loadErrorTitle: "Não foi possível carregar os registros",
778782
loadErrorMessage: "Algo deu errado ao carregar estes dados. Verifique sua conexão e tente novamente.",
779783
loadErrorForbiddenTitle: "Você não tem acesso",

0 commit comments

Comments
 (0)