@ai-matrx/records-ui 0.69.0 → 0.70.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 +27 -0
- package/dist/index.cjs +60 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +74 -43
- package/dist/index.d.ts +74 -43
- package/dist/index.js +61 -17
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.d.cts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode, ComponentType, RefObject } from 'react';
|
|
3
3
|
import * as _ai_matrx_records from '@ai-matrx/records';
|
|
4
|
-
import { PermissionLevel, Table, RecordScopeContext, EnrichCell, RecordsError, Uuid as Uuid$1, AggregateFilter, Field, RecordDocument, ReadRow, RelationDisplay, WriteConflict, ParityFieldType, NewFieldDeclaration, RuleExpression, ValueEnvelope, ChecklistRequirementKind, ContextPolicy, WorkDueState, FieldSensitivity, StageRuleOnFail, SubscriptionCadence, WorkInboxKind, FieldKind, FormSummary, PortalCard, PortalPrincipal, PortalPreviewRow, DashboardBlockKind, AggregateBucket, AggregateMeasure, DashboardBlock, DashboardSummary, DocTemplateRow, DocRenderRow, QuietHours, DashboardBlockResult, ChecklistRunStep, BookingSummary, CaptureSheetFace, CaptureField, AnonTokenBinding, RecordsConfig, RecordsActor, RecordsDataSource, HistoryActor, FieldHistoryEntry, FieldTypeWord } from '@ai-matrx/records';
|
|
5
|
-
import { Uuid, Field as Field$1, ReadRow as ReadRow$1 } from '@ai-matrx/records/react';
|
|
4
|
+
import { PermissionLevel, Table, RecordScopeContext, EnrichCell, RecordsError, Uuid as Uuid$1, AggregateFilter, Field, RecordDocument, ReadRow, RelationDisplay, WriteConflict, ParityFieldType, NewFieldDeclaration, RuleExpression, ValueEnvelope, ChecklistRequirementKind, ContextPolicy, WorkDueState, FieldSensitivity, StageRuleOnFail, SubscriptionCadence, WorkInboxKind, FieldKind, RecordFilter as RecordFilter$1, FormSummary, PortalCard, PortalPrincipal, PortalPreviewRow, DashboardBlockKind, AggregateBucket, AggregateMeasure, DashboardBlock, DashboardSummary, DocTemplateRow, DocRenderRow, QuietHours, DashboardBlockResult, ChecklistRunStep, BookingSummary, CaptureSheetFace, CaptureField, AnonTokenBinding, RecordsConfig, RecordsActor, RecordsDataSource, HistoryActor, FieldHistoryEntry, FieldTypeWord } from '@ai-matrx/records';
|
|
5
|
+
import { Uuid, RecordFilter, Field as Field$1, ReadRow as ReadRow$1 } from '@ai-matrx/records/react';
|
|
6
6
|
import { RecordsClient } from '@ai-matrx/records/core';
|
|
7
7
|
import { FieldFormatConfig } from '@ai-matrx/design-system/field-formats';
|
|
8
8
|
import { MatrxColumnDef } from '@ai-matrx/design-system/data-table/types';
|
|
@@ -1040,6 +1040,16 @@ interface GridProps {
|
|
|
1040
1040
|
tableId: Uuid;
|
|
1041
1041
|
/** Rows per page. The table's own pagination handles the rest. */
|
|
1042
1042
|
pageSize?: number | undefined;
|
|
1043
|
+
/**
|
|
1044
|
+
* THE QUESTION THIS GRID IS ANSWERING, in the store's one filter shape.
|
|
1045
|
+
*
|
|
1046
|
+
* Left out, it is the whole table. Supplied — a dashboard number was clicked,
|
|
1047
|
+
* or a saved view carries `filters` — the STORE narrows the page inside its
|
|
1048
|
+
* own query. Never filter `rows` here: this grid only ever holds one page,
|
|
1049
|
+
* so filtering in the browser would drop every matching record on page two
|
|
1050
|
+
* and call the result the answer.
|
|
1051
|
+
*/
|
|
1052
|
+
filter?: RecordFilter | undefined;
|
|
1043
1053
|
/** Opened when a row is clicked. A host that has a record page passes its own. */
|
|
1044
1054
|
onOpenRecord?: ((recordId: Uuid) => void) | undefined;
|
|
1045
1055
|
/** SCR-2. Called when an admin clicks the "+" at the end of the headers. */
|
|
@@ -1093,7 +1103,7 @@ interface GridProps {
|
|
|
1093
1103
|
onPresentationChange?: ((next: GridPresentation) => void) | undefined;
|
|
1094
1104
|
className?: string | undefined;
|
|
1095
1105
|
}
|
|
1096
|
-
declare function Grid({ tableId, pageSize, onOpenRecord, onAddField, onNewRecordForm, toolbarActions, editable, presentation, onPresentationChange, className, onAskWhoChanged, }: GridProps): react.JSX.Element;
|
|
1106
|
+
declare function Grid({ tableId, pageSize, filter, onOpenRecord, onAddField, onNewRecordForm, toolbarActions, editable, presentation, onPresentationChange, className, onAskWhoChanged, }: GridProps): react.JSX.Element;
|
|
1097
1107
|
/**
|
|
1098
1108
|
* One Field becomes one column. The header is the Field's own NAME — never its
|
|
1099
1109
|
* key (`names.ts`) — and the cell is the value, rendered by its parity type and
|
|
@@ -1817,17 +1827,24 @@ interface ViewSwitcherProps {
|
|
|
1817
1827
|
/** "Who changed this?" from a grid cell — the host opens that column's timeline. */
|
|
1818
1828
|
onAskWhoChanged?: ((fieldKey: string, recordId: Uuid) => void) | undefined;
|
|
1819
1829
|
pageSize?: number | undefined;
|
|
1830
|
+
/**
|
|
1831
|
+
* THE QUESTION THE ADDRESS ASKED, in the store's one filter shape — the same
|
|
1832
|
+
* object the chart counted with. It narrows the grid AND the board through
|
|
1833
|
+
* the store, so switching layout after a drill-through keeps the same rows
|
|
1834
|
+
* instead of quietly widening back to the whole table.
|
|
1835
|
+
*/
|
|
1836
|
+
filter?: RecordFilter$1 | undefined;
|
|
1820
1837
|
className?: string | undefined;
|
|
1821
1838
|
}
|
|
1822
1839
|
/** The records this view holds: its Rule's members when it names one, the table otherwise. */
|
|
1823
|
-
declare function useViewRecords(view: SavedViewSpec, pageSize?: number): {
|
|
1840
|
+
declare function useViewRecords(view: SavedViewSpec, pageSize?: number, filter?: RecordFilter$1): {
|
|
1824
1841
|
rows: ReadRow$1[];
|
|
1825
1842
|
loading: boolean;
|
|
1826
1843
|
error: RecordsError | null;
|
|
1827
1844
|
/** Where membership came from, in a sentence. A screen that hides this is hiding the view's meaning. */
|
|
1828
1845
|
membership: string;
|
|
1829
1846
|
};
|
|
1830
|
-
declare function ViewSwitcher({ view, onLayoutChange, onViewChange, onOpenRecord, onMoved, onNewRecordForm, onAskWhoChanged, pageSize, className, }: ViewSwitcherProps): react.JSX.Element;
|
|
1847
|
+
declare function ViewSwitcher({ view, onLayoutChange, onViewChange, onOpenRecord, onMoved, onNewRecordForm, onAskWhoChanged, pageSize, filter, className, }: ViewSwitcherProps): react.JSX.Element;
|
|
1831
1848
|
|
|
1832
1849
|
interface ViewBarProps {
|
|
1833
1850
|
/** The Table whose views these are. */
|
|
@@ -2169,8 +2186,16 @@ interface DigestSchedulerProps {
|
|
|
2169
2186
|
* view's name, so nothing in the list is called "Saved view".
|
|
2170
2187
|
*/
|
|
2171
2188
|
subjectName?: string | null | undefined;
|
|
2172
|
-
/**
|
|
2173
|
-
|
|
2189
|
+
/**
|
|
2190
|
+
* The filters of the thing being scheduled, written straight into the saved
|
|
2191
|
+
* view — in the store's ONE filter shape (`RecordFilter`), which is what
|
|
2192
|
+
* `custom.view_declare` stores, what `custom.agg_view_admits` reads to decide
|
|
2193
|
+
* whether a change belongs to this view, and what the read and aggregate
|
|
2194
|
+
* doors both take. It was `Record<string, unknown>` until DRILL named the
|
|
2195
|
+
* shape: an unknown here is a subscription that silently notifies about the
|
|
2196
|
+
* wrong records.
|
|
2197
|
+
*/
|
|
2198
|
+
filters?: RecordFilter$1 | null | undefined;
|
|
2174
2199
|
onScheduled?: (() => void) | undefined;
|
|
2175
2200
|
onClose?: (() => void) | undefined;
|
|
2176
2201
|
className?: string | undefined;
|
|
@@ -2892,14 +2917,12 @@ interface TablePageProps {
|
|
|
2892
2917
|
/**
|
|
2893
2918
|
* WHICH FIELD THE ADDRESS WANTS THE BOARD'S COLUMNS TO BE, for this visit.
|
|
2894
2919
|
*
|
|
2895
|
-
* 🚨 THIS IS HOW A DASHBOARD NUMBER CLICKS THROUGH (lane TAILS-6,
|
|
2896
|
-
*
|
|
2897
|
-
*
|
|
2898
|
-
*
|
|
2899
|
-
*
|
|
2900
|
-
*
|
|
2901
|
-
* with its columns set to the very field the chart grouped by, where the
|
|
2902
|
-
* column the person clicked is right there and every card in it is real.
|
|
2920
|
+
* 🚨 THIS IS HALF OF HOW A DASHBOARD NUMBER CLICKS THROUGH (lane TAILS-6,
|
|
2921
|
+
* 2026-09-22): the board opens with its columns set to the very field the
|
|
2922
|
+
* chart grouped by, so the column the person clicked is right there. The
|
|
2923
|
+
* other half is `filter`, which lane DRILL added hours later once the store
|
|
2924
|
+
* grew `custom.read_records_matching` — until then a click could only widen,
|
|
2925
|
+
* and the sentence under the board said so out loud.
|
|
2903
2926
|
*
|
|
2904
2927
|
* It overrides the saved view's own `groupField` for this visit only, exactly
|
|
2905
2928
|
* as `activeView` overrides its layout, and it is never written back.
|
|
@@ -2908,40 +2931,48 @@ interface TablePageProps {
|
|
|
2908
2931
|
/**
|
|
2909
2932
|
* WHAT THE PERSON CLICKED TO GET HERE — "Jobs by stage · Awaiting parts".
|
|
2910
2933
|
*
|
|
2911
|
-
* Present, the board says where they came from AND
|
|
2912
|
-
*
|
|
2913
|
-
*
|
|
2914
|
-
*
|
|
2934
|
+
* Present, the board says where they came from AND, when `filter` came with
|
|
2935
|
+
* it, which question is holding the rest of the table back. A screen showing
|
|
2936
|
+
* a subset without saying so is a table lying about how much of itself it is
|
|
2937
|
+
* — and one showing MORE rows than the number promised is the number lying
|
|
2938
|
+
* twice.
|
|
2915
2939
|
*/
|
|
2916
2940
|
cameFrom?: string | null | undefined;
|
|
2941
|
+
/**
|
|
2942
|
+
* WHICH RECORDS THE ADDRESS ASKED FOR, in the store's one filter shape.
|
|
2943
|
+
*
|
|
2944
|
+
* 🚨 THIS IS WHAT MAKES A DASHBOARD NUMBER WALKABLE (lane DRILL, 2026-09-22).
|
|
2945
|
+
* TAILS-6 could route a bar click here but could not narrow anything: the
|
|
2946
|
+
* store had no door that took a filter, so the click opened the WHOLE table
|
|
2947
|
+
* and a sentence underneath said so. `custom.read_records_matching` takes the
|
|
2948
|
+
* same object the chart counted with, and this prop is how the address hands
|
|
2949
|
+
* it to the grid and the board — both of them, so switching layout does not
|
|
2950
|
+
* quietly widen back to every record.
|
|
2951
|
+
*
|
|
2952
|
+
* It is a QUESTION for this visit, exactly like `activeView` and
|
|
2953
|
+
* `activeGroupField`, and is never written onto the saved view.
|
|
2954
|
+
*/
|
|
2955
|
+
filter?: RecordFilter$1 | null | undefined;
|
|
2917
2956
|
className?: string | undefined;
|
|
2918
2957
|
}
|
|
2919
|
-
/**
|
|
2920
|
-
* THE SENTENCE A TABLE YOU CANNOT SEE SAYS.
|
|
2921
|
-
*
|
|
2922
|
-
* Measured on the real screen (independent verdict, 19 September): opening a
|
|
2923
|
-
* table's page while another organization was active left the main area's text
|
|
2924
|
-
* "literally the empty string" after eighteen seconds — no sentence, no
|
|
2925
|
-
* spinner, no console error. A made-up id did the same. The cause is exactly
|
|
2926
|
-
* one line of this file: `custom.table_list` correctly answers the tables this
|
|
2927
|
-
* person can see in THIS organization, the table is not among them, so
|
|
2928
|
-
* `useTable` answers `{ data: null, error: null }` — not a refusal, just
|
|
2929
|
-
* absence — and the page rendered a skeleton for ever.
|
|
2930
|
-
*
|
|
2931
|
-
* Absence is an ANSWER and it gets a sentence. It deliberately does not say
|
|
2932
|
-
* whether the table exists: this person cannot see it, and which of the two it
|
|
2933
|
-
* is is not theirs to learn.
|
|
2934
|
-
*/
|
|
2935
2958
|
/**
|
|
2936
2959
|
* WHAT THE BOARD SAYS TO SOMEBODY A DASHBOARD NUMBER SENT HERE.
|
|
2937
2960
|
*
|
|
2938
|
-
*
|
|
2939
|
-
*
|
|
2940
|
-
*
|
|
2941
|
-
*
|
|
2942
|
-
*
|
|
2943
|
-
|
|
2944
|
-
|
|
2961
|
+
* 🚨 IT USED TO SAY THE OPPOSITE, AND IT WAS TELLING THE TRUTH WHEN IT DID.
|
|
2962
|
+
* Until 2026-09-22 this sentence read "This is every record grouped into the
|
|
2963
|
+
* same columns the chart used — not only the ones that number counted",
|
|
2964
|
+
* because `custom.read_records` took no filter and `custom.record_aggregate`
|
|
2965
|
+
* answers groups and counts, never ids: the store had no door that could open
|
|
2966
|
+
* the rows behind a number, so claiming a narrowed list would have been a lie.
|
|
2967
|
+
* `custom.read_records_matching` is that door, and it evaluates the question
|
|
2968
|
+
* through the very same `custom.record_filter_sql` the number was counted
|
|
2969
|
+
* with — so this now says the narrow thing, and means it.
|
|
2970
|
+
*
|
|
2971
|
+
* It still says both halves out loud: which number they clicked, and WHICH
|
|
2972
|
+
* QUESTION is holding rows back. A screen showing a subset without saying so
|
|
2973
|
+
* is a table lying about how much of itself it is.
|
|
2974
|
+
*/
|
|
2975
|
+
declare function cameFromLine(label: string, groupField: string | null, question?: string | null): string;
|
|
2945
2976
|
declare const TABLE_NOT_REACHABLE: string;
|
|
2946
2977
|
/**
|
|
2947
2978
|
* THE VIEW EVERY TABLE HAS, AND WHY IT IS NOT OPTIONAL.
|
|
@@ -3050,7 +3081,7 @@ declare function openingView(activeView: string | null | undefined, activeDashbo
|
|
|
3050
3081
|
layout: ViewLayout | null;
|
|
3051
3082
|
unknown: string | null;
|
|
3052
3083
|
};
|
|
3053
|
-
declare function TablePage({ tableId, pageSize, seedViews, onLeave, leaveLabel, activeDashboardId, activeRecordId, activeView, onViewChanged, activeGroupField, cameFrom, className, }: TablePageProps): react.JSX.Element;
|
|
3084
|
+
declare function TablePage({ tableId, pageSize, seedViews, onLeave, leaveLabel, activeDashboardId, activeRecordId, activeView, onViewChanged, activeGroupField, cameFrom, filter, className, }: TablePageProps): react.JSX.Element;
|
|
3054
3085
|
|
|
3055
3086
|
/** What the roster can tell us about a user id. `null` when it cannot. */
|
|
3056
3087
|
type ResolveName = (userId: Uuid$1) => string | null;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode, ComponentType, RefObject } from 'react';
|
|
3
3
|
import * as _ai_matrx_records from '@ai-matrx/records';
|
|
4
|
-
import { PermissionLevel, Table, RecordScopeContext, EnrichCell, RecordsError, Uuid as Uuid$1, AggregateFilter, Field, RecordDocument, ReadRow, RelationDisplay, WriteConflict, ParityFieldType, NewFieldDeclaration, RuleExpression, ValueEnvelope, ChecklistRequirementKind, ContextPolicy, WorkDueState, FieldSensitivity, StageRuleOnFail, SubscriptionCadence, WorkInboxKind, FieldKind, FormSummary, PortalCard, PortalPrincipal, PortalPreviewRow, DashboardBlockKind, AggregateBucket, AggregateMeasure, DashboardBlock, DashboardSummary, DocTemplateRow, DocRenderRow, QuietHours, DashboardBlockResult, ChecklistRunStep, BookingSummary, CaptureSheetFace, CaptureField, AnonTokenBinding, RecordsConfig, RecordsActor, RecordsDataSource, HistoryActor, FieldHistoryEntry, FieldTypeWord } from '@ai-matrx/records';
|
|
5
|
-
import { Uuid, Field as Field$1, ReadRow as ReadRow$1 } from '@ai-matrx/records/react';
|
|
4
|
+
import { PermissionLevel, Table, RecordScopeContext, EnrichCell, RecordsError, Uuid as Uuid$1, AggregateFilter, Field, RecordDocument, ReadRow, RelationDisplay, WriteConflict, ParityFieldType, NewFieldDeclaration, RuleExpression, ValueEnvelope, ChecklistRequirementKind, ContextPolicy, WorkDueState, FieldSensitivity, StageRuleOnFail, SubscriptionCadence, WorkInboxKind, FieldKind, RecordFilter as RecordFilter$1, FormSummary, PortalCard, PortalPrincipal, PortalPreviewRow, DashboardBlockKind, AggregateBucket, AggregateMeasure, DashboardBlock, DashboardSummary, DocTemplateRow, DocRenderRow, QuietHours, DashboardBlockResult, ChecklistRunStep, BookingSummary, CaptureSheetFace, CaptureField, AnonTokenBinding, RecordsConfig, RecordsActor, RecordsDataSource, HistoryActor, FieldHistoryEntry, FieldTypeWord } from '@ai-matrx/records';
|
|
5
|
+
import { Uuid, RecordFilter, Field as Field$1, ReadRow as ReadRow$1 } from '@ai-matrx/records/react';
|
|
6
6
|
import { RecordsClient } from '@ai-matrx/records/core';
|
|
7
7
|
import { FieldFormatConfig } from '@ai-matrx/design-system/field-formats';
|
|
8
8
|
import { MatrxColumnDef } from '@ai-matrx/design-system/data-table/types';
|
|
@@ -1040,6 +1040,16 @@ interface GridProps {
|
|
|
1040
1040
|
tableId: Uuid;
|
|
1041
1041
|
/** Rows per page. The table's own pagination handles the rest. */
|
|
1042
1042
|
pageSize?: number | undefined;
|
|
1043
|
+
/**
|
|
1044
|
+
* THE QUESTION THIS GRID IS ANSWERING, in the store's one filter shape.
|
|
1045
|
+
*
|
|
1046
|
+
* Left out, it is the whole table. Supplied — a dashboard number was clicked,
|
|
1047
|
+
* or a saved view carries `filters` — the STORE narrows the page inside its
|
|
1048
|
+
* own query. Never filter `rows` here: this grid only ever holds one page,
|
|
1049
|
+
* so filtering in the browser would drop every matching record on page two
|
|
1050
|
+
* and call the result the answer.
|
|
1051
|
+
*/
|
|
1052
|
+
filter?: RecordFilter | undefined;
|
|
1043
1053
|
/** Opened when a row is clicked. A host that has a record page passes its own. */
|
|
1044
1054
|
onOpenRecord?: ((recordId: Uuid) => void) | undefined;
|
|
1045
1055
|
/** SCR-2. Called when an admin clicks the "+" at the end of the headers. */
|
|
@@ -1093,7 +1103,7 @@ interface GridProps {
|
|
|
1093
1103
|
onPresentationChange?: ((next: GridPresentation) => void) | undefined;
|
|
1094
1104
|
className?: string | undefined;
|
|
1095
1105
|
}
|
|
1096
|
-
declare function Grid({ tableId, pageSize, onOpenRecord, onAddField, onNewRecordForm, toolbarActions, editable, presentation, onPresentationChange, className, onAskWhoChanged, }: GridProps): react.JSX.Element;
|
|
1106
|
+
declare function Grid({ tableId, pageSize, filter, onOpenRecord, onAddField, onNewRecordForm, toolbarActions, editable, presentation, onPresentationChange, className, onAskWhoChanged, }: GridProps): react.JSX.Element;
|
|
1097
1107
|
/**
|
|
1098
1108
|
* One Field becomes one column. The header is the Field's own NAME — never its
|
|
1099
1109
|
* key (`names.ts`) — and the cell is the value, rendered by its parity type and
|
|
@@ -1817,17 +1827,24 @@ interface ViewSwitcherProps {
|
|
|
1817
1827
|
/** "Who changed this?" from a grid cell — the host opens that column's timeline. */
|
|
1818
1828
|
onAskWhoChanged?: ((fieldKey: string, recordId: Uuid) => void) | undefined;
|
|
1819
1829
|
pageSize?: number | undefined;
|
|
1830
|
+
/**
|
|
1831
|
+
* THE QUESTION THE ADDRESS ASKED, in the store's one filter shape — the same
|
|
1832
|
+
* object the chart counted with. It narrows the grid AND the board through
|
|
1833
|
+
* the store, so switching layout after a drill-through keeps the same rows
|
|
1834
|
+
* instead of quietly widening back to the whole table.
|
|
1835
|
+
*/
|
|
1836
|
+
filter?: RecordFilter$1 | undefined;
|
|
1820
1837
|
className?: string | undefined;
|
|
1821
1838
|
}
|
|
1822
1839
|
/** The records this view holds: its Rule's members when it names one, the table otherwise. */
|
|
1823
|
-
declare function useViewRecords(view: SavedViewSpec, pageSize?: number): {
|
|
1840
|
+
declare function useViewRecords(view: SavedViewSpec, pageSize?: number, filter?: RecordFilter$1): {
|
|
1824
1841
|
rows: ReadRow$1[];
|
|
1825
1842
|
loading: boolean;
|
|
1826
1843
|
error: RecordsError | null;
|
|
1827
1844
|
/** Where membership came from, in a sentence. A screen that hides this is hiding the view's meaning. */
|
|
1828
1845
|
membership: string;
|
|
1829
1846
|
};
|
|
1830
|
-
declare function ViewSwitcher({ view, onLayoutChange, onViewChange, onOpenRecord, onMoved, onNewRecordForm, onAskWhoChanged, pageSize, className, }: ViewSwitcherProps): react.JSX.Element;
|
|
1847
|
+
declare function ViewSwitcher({ view, onLayoutChange, onViewChange, onOpenRecord, onMoved, onNewRecordForm, onAskWhoChanged, pageSize, filter, className, }: ViewSwitcherProps): react.JSX.Element;
|
|
1831
1848
|
|
|
1832
1849
|
interface ViewBarProps {
|
|
1833
1850
|
/** The Table whose views these are. */
|
|
@@ -2169,8 +2186,16 @@ interface DigestSchedulerProps {
|
|
|
2169
2186
|
* view's name, so nothing in the list is called "Saved view".
|
|
2170
2187
|
*/
|
|
2171
2188
|
subjectName?: string | null | undefined;
|
|
2172
|
-
/**
|
|
2173
|
-
|
|
2189
|
+
/**
|
|
2190
|
+
* The filters of the thing being scheduled, written straight into the saved
|
|
2191
|
+
* view — in the store's ONE filter shape (`RecordFilter`), which is what
|
|
2192
|
+
* `custom.view_declare` stores, what `custom.agg_view_admits` reads to decide
|
|
2193
|
+
* whether a change belongs to this view, and what the read and aggregate
|
|
2194
|
+
* doors both take. It was `Record<string, unknown>` until DRILL named the
|
|
2195
|
+
* shape: an unknown here is a subscription that silently notifies about the
|
|
2196
|
+
* wrong records.
|
|
2197
|
+
*/
|
|
2198
|
+
filters?: RecordFilter$1 | null | undefined;
|
|
2174
2199
|
onScheduled?: (() => void) | undefined;
|
|
2175
2200
|
onClose?: (() => void) | undefined;
|
|
2176
2201
|
className?: string | undefined;
|
|
@@ -2892,14 +2917,12 @@ interface TablePageProps {
|
|
|
2892
2917
|
/**
|
|
2893
2918
|
* WHICH FIELD THE ADDRESS WANTS THE BOARD'S COLUMNS TO BE, for this visit.
|
|
2894
2919
|
*
|
|
2895
|
-
* 🚨 THIS IS HOW A DASHBOARD NUMBER CLICKS THROUGH (lane TAILS-6,
|
|
2896
|
-
*
|
|
2897
|
-
*
|
|
2898
|
-
*
|
|
2899
|
-
*
|
|
2900
|
-
*
|
|
2901
|
-
* with its columns set to the very field the chart grouped by, where the
|
|
2902
|
-
* column the person clicked is right there and every card in it is real.
|
|
2920
|
+
* 🚨 THIS IS HALF OF HOW A DASHBOARD NUMBER CLICKS THROUGH (lane TAILS-6,
|
|
2921
|
+
* 2026-09-22): the board opens with its columns set to the very field the
|
|
2922
|
+
* chart grouped by, so the column the person clicked is right there. The
|
|
2923
|
+
* other half is `filter`, which lane DRILL added hours later once the store
|
|
2924
|
+
* grew `custom.read_records_matching` — until then a click could only widen,
|
|
2925
|
+
* and the sentence under the board said so out loud.
|
|
2903
2926
|
*
|
|
2904
2927
|
* It overrides the saved view's own `groupField` for this visit only, exactly
|
|
2905
2928
|
* as `activeView` overrides its layout, and it is never written back.
|
|
@@ -2908,40 +2931,48 @@ interface TablePageProps {
|
|
|
2908
2931
|
/**
|
|
2909
2932
|
* WHAT THE PERSON CLICKED TO GET HERE — "Jobs by stage · Awaiting parts".
|
|
2910
2933
|
*
|
|
2911
|
-
* Present, the board says where they came from AND
|
|
2912
|
-
*
|
|
2913
|
-
*
|
|
2914
|
-
*
|
|
2934
|
+
* Present, the board says where they came from AND, when `filter` came with
|
|
2935
|
+
* it, which question is holding the rest of the table back. A screen showing
|
|
2936
|
+
* a subset without saying so is a table lying about how much of itself it is
|
|
2937
|
+
* — and one showing MORE rows than the number promised is the number lying
|
|
2938
|
+
* twice.
|
|
2915
2939
|
*/
|
|
2916
2940
|
cameFrom?: string | null | undefined;
|
|
2941
|
+
/**
|
|
2942
|
+
* WHICH RECORDS THE ADDRESS ASKED FOR, in the store's one filter shape.
|
|
2943
|
+
*
|
|
2944
|
+
* 🚨 THIS IS WHAT MAKES A DASHBOARD NUMBER WALKABLE (lane DRILL, 2026-09-22).
|
|
2945
|
+
* TAILS-6 could route a bar click here but could not narrow anything: the
|
|
2946
|
+
* store had no door that took a filter, so the click opened the WHOLE table
|
|
2947
|
+
* and a sentence underneath said so. `custom.read_records_matching` takes the
|
|
2948
|
+
* same object the chart counted with, and this prop is how the address hands
|
|
2949
|
+
* it to the grid and the board — both of them, so switching layout does not
|
|
2950
|
+
* quietly widen back to every record.
|
|
2951
|
+
*
|
|
2952
|
+
* It is a QUESTION for this visit, exactly like `activeView` and
|
|
2953
|
+
* `activeGroupField`, and is never written onto the saved view.
|
|
2954
|
+
*/
|
|
2955
|
+
filter?: RecordFilter$1 | null | undefined;
|
|
2917
2956
|
className?: string | undefined;
|
|
2918
2957
|
}
|
|
2919
|
-
/**
|
|
2920
|
-
* THE SENTENCE A TABLE YOU CANNOT SEE SAYS.
|
|
2921
|
-
*
|
|
2922
|
-
* Measured on the real screen (independent verdict, 19 September): opening a
|
|
2923
|
-
* table's page while another organization was active left the main area's text
|
|
2924
|
-
* "literally the empty string" after eighteen seconds — no sentence, no
|
|
2925
|
-
* spinner, no console error. A made-up id did the same. The cause is exactly
|
|
2926
|
-
* one line of this file: `custom.table_list` correctly answers the tables this
|
|
2927
|
-
* person can see in THIS organization, the table is not among them, so
|
|
2928
|
-
* `useTable` answers `{ data: null, error: null }` — not a refusal, just
|
|
2929
|
-
* absence — and the page rendered a skeleton for ever.
|
|
2930
|
-
*
|
|
2931
|
-
* Absence is an ANSWER and it gets a sentence. It deliberately does not say
|
|
2932
|
-
* whether the table exists: this person cannot see it, and which of the two it
|
|
2933
|
-
* is is not theirs to learn.
|
|
2934
|
-
*/
|
|
2935
2958
|
/**
|
|
2936
2959
|
* WHAT THE BOARD SAYS TO SOMEBODY A DASHBOARD NUMBER SENT HERE.
|
|
2937
2960
|
*
|
|
2938
|
-
*
|
|
2939
|
-
*
|
|
2940
|
-
*
|
|
2941
|
-
*
|
|
2942
|
-
*
|
|
2943
|
-
|
|
2944
|
-
|
|
2961
|
+
* 🚨 IT USED TO SAY THE OPPOSITE, AND IT WAS TELLING THE TRUTH WHEN IT DID.
|
|
2962
|
+
* Until 2026-09-22 this sentence read "This is every record grouped into the
|
|
2963
|
+
* same columns the chart used — not only the ones that number counted",
|
|
2964
|
+
* because `custom.read_records` took no filter and `custom.record_aggregate`
|
|
2965
|
+
* answers groups and counts, never ids: the store had no door that could open
|
|
2966
|
+
* the rows behind a number, so claiming a narrowed list would have been a lie.
|
|
2967
|
+
* `custom.read_records_matching` is that door, and it evaluates the question
|
|
2968
|
+
* through the very same `custom.record_filter_sql` the number was counted
|
|
2969
|
+
* with — so this now says the narrow thing, and means it.
|
|
2970
|
+
*
|
|
2971
|
+
* It still says both halves out loud: which number they clicked, and WHICH
|
|
2972
|
+
* QUESTION is holding rows back. A screen showing a subset without saying so
|
|
2973
|
+
* is a table lying about how much of itself it is.
|
|
2974
|
+
*/
|
|
2975
|
+
declare function cameFromLine(label: string, groupField: string | null, question?: string | null): string;
|
|
2945
2976
|
declare const TABLE_NOT_REACHABLE: string;
|
|
2946
2977
|
/**
|
|
2947
2978
|
* THE VIEW EVERY TABLE HAS, AND WHY IT IS NOT OPTIONAL.
|
|
@@ -3050,7 +3081,7 @@ declare function openingView(activeView: string | null | undefined, activeDashbo
|
|
|
3050
3081
|
layout: ViewLayout | null;
|
|
3051
3082
|
unknown: string | null;
|
|
3052
3083
|
};
|
|
3053
|
-
declare function TablePage({ tableId, pageSize, seedViews, onLeave, leaveLabel, activeDashboardId, activeRecordId, activeView, onViewChanged, activeGroupField, cameFrom, className, }: TablePageProps): react.JSX.Element;
|
|
3084
|
+
declare function TablePage({ tableId, pageSize, seedViews, onLeave, leaveLabel, activeDashboardId, activeRecordId, activeView, onViewChanged, activeGroupField, cameFrom, filter, className, }: TablePageProps): react.JSX.Element;
|
|
3054
3085
|
|
|
3055
3086
|
/** What the roster can tell us about a user id. `null` when it cannot. */
|
|
3056
3087
|
type ResolveName = (userId: Uuid$1) => string | null;
|
package/dist/index.js
CHANGED
|
@@ -3332,6 +3332,7 @@ var NO_COLUMNS_WHY = "A column is what a record holds \u2014 a name, a date, an
|
|
|
3332
3332
|
function Grid({
|
|
3333
3333
|
tableId,
|
|
3334
3334
|
pageSize = 50,
|
|
3335
|
+
filter,
|
|
3335
3336
|
onOpenRecord,
|
|
3336
3337
|
onAddField,
|
|
3337
3338
|
onNewRecordForm,
|
|
@@ -3347,7 +3348,7 @@ function Grid({
|
|
|
3347
3348
|
const table = useTable2(tableId);
|
|
3348
3349
|
const fields = useFields3(tableId);
|
|
3349
3350
|
const [page, setPage] = useState10(0);
|
|
3350
|
-
const records = useRecords2(tableId, { pageSize, page });
|
|
3351
|
+
const records = useRecords2(tableId, { pageSize, page, ...filter ? { filter } : {} });
|
|
3351
3352
|
const rights = useTableRights(table.data);
|
|
3352
3353
|
const rows = useMemo9(() => records.data?.rows ?? [], [records.data]);
|
|
3353
3354
|
const rowIds = useMemo9(() => rows.map((row) => row.id), [rows]);
|
|
@@ -7131,9 +7132,12 @@ function unplacedName(record, field, labels) {
|
|
|
7131
7132
|
// src/ViewSwitcher.tsx
|
|
7132
7133
|
import { jsx as jsx26, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
7133
7134
|
var NO_FIELDS = [];
|
|
7134
|
-
function useViewRecords(view, pageSize = 200) {
|
|
7135
|
+
function useViewRecords(view, pageSize = 200, filter) {
|
|
7135
7136
|
const client = useRecordsClient16();
|
|
7136
|
-
const table = useRecords5(view.ruleId ? null : view.subject, {
|
|
7137
|
+
const table = useRecords5(view.ruleId ? null : view.subject, {
|
|
7138
|
+
pageSize,
|
|
7139
|
+
...filter ? { filter } : {}
|
|
7140
|
+
});
|
|
7137
7141
|
const [ruled, setRuled] = useState24({
|
|
7138
7142
|
rows: [],
|
|
7139
7143
|
loading: Boolean(view.ruleId),
|
|
@@ -7188,6 +7192,7 @@ function ViewSwitcher({
|
|
|
7188
7192
|
onNewRecordForm,
|
|
7189
7193
|
onAskWhoChanged,
|
|
7190
7194
|
pageSize = 200,
|
|
7195
|
+
filter,
|
|
7191
7196
|
className
|
|
7192
7197
|
}) {
|
|
7193
7198
|
const [layout, setLayout] = useState24(view.layout);
|
|
@@ -7225,6 +7230,7 @@ function ViewSwitcher({
|
|
|
7225
7230
|
{
|
|
7226
7231
|
tableId: view.subject,
|
|
7227
7232
|
pageSize,
|
|
7233
|
+
...filter ? { filter } : {},
|
|
7228
7234
|
...local.presentation ?? view.presentation ? { presentation: local.presentation ?? view.presentation } : {},
|
|
7229
7235
|
...onViewChange ? { onPresentationChange: (next) => change({ presentation: next }) } : {},
|
|
7230
7236
|
...onOpenRecord ? { onOpenRecord } : {},
|
|
@@ -7236,6 +7242,7 @@ function ViewSwitcher({
|
|
|
7236
7242
|
{
|
|
7237
7243
|
view: { ...view, ...local, layout },
|
|
7238
7244
|
pageSize,
|
|
7245
|
+
...filter ? { filter } : {},
|
|
7239
7246
|
onChange: change,
|
|
7240
7247
|
remembered: Boolean(onViewChange),
|
|
7241
7248
|
...onOpenRecord ? { onOpenRecord } : {},
|
|
@@ -7283,13 +7290,14 @@ function boardColumnChoice(fields, chosenKey, stage) {
|
|
|
7283
7290
|
function Board({
|
|
7284
7291
|
view,
|
|
7285
7292
|
pageSize,
|
|
7293
|
+
filter,
|
|
7286
7294
|
onChange,
|
|
7287
7295
|
remembered,
|
|
7288
7296
|
onOpenRecord,
|
|
7289
7297
|
onMoved
|
|
7290
7298
|
}) {
|
|
7291
7299
|
const fields = useFields12(view.subject);
|
|
7292
|
-
const records = useViewRecords(view, pageSize);
|
|
7300
|
+
const records = useViewRecords(view, pageSize, filter);
|
|
7293
7301
|
const stage = useStageField(view.subject);
|
|
7294
7302
|
if (fields.error) return /* @__PURE__ */ jsx26(RefusalNotice, { error: fields.error });
|
|
7295
7303
|
if (records.error) return /* @__PURE__ */ jsx26(RefusalNotice, { error: records.error });
|
|
@@ -12852,14 +12860,20 @@ function DashboardCanvas({ tableId, activeDashboardId, filter, className }) {
|
|
|
12852
12860
|
rights.admin ? /* @__PURE__ */ jsx47(Button39, { size: "sm", variant: "ghost", onClick: () => void remove(board), children: "Delete" }) : null
|
|
12853
12861
|
] }) : null
|
|
12854
12862
|
] }),
|
|
12855
|
-
board && scheduling ?
|
|
12856
|
-
|
|
12857
|
-
|
|
12858
|
-
|
|
12859
|
-
|
|
12860
|
-
|
|
12861
|
-
|
|
12862
|
-
|
|
12863
|
+
board && scheduling ? (
|
|
12864
|
+
// ONE SHAPE, SO NO CAST. The dashboard's own filter, the saved view it
|
|
12865
|
+
// schedules over and the drill-through it opens are all the same
|
|
12866
|
+
// `RecordFilter`; the `as Record<string, unknown>` that used to be here
|
|
12867
|
+
// was the seam where they could have drifted.
|
|
12868
|
+
/* @__PURE__ */ jsx47(
|
|
12869
|
+
DigestScheduler,
|
|
12870
|
+
{
|
|
12871
|
+
tableId,
|
|
12872
|
+
subjectName: board.name,
|
|
12873
|
+
...filter ? { filters: filter } : {},
|
|
12874
|
+
onClose: () => setScheduling(false)
|
|
12875
|
+
}
|
|
12876
|
+
)
|
|
12863
12877
|
) : null,
|
|
12864
12878
|
board ? /* @__PURE__ */ jsxs43("div", { className: "flex flex-col gap-1", children: [
|
|
12865
12879
|
/* @__PURE__ */ jsxs43("div", { className: "flex items-center gap-1", children: [
|
|
@@ -14862,12 +14876,38 @@ function TablesHome({ onOpenTable, className }) {
|
|
|
14862
14876
|
|
|
14863
14877
|
// src/TablePage.tsx
|
|
14864
14878
|
import { useCallback as useCallback37, useEffect as useEffect41, useState as useState53 } from "react";
|
|
14865
|
-
import { useRecordsClient as useRecordsClient43, useTable as useTable22 } from "@ai-matrx/records/react";
|
|
14879
|
+
import { useFields as useFields23, useRecordsClient as useRecordsClient43, useTable as useTable22 } from "@ai-matrx/records/react";
|
|
14866
14880
|
import { Button as Button48, Separator as Separator12, Skeleton as Skeleton34, cn as cn51 } from "@ai-matrx/design-system";
|
|
14867
14881
|
import { Fragment as Fragment28, jsx as jsx58, jsxs as jsxs53 } from "react/jsx-runtime";
|
|
14868
|
-
function
|
|
14869
|
-
const
|
|
14870
|
-
|
|
14882
|
+
function filterInWords(filter, fields) {
|
|
14883
|
+
const nameOf = (key) => {
|
|
14884
|
+
const field = fields.find((f) => f.key === key);
|
|
14885
|
+
return field ? fieldName(field) : humanize(key);
|
|
14886
|
+
};
|
|
14887
|
+
const parts = Object.keys(filter).map((key) => {
|
|
14888
|
+
const value = filter[key];
|
|
14889
|
+
if (value === null || value === void 0) return `${nameOf(key)} is not filled in`;
|
|
14890
|
+
if (typeof value === "object") {
|
|
14891
|
+
const from = (value.from ?? "").toString().trim();
|
|
14892
|
+
const to = (value.to ?? "").toString().trim();
|
|
14893
|
+
if (from !== "" && to !== "") return `${nameOf(key)} from ${from} up to ${to}`;
|
|
14894
|
+
if (from !== "") return `${nameOf(key)} from ${from} onwards`;
|
|
14895
|
+
if (to !== "") return `${nameOf(key)} up to ${to}`;
|
|
14896
|
+
return `${nameOf(key)} in a period nobody named`;
|
|
14897
|
+
}
|
|
14898
|
+
return `${nameOf(key)} is ${String(value)}`;
|
|
14899
|
+
});
|
|
14900
|
+
if (parts.length === 0) return "";
|
|
14901
|
+
if (parts.length === 1) return parts[0];
|
|
14902
|
+
return `${parts.slice(0, -1).join(", ")} and ${parts[parts.length - 1]}`;
|
|
14903
|
+
}
|
|
14904
|
+
function cameFromLine(label, groupField, question) {
|
|
14905
|
+
const asked = (question ?? "").trim();
|
|
14906
|
+
if (asked === "") {
|
|
14907
|
+
const grouped = groupField ? " grouped into the same columns the chart used" : "";
|
|
14908
|
+
return `You came here from \u201C${label}\u201D. This is every record in this table${grouped} \u2014 that number was counted over all of them.`;
|
|
14909
|
+
}
|
|
14910
|
+
return `You came here from \u201C${label}\u201D. These are only the records that number counted: ${asked}. Everything else in this table is being held back \u2014 clear the filter to see all of it.`;
|
|
14871
14911
|
}
|
|
14872
14912
|
var TABLE_NOT_REACHABLE = "This table is not in the organization you are working in, so there is nothing here to show. Switch to the organization that owns it and open it again \u2014 or it may have been deleted.";
|
|
14873
14913
|
var DEFAULT_VIEW_NAME = "All records";
|
|
@@ -14914,11 +14954,14 @@ function TablePage({
|
|
|
14914
14954
|
onViewChanged,
|
|
14915
14955
|
activeGroupField,
|
|
14916
14956
|
cameFrom,
|
|
14957
|
+
filter,
|
|
14917
14958
|
className
|
|
14918
14959
|
}) {
|
|
14919
14960
|
const client = useRecordsClient43();
|
|
14920
14961
|
const table = useTable22(tableId);
|
|
14921
14962
|
const rights = useTableRights(table.data);
|
|
14963
|
+
const pageFields = useFields23(filter ? tableId : null);
|
|
14964
|
+
const askedInWords = filter ? filterInWords(filter, pageFields.data ?? []) : null;
|
|
14922
14965
|
const organizationId = useRecordsClient43().config.organizationId;
|
|
14923
14966
|
const [view, setView] = useState53(null);
|
|
14924
14967
|
const opening = openingRail(activeRecordId);
|
|
@@ -15112,7 +15155,7 @@ function TablePage({
|
|
|
15112
15155
|
{
|
|
15113
15156
|
"data-testid": "came-from-a-number",
|
|
15114
15157
|
className: "rounded-md border px-3 py-2 text-xs leading-relaxed text-muted-foreground",
|
|
15115
|
-
children: cameFromLine(cameFrom, activeGroupField ?? null)
|
|
15158
|
+
children: cameFromLine(cameFrom, activeGroupField ?? null, askedInWords)
|
|
15116
15159
|
}
|
|
15117
15160
|
) : null,
|
|
15118
15161
|
/* @__PURE__ */ jsx58(
|
|
@@ -15126,6 +15169,7 @@ function TablePage({
|
|
|
15126
15169
|
...activeGroupField ? { groupField: activeGroupField } : {}
|
|
15127
15170
|
},
|
|
15128
15171
|
pageSize,
|
|
15172
|
+
...filter ? { filter } : {},
|
|
15129
15173
|
onLayoutChange: (layout) => {
|
|
15130
15174
|
setLayoutFromLink(layout);
|
|
15131
15175
|
onViewChanged?.(layout);
|