@ai-matrx/records 0.72.24 → 0.73.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/dist/list/index.cjs +221 -0
- package/dist/list/index.cjs.map +1 -0
- package/dist/list/index.d.cts +395 -0
- package/dist/list/index.d.ts +395 -0
- package/dist/list/index.js +198 -0
- package/dist/list/index.js.map +1 -0
- package/package.json +12 -1
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import { ArchiveFilterValue } from '@ai-matrx/design-system';
|
|
2
|
+
|
|
3
|
+
type ListScopeKind = "all" | "mine" | "team" | "orgs" | "shared" | "industry" | "public" | "system" | "platform_orgs" | "platform_users" | "platform_all";
|
|
4
|
+
type ListScope =
|
|
5
|
+
/** ALL: Mine ∪ My team ∪ My Orgs ∪ Shared, one row per record. The default lane. */
|
|
6
|
+
{
|
|
7
|
+
kind: "all";
|
|
8
|
+
} | {
|
|
9
|
+
kind: "mine";
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* MY TEAM (access ladder: "my team or department"). Rows in an organization I
|
|
13
|
+
* belong to, made by someone who shares a live team with me THERE — me
|
|
14
|
+
* included where I am on a team. A person on no team sees NOTHING here (and
|
|
15
|
+
* the list says so) — never a copy of Mine (2026-09-28). A team is a
|
|
16
|
+
* list filter, never an access boundary: every row here is one "orgs" would
|
|
17
|
+
* also show. Server reach: `iam.my_team_reach(p_org_id)` (teams FEATURE.md).
|
|
18
|
+
* Narrowing to one organization is the organization filter's job (`orgId`).
|
|
19
|
+
*/
|
|
20
|
+
| {
|
|
21
|
+
kind: "team";
|
|
22
|
+
}
|
|
23
|
+
/** Every organization I belong to; one organization = the organization filter. */
|
|
24
|
+
| {
|
|
25
|
+
kind: "orgs";
|
|
26
|
+
} | {
|
|
27
|
+
kind: "shared";
|
|
28
|
+
}
|
|
29
|
+
/** `industryId: null` = blended across every industry my orgs have attached. */
|
|
30
|
+
| {
|
|
31
|
+
kind: "industry";
|
|
32
|
+
industryId: string | null;
|
|
33
|
+
} | {
|
|
34
|
+
kind: "public";
|
|
35
|
+
}
|
|
36
|
+
/** Platform-published records; the RPC decides what each viewer reads. */
|
|
37
|
+
| {
|
|
38
|
+
kind: "system";
|
|
39
|
+
}
|
|
40
|
+
/** ADMIN: every organization's records. `organizationId` narrows to one. */
|
|
41
|
+
| {
|
|
42
|
+
kind: "platform_orgs";
|
|
43
|
+
organizationId: string | null;
|
|
44
|
+
}
|
|
45
|
+
/** ADMIN: every person's own records. `organizationId` = one person's personal org. */
|
|
46
|
+
| {
|
|
47
|
+
kind: "platform_users";
|
|
48
|
+
organizationId: string | null;
|
|
49
|
+
}
|
|
50
|
+
/** ADMIN: the whole platform corpus. */
|
|
51
|
+
| {
|
|
52
|
+
kind: "platform_all";
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* The scopes an admin SUPPORT route declares (…/support, e.g. Mandate support
|
|
56
|
+
* lookup) — never on a management page, never a personal-seat scope.
|
|
57
|
+
*/
|
|
58
|
+
declare const ADMIN_SUPPORT_LIST_SCOPES: ListScopeKind[];
|
|
59
|
+
/** The personal-seat scopes: legal on user pages, banned on admin pages. */
|
|
60
|
+
declare const PERSONAL_SEAT_SCOPES: readonly ListScopeKind[];
|
|
61
|
+
/** The lanes the All lane unions. */
|
|
62
|
+
declare const ALL_LANE_MEMBERS: readonly ListScopeKind[];
|
|
63
|
+
/** Every user list opens on All unless its page or knob says otherwise. */
|
|
64
|
+
declare const DEFAULT_LIST_SCOPE: ListScope;
|
|
65
|
+
/**
|
|
66
|
+
* The vocabulary as a runtime value — for validating a scope string that
|
|
67
|
+
* arrives from outside TypeScript (a counts RPC row, a URL parameter).
|
|
68
|
+
*
|
|
69
|
+
* Every such check reads THIS. A hand-listed subset at a call site is how the
|
|
70
|
+
* `system` scope came back from the server with a real total and was silently
|
|
71
|
+
* dropped on the way to its tab.
|
|
72
|
+
*/
|
|
73
|
+
declare const LIST_SCOPE_KINDS: readonly ListScopeKind[];
|
|
74
|
+
declare function isMineScope(scope: ListScope): scope is Extract<ListScope, {
|
|
75
|
+
kind: "mine";
|
|
76
|
+
}>;
|
|
77
|
+
declare function isTeamScope(scope: ListScope): scope is Extract<ListScope, {
|
|
78
|
+
kind: "team";
|
|
79
|
+
}>;
|
|
80
|
+
/** The lanes a type can structurally be unable to hold (All and Mine always exist). */
|
|
81
|
+
type DeclarableLane = "team" | "orgs" | "shared" | "industry" | "public" | "system";
|
|
82
|
+
/**
|
|
83
|
+
* THE ONE DECLARED STATEMENT of which lanes a surface's type cannot hold
|
|
84
|
+
* (`EntityListConfig.lanes`, `EntityScopeTabs lanes`): `{ public: false }` = this type has no
|
|
85
|
+
* publish path, so Public would only ever say 0. A lane that is possible but empty for this
|
|
86
|
+
* person is NOT declared: it stays and shows an honest 0. Omitted = every lane the surface's
|
|
87
|
+
* `scopes` names is offered (and the shell adds My team beside My Orgs).
|
|
88
|
+
*
|
|
89
|
+
* The facts come from the type, never from who is looking: team needs a team concept on its
|
|
90
|
+
* records, shared needs a `platform.shareable_resource_registry` row, public needs a publish path
|
|
91
|
+
* (`is_link_shareable`, or a public-card path as agents and workflows have), system needs a
|
|
92
|
+
* platform corpus of that type.
|
|
93
|
+
*/
|
|
94
|
+
type LaneSupport = Partial<Record<DeclarableLane, false>>;
|
|
95
|
+
/** What a surface declares about the standard lanes (`EntityListConfig.lanes`). */
|
|
96
|
+
interface StandardLanesOptions {
|
|
97
|
+
lanes?: LaneSupport;
|
|
98
|
+
}
|
|
99
|
+
/** The lanes a surface declared it cannot hold. */
|
|
100
|
+
declare function declaredAbsentLanes(lanes: LaneSupport | undefined): ListScopeKind[];
|
|
101
|
+
/**
|
|
102
|
+
* THE ONE PLACE "My team" joins a list (T-29, 2026-09-27). Every surface that
|
|
103
|
+
* offers "My Orgs" offers "My team" beside it — never declared per page. Its
|
|
104
|
+
* `*_list_scoped` RPC answers `p_scope = 'team'` through `iam.my_team_reach`,
|
|
105
|
+
* and its counts RPC returns a `team` row; a surface whose RPC cannot is a
|
|
106
|
+
* defect in that RPC, not a reason to hide the tab here.
|
|
107
|
+
*
|
|
108
|
+
* Inserted directly after "mine" (the ladder reads narrow → wide: only me, my
|
|
109
|
+
* team, my organization). Idempotent; a list without "orgs" is untouched —
|
|
110
|
+
* an admin page, or a Private type, has no team question to answer.
|
|
111
|
+
*/
|
|
112
|
+
declare function withTeamScope(scopes: readonly ListScopeKind[], lanes?: StandardLanesOptions): ListScopeKind[];
|
|
113
|
+
/**
|
|
114
|
+
* THE ONE PLACE "All" joins a list (2026-09-30). A tab bar offering two or more
|
|
115
|
+
* of the personal lanes (Mine / My team / My Orgs / Shared) gets All as its
|
|
116
|
+
* FIRST tab — never declared per page. A list with one personal lane (a Private
|
|
117
|
+
* type: Mine alone) or none (an admin page) is untouched: All would repeat it.
|
|
118
|
+
* Idempotent; its `*_list_scoped` RPC answers `p_scope = 'all'`.
|
|
119
|
+
*/
|
|
120
|
+
declare function withAllScope(scopes: readonly ListScopeKind[]): ListScopeKind[];
|
|
121
|
+
/** The lanes a tab bar renders: the surface's own, plus All and My team where they belong. */
|
|
122
|
+
declare function withStandardLanes(scopes: readonly ListScopeKind[], lanes?: StandardLanesOptions): ListScopeKind[];
|
|
123
|
+
declare function isAllScope(scope: ListScope): scope is Extract<ListScope, {
|
|
124
|
+
kind: "all";
|
|
125
|
+
}>;
|
|
126
|
+
declare function isOrgsScope(scope: ListScope): scope is Extract<ListScope, {
|
|
127
|
+
kind: "orgs";
|
|
128
|
+
}>;
|
|
129
|
+
declare function isSharedScope(scope: ListScope): scope is Extract<ListScope, {
|
|
130
|
+
kind: "shared";
|
|
131
|
+
}>;
|
|
132
|
+
declare function isIndustryScope(scope: ListScope): scope is Extract<ListScope, {
|
|
133
|
+
kind: "industry";
|
|
134
|
+
}>;
|
|
135
|
+
declare function isSystemScope(scope: ListScope): scope is Extract<ListScope, {
|
|
136
|
+
kind: "system";
|
|
137
|
+
}>;
|
|
138
|
+
declare function isPublicScope(scope: ListScope): scope is Extract<ListScope, {
|
|
139
|
+
kind: "public";
|
|
140
|
+
}>;
|
|
141
|
+
/** The industry this scope narrows to, or null. */
|
|
142
|
+
declare function scopeIndustryId(scope: ListScope): string | null;
|
|
143
|
+
/**
|
|
144
|
+
* The id a LANE itself is narrowed to — an industry, or an admin support lane's
|
|
145
|
+
* one organization / person. Personal lanes never carry one: which organization
|
|
146
|
+
* a list shows is the organization filter (`EntityListQuery.orgId`).
|
|
147
|
+
*/
|
|
148
|
+
declare function scopeNarrowId(scope: ListScope): string | null;
|
|
149
|
+
/**
|
|
150
|
+
* THE `p_org_id` EVERY `*_list_scoped` / `*_scope_counts` / facets RPC receives:
|
|
151
|
+
* the page's organization filter (null = All organizations → undefined), or —
|
|
152
|
+
* on an admin support lane — that lane's own one-organization narrowing. Never
|
|
153
|
+
* the active organization.
|
|
154
|
+
*/
|
|
155
|
+
declare function listOrgParam(query: {
|
|
156
|
+
scope: ListScope;
|
|
157
|
+
orgId: string | null;
|
|
158
|
+
}): string | undefined;
|
|
159
|
+
/** Stable identity for tab selection / React keys. */
|
|
160
|
+
declare function scopeKey(scope: ListScope): string;
|
|
161
|
+
/** Build a scope from a kind + optional narrowing id (tab click handlers). */
|
|
162
|
+
declare function makeScope(kind: ListScopeKind, narrowToId?: string | null): ListScope;
|
|
163
|
+
|
|
164
|
+
/** Sentinel for "has no value" (uncategorized / untagged). Matches the SQL. */
|
|
165
|
+
declare const NONE_VALUE = "__none__";
|
|
166
|
+
/**
|
|
167
|
+
* ONE filter vocabulary, shared by column headers and any filter panel.
|
|
168
|
+
* Serialized straight into `<feature>_list_scoped(p_filters)`, so a filter set
|
|
169
|
+
* from a column header and the same filter set from a panel are the same query
|
|
170
|
+
* and cannot drift.
|
|
171
|
+
*
|
|
172
|
+
* Mirrors MatrxDataTable's ColumnFilterValue on purpose — the table is the
|
|
173
|
+
* primary producer, and a translation layer with its own vocabulary would be a
|
|
174
|
+
* third place for the two to disagree.
|
|
175
|
+
*/
|
|
176
|
+
type EntityFilterValue = {
|
|
177
|
+
kind: "text";
|
|
178
|
+
value: string;
|
|
179
|
+
} | {
|
|
180
|
+
kind: "select";
|
|
181
|
+
values: string[];
|
|
182
|
+
} | {
|
|
183
|
+
kind: "boolean";
|
|
184
|
+
value: boolean;
|
|
185
|
+
};
|
|
186
|
+
/** Keyed by COLUMN ID, so the server predicate and the header agree by name. */
|
|
187
|
+
type EntityFilters = Record<string, EntityFilterValue>;
|
|
188
|
+
/**
|
|
189
|
+
* THE ARCHIVED-ITEMS LAW's tri-state, from the ONE place it is defined:
|
|
190
|
+
* `@ai-matrx/design-system`'s `ArchiveFilterValue` (`active` | `archived` |
|
|
191
|
+
* `all`). This alias exists only so the ~200 call sites that already say
|
|
192
|
+
* `ArchivedFilter` keep reading naturally — the vocabulary itself is the
|
|
193
|
+
* package's, never this repo's, so the words on screen and the words in the
|
|
194
|
+
* type can never drift apart.
|
|
195
|
+
*/
|
|
196
|
+
type ArchivedFilter = ArchiveFilterValue;
|
|
197
|
+
interface EntityListQuery {
|
|
198
|
+
/** The access LANE — how I can see a record (All, Mine, My team, …). */
|
|
199
|
+
scope: ListScope;
|
|
200
|
+
/**
|
|
201
|
+
* THE ORGANIZATION FILTER — which of my organizations to look at. Its own
|
|
202
|
+
* axis, never inside a lane: null = All organizations (the default on every
|
|
203
|
+
* load), else one organization id. URL `?org_filter=`. It narrows EVERY lane and
|
|
204
|
+
* the counts, and is sent to every list RPC as `p_org_id`
|
|
205
|
+
* (`listOrgParam(query)`). It never initializes from, syncs with, or writes
|
|
206
|
+
* to the ACTIVE organization (common-docs
|
|
207
|
+
* /policies/access-ladder.md).
|
|
208
|
+
*/
|
|
209
|
+
orgId: string | null;
|
|
210
|
+
search: string;
|
|
211
|
+
/**
|
|
212
|
+
* Reach into full record content (prompt bodies, transcript text...).
|
|
213
|
+
* Opt-in: it is a full scan server-side and must never be the default.
|
|
214
|
+
*/
|
|
215
|
+
deep: boolean;
|
|
216
|
+
/**
|
|
217
|
+
* Kept OUT of `filters` because it carries a DEFAULT ("active only") rather
|
|
218
|
+
* than being absent-means-unfiltered like every other column.
|
|
219
|
+
*/
|
|
220
|
+
archived: ArchivedFilter;
|
|
221
|
+
filters: EntityFilters;
|
|
222
|
+
/** One-based, matching the pagination UI. */
|
|
223
|
+
page: number;
|
|
224
|
+
}
|
|
225
|
+
declare const DEFAULT_ENTITY_LIST_QUERY: EntityListQuery;
|
|
226
|
+
/**
|
|
227
|
+
* How many things are narrowing the list right now (the Filters badge).
|
|
228
|
+
* Counts ONLY what the user applied — never the sort or the active scope.
|
|
229
|
+
* A badge that reads "1" on an untouched page is a permanent lie that trains
|
|
230
|
+
* people to ignore the number (which is exactly what /agents/all's did).
|
|
231
|
+
*
|
|
232
|
+
* 🚨 THE ARCHIVE AXIS IS COMPARED AGAINST THE SURFACE'S OWN DEFAULT, NOT
|
|
233
|
+
* AGAINST THE LITERAL `"active"`. THE ARCHIVED-ITEMS LAW §6 made the starting
|
|
234
|
+
* value a user KNOB (`userPreferences.lists.archivedDefault`), so a user who
|
|
235
|
+
* set theirs to "Active + archived" met an untouched page whose Filters badge
|
|
236
|
+
* read "1" and whose empty state claimed a search-and-filter narrowing that
|
|
237
|
+
* nobody had applied — the exact permanent lie this function exists to
|
|
238
|
+
* prevent, reintroduced by the knob. The default is passed in because only the
|
|
239
|
+
* hook knows it.
|
|
240
|
+
*/
|
|
241
|
+
declare function countActiveFilters(query: EntityListQuery, defaultArchived?: ArchivedFilter): number;
|
|
242
|
+
/**
|
|
243
|
+
* 🚨 THE ALL-ARCHIVED FACT — what a list must know before it may say "none".
|
|
244
|
+
*
|
|
245
|
+
* THE DEFECT THIS CLOSES (row F10 repair, 2026-09-10). Every archive-aware
|
|
246
|
+
* `EntityListConfig` carries a STATIC `emptyState`, and the shell fell back to
|
|
247
|
+
* it whenever the query was untouched and no rows came back. But the archive
|
|
248
|
+
* filter's default HIDES archived rows (THE ARCHIVED-ITEMS LAW §2), so "the
|
|
249
|
+
* live half is empty" and "there is nothing here" are different facts — and
|
|
250
|
+
* the shell was printing the second when it only knew the first. Measured on
|
|
251
|
+
* `/maps` with all 46 of a user's maps archived: *"No maps yet — A map is a
|
|
252
|
+
* picture of how something works … Make one"*, beside a "New map" button, one
|
|
253
|
+
* popover away from that page's own Archived filter holding all 46. A screen
|
|
254
|
+
* telling an Expert to rebuild work they already have.
|
|
255
|
+
*
|
|
256
|
+
* The count CANNOT come from the facets or the scope counts: both are fetched
|
|
257
|
+
* with the query's CURRENT `archived` value, so on the defaulted page they
|
|
258
|
+
* describe the live half too. It is its own read, `archived: "archived"`, and
|
|
259
|
+
* it is asked ONLY when the live half came back empty — at most one extra,
|
|
260
|
+
* page-size-1 request, on the one screen that would otherwise lie.
|
|
261
|
+
*/
|
|
262
|
+
type ArchivedProbe =
|
|
263
|
+
/** Not applicable: the surface has no archive axis, or is already showing archived rows, or the live half is not empty. */
|
|
264
|
+
{
|
|
265
|
+
state: "off";
|
|
266
|
+
}
|
|
267
|
+
/** The live half is empty and the archived count is in flight. Say nothing yet. */
|
|
268
|
+
| {
|
|
269
|
+
state: "loading";
|
|
270
|
+
}
|
|
271
|
+
/** Answered. `total` archived rows match this exact scope/search/filters. */
|
|
272
|
+
| {
|
|
273
|
+
state: "known";
|
|
274
|
+
total: number; /** An open-ended archive: at least `total`, the store named no count. */
|
|
275
|
+
more?: boolean;
|
|
276
|
+
}
|
|
277
|
+
/** The read broke. The page must say it cannot tell — never fall back to "none yet". */
|
|
278
|
+
| {
|
|
279
|
+
state: "failed";
|
|
280
|
+
};
|
|
281
|
+
/**
|
|
282
|
+
* One narrowing choice inside a scope — an org under "My Orgs", an industry
|
|
283
|
+
* under "Industry". The LABEL comes from the same query as the count.
|
|
284
|
+
*
|
|
285
|
+
* That is deliberate and load-bearing: reading names from a Redux slice meant
|
|
286
|
+
* depending on `fetchFullContext`, a thunk that only runs on tasks/org-settings
|
|
287
|
+
* surfaces — so on /agents/all the slice was empty and the My Orgs dropdown
|
|
288
|
+
* silently never rendered. A tab bar must be self-sufficient from its own
|
|
289
|
+
* query, with no hydration ordering to get wrong.
|
|
290
|
+
*/
|
|
291
|
+
interface ScopeNarrowOption {
|
|
292
|
+
id: string;
|
|
293
|
+
label: string;
|
|
294
|
+
count: number;
|
|
295
|
+
}
|
|
296
|
+
interface EntityScopeCounts {
|
|
297
|
+
/** True server-side total per scope kind. Absent kinds are unsupported. */
|
|
298
|
+
byKind: Partial<Record<ListScopeKind, number>>;
|
|
299
|
+
/**
|
|
300
|
+
* The service names no lane counts for this question (an open-ended list the store pages
|
|
301
|
+
* without a count — `EntityListPage.hasMore`). The tabs draw no number at all, never `0`.
|
|
302
|
+
*/
|
|
303
|
+
uncounted?: boolean;
|
|
304
|
+
/** Narrowing options per scope kind, in server order. */
|
|
305
|
+
narrow: Partial<Record<ListScopeKind, ScopeNarrowOption[]>>;
|
|
306
|
+
/**
|
|
307
|
+
* WHY a scope has no narrowing options, in a sentence a person can act on.
|
|
308
|
+
*
|
|
309
|
+
* 🚨 THE DEFECT THIS CLOSES (one-resolution FIX-R6/F1, 2026-09-08). On
|
|
310
|
+
* production `/mandates`, `narrow.orgs` came back empty and the declared
|
|
311
|
+
* **Organization** section simply was not rendered — an admin belonging to
|
|
312
|
+
* nine organizations saw a Filters panel that never mentioned organizations,
|
|
313
|
+
* with nothing on screen saying why. "No options, so no section" is the
|
|
314
|
+
* fourth law's silent failure: a declared narrowing is either offered or it
|
|
315
|
+
* says what happened.
|
|
316
|
+
*
|
|
317
|
+
* A service that CANNOT offer options — the counts were refused, the caller's
|
|
318
|
+
* organizations have not been read yet, the caller genuinely belongs to none
|
|
319
|
+
* — sets the reason here and the panel prints it. Absent + no options means
|
|
320
|
+
* the shell will say so in its own generic words rather than hide.
|
|
321
|
+
*/
|
|
322
|
+
narrowUnavailable?: Partial<Record<ListScopeKind, string>>;
|
|
323
|
+
}
|
|
324
|
+
declare const EMPTY_SCOPE_COUNTS: EntityScopeCounts;
|
|
325
|
+
/** One row of any `*_list_scope_counts` RPC. */
|
|
326
|
+
interface ScopeCountRow {
|
|
327
|
+
scope: string;
|
|
328
|
+
narrow_id?: string | null;
|
|
329
|
+
label?: string | null;
|
|
330
|
+
total: number | string | null;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* THE ONE READER of a `*_list_scope_counts` result. Every surface converts
|
|
334
|
+
* through here, so a scope the server learns (System, then My team) reaches its
|
|
335
|
+
* tab everywhere at once. A hand-listed `kind !== "mine" && kind !== "orgs"…`
|
|
336
|
+
* filter per service is how `system` came back with a real total and was
|
|
337
|
+
* silently dropped on the way to its tab — the vocabulary check reads
|
|
338
|
+
* LIST_SCOPE_KINDS and nothing else. A row with `narrow_id` is one
|
|
339
|
+
* organization (or industry) inside its scope; without one it is the scope's
|
|
340
|
+
* blended total.
|
|
341
|
+
*/
|
|
342
|
+
declare function scopeCountsFromRows(rows: readonly ScopeCountRow[] | null | undefined, unnamed?: string): EntityScopeCounts;
|
|
343
|
+
/**
|
|
344
|
+
* Server-computed filter OPTIONS with counts, keyed by facet name.
|
|
345
|
+
*
|
|
346
|
+
* Server-computed is not a nicety: deriving "which categories exist" from
|
|
347
|
+
* loaded rows means loading every row, which is the pattern this system
|
|
348
|
+
* replaced. One live account has 34 categories and 773 distinct tags.
|
|
349
|
+
*/
|
|
350
|
+
interface EntityFacets {
|
|
351
|
+
byKind: Record<string, {
|
|
352
|
+
value: string;
|
|
353
|
+
count: number;
|
|
354
|
+
}[]>;
|
|
355
|
+
}
|
|
356
|
+
declare const EMPTY_FACETS: EntityFacets;
|
|
357
|
+
/** Count for one facet value, 0 when the facet or value is absent. */
|
|
358
|
+
declare function facetCount(facets: EntityFacets, kind: string, value: string): number;
|
|
359
|
+
/** Options for one facet, most-used first (the RPC already orders them). */
|
|
360
|
+
declare function facetValues(facets: EntityFacets, kind: string): {
|
|
361
|
+
value: string;
|
|
362
|
+
count: number;
|
|
363
|
+
}[];
|
|
364
|
+
/**
|
|
365
|
+
* A page of rows plus the TRUE total — not `rows.length`. Every
|
|
366
|
+
* `*_list_scoped` RPC returns `total_count` as a window function over the
|
|
367
|
+
* filtered set, identical on every row.
|
|
368
|
+
*/
|
|
369
|
+
interface EntityListPage<TRow> {
|
|
370
|
+
rows: TRow[];
|
|
371
|
+
total: number;
|
|
372
|
+
/**
|
|
373
|
+
* AN OPEN-ENDED PAGE (TABLE-ACTIONS, 2026-10-03): the store pages this list and names no
|
|
374
|
+
* count. Present → `total` is only how many rows reach through THIS page, and `hasMore` says
|
|
375
|
+
* whether a next page exists; the pagers draw no "of N" and Next follows `hasMore`. Absent → a
|
|
376
|
+
* counted list, as always. A count is the store's or absent — never a full read to make one.
|
|
377
|
+
*/
|
|
378
|
+
hasMore?: boolean;
|
|
379
|
+
}
|
|
380
|
+
/** Sort + paging, handed to the RPC. Style-owned, so it arrives from prefs. */
|
|
381
|
+
interface EntityListSort {
|
|
382
|
+
/**
|
|
383
|
+
* Column id. Free string, not a closed union: every column a surface
|
|
384
|
+
* declares is sortable (app policy), so the valid set is that surface's
|
|
385
|
+
* column registry. The server whitelists it and falls back rather than
|
|
386
|
+
* erroring, so a stale stored value can never break a page.
|
|
387
|
+
*/
|
|
388
|
+
sort: string;
|
|
389
|
+
direction: "asc" | "desc";
|
|
390
|
+
/** Pin favorites above every other ordering. */
|
|
391
|
+
favoritesFirst: boolean;
|
|
392
|
+
pageSize: number;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
export { ADMIN_SUPPORT_LIST_SCOPES, ALL_LANE_MEMBERS, type ArchivedFilter, type ArchivedProbe, DEFAULT_ENTITY_LIST_QUERY, DEFAULT_LIST_SCOPE, type DeclarableLane, EMPTY_FACETS, EMPTY_SCOPE_COUNTS, type EntityFacets, type EntityFilterValue, type EntityFilters, type EntityListPage, type EntityListQuery, type EntityListSort, type EntityScopeCounts, LIST_SCOPE_KINDS, type LaneSupport, type ListScope, type ListScopeKind, NONE_VALUE, PERSONAL_SEAT_SCOPES, type ScopeCountRow, type ScopeNarrowOption, type StandardLanesOptions, countActiveFilters, declaredAbsentLanes, facetCount, facetValues, isAllScope, isIndustryScope, isMineScope, isOrgsScope, isPublicScope, isSharedScope, isSystemScope, isTeamScope, listOrgParam, makeScope, scopeCountsFromRows, scopeIndustryId, scopeKey, scopeNarrowId, withAllScope, withStandardLanes, withTeamScope };
|