@ai-matrx/records-ui 0.68.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/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
@@ -1406,8 +1416,18 @@ interface RelationPickerProps {
1406
1416
  disabled?: boolean | undefined;
1407
1417
  id?: string | undefined;
1408
1418
  className?: string | undefined;
1419
+ /**
1420
+ * WHETHER A PERSON MAY MAKE THE THING SHE IS LOOKING FOR (RELATION-CREATE, 2026-09-22).
1421
+ *
1422
+ * On by default, because that is what this control is for on every surface a
1423
+ * person fills a record in: the grid cell, the generated form, a public form
1424
+ * runner. The one surface that passes `false` is the filter builder — picking
1425
+ * a value to FILTER by is not a reason to put a new customer in the book, and
1426
+ * a Create button there would write a row nobody asked for.
1427
+ */
1428
+ allowCreate?: boolean | undefined;
1409
1429
  }
1410
- declare function RelationPicker({ field, value, onChange, disabled, id, className }: RelationPickerProps): react.JSX.Element;
1430
+ declare function RelationPicker({ field, value, onChange, disabled, id, className, allowCreate }: RelationPickerProps): react.JSX.Element;
1411
1431
 
1412
1432
  interface FieldEditorControlProps {
1413
1433
  field: Field;
@@ -1807,17 +1827,24 @@ interface ViewSwitcherProps {
1807
1827
  /** "Who changed this?" from a grid cell — the host opens that column's timeline. */
1808
1828
  onAskWhoChanged?: ((fieldKey: string, recordId: Uuid) => void) | undefined;
1809
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;
1810
1837
  className?: string | undefined;
1811
1838
  }
1812
1839
  /** The records this view holds: its Rule's members when it names one, the table otherwise. */
1813
- declare function useViewRecords(view: SavedViewSpec, pageSize?: number): {
1840
+ declare function useViewRecords(view: SavedViewSpec, pageSize?: number, filter?: RecordFilter$1): {
1814
1841
  rows: ReadRow$1[];
1815
1842
  loading: boolean;
1816
1843
  error: RecordsError | null;
1817
1844
  /** Where membership came from, in a sentence. A screen that hides this is hiding the view's meaning. */
1818
1845
  membership: string;
1819
1846
  };
1820
- 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;
1821
1848
 
1822
1849
  interface ViewBarProps {
1823
1850
  /** The Table whose views these are. */
@@ -2159,8 +2186,16 @@ interface DigestSchedulerProps {
2159
2186
  * view's name, so nothing in the list is called "Saved view".
2160
2187
  */
2161
2188
  subjectName?: string | null | undefined;
2162
- /** The filters of the thing being scheduled, written straight into the saved view. */
2163
- filters?: Record<string, unknown> | null | undefined;
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;
2164
2199
  onScheduled?: (() => void) | undefined;
2165
2200
  onClose?: (() => void) | undefined;
2166
2201
  className?: string | undefined;
@@ -2882,14 +2917,12 @@ interface TablePageProps {
2882
2917
  /**
2883
2918
  * WHICH FIELD THE ADDRESS WANTS THE BOARD'S COLUMNS TO BE, for this visit.
2884
2919
  *
2885
- * 🚨 THIS IS HOW A DASHBOARD NUMBER CLICKS THROUGH (lane TAILS-6, 2026-09-22).
2886
- * Until today the canvas told the owner of the business to "bind `openRecords`
2887
- * on <RecordsUiProvider>" a developer's sentence on a customer's screen
2888
- * because nothing in this app routed a bar or a slice anywhere. The store has
2889
- * no door that returns the ROWS behind an aggregate filter, so a click cannot
2890
- * honestly promise a narrowed list; what it CAN do is open this table's board
2891
- * with its columns set to the very field the chart grouped by, where the
2892
- * 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.
2893
2926
  *
2894
2927
  * It overrides the saved view's own `groupField` for this visit only, exactly
2895
2928
  * as `activeView` overrides its layout, and it is never written back.
@@ -2898,40 +2931,48 @@ interface TablePageProps {
2898
2931
  /**
2899
2932
  * WHAT THE PERSON CLICKED TO GET HERE — "Jobs by stage · Awaiting parts".
2900
2933
  *
2901
- * Present, the board says where they came from AND says plainly that it is
2902
- * showing every record grouped by that field rather than only the ones the
2903
- * number counted. A screen that quietly showed more rows than the number
2904
- * promised would be the number lying twice.
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.
2905
2939
  */
2906
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;
2907
2956
  className?: string | undefined;
2908
2957
  }
2909
- /**
2910
- * THE SENTENCE A TABLE YOU CANNOT SEE SAYS.
2911
- *
2912
- * Measured on the real screen (independent verdict, 19 September): opening a
2913
- * table's page while another organization was active left the main area's text
2914
- * "literally the empty string" after eighteen seconds — no sentence, no
2915
- * spinner, no console error. A made-up id did the same. The cause is exactly
2916
- * one line of this file: `custom.table_list` correctly answers the tables this
2917
- * person can see in THIS organization, the table is not among them, so
2918
- * `useTable` answers `{ data: null, error: null }` — not a refusal, just
2919
- * absence — and the page rendered a skeleton for ever.
2920
- *
2921
- * Absence is an ANSWER and it gets a sentence. It deliberately does not say
2922
- * whether the table exists: this person cannot see it, and which of the two it
2923
- * is is not theirs to learn.
2924
- */
2925
2958
  /**
2926
2959
  * WHAT THE BOARD SAYS TO SOMEBODY A DASHBOARD NUMBER SENT HERE.
2927
2960
  *
2928
- * It says two things and neither is optional: which number they clicked, and
2929
- * that this is every record grouped by that field rather than only the ones the
2930
- * number counted. The store has no door that returns the rows behind an
2931
- * aggregate filter, so claiming a narrowed list would be a lie — and showing a
2932
- * wider one silently would be the same lie with nobody told.
2933
- */
2934
- declare function cameFromLine(label: string, groupField: string | null): string;
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;
2935
2976
  declare const TABLE_NOT_REACHABLE: string;
2936
2977
  /**
2937
2978
  * THE VIEW EVERY TABLE HAS, AND WHY IT IS NOT OPTIONAL.
@@ -3040,7 +3081,7 @@ declare function openingView(activeView: string | null | undefined, activeDashbo
3040
3081
  layout: ViewLayout | null;
3041
3082
  unknown: string | null;
3042
3083
  };
3043
- 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;
3044
3085
 
3045
3086
  /** What the roster can tell us about a user id. `null` when it cannot. */
3046
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
@@ -1406,8 +1416,18 @@ interface RelationPickerProps {
1406
1416
  disabled?: boolean | undefined;
1407
1417
  id?: string | undefined;
1408
1418
  className?: string | undefined;
1419
+ /**
1420
+ * WHETHER A PERSON MAY MAKE THE THING SHE IS LOOKING FOR (RELATION-CREATE, 2026-09-22).
1421
+ *
1422
+ * On by default, because that is what this control is for on every surface a
1423
+ * person fills a record in: the grid cell, the generated form, a public form
1424
+ * runner. The one surface that passes `false` is the filter builder — picking
1425
+ * a value to FILTER by is not a reason to put a new customer in the book, and
1426
+ * a Create button there would write a row nobody asked for.
1427
+ */
1428
+ allowCreate?: boolean | undefined;
1409
1429
  }
1410
- declare function RelationPicker({ field, value, onChange, disabled, id, className }: RelationPickerProps): react.JSX.Element;
1430
+ declare function RelationPicker({ field, value, onChange, disabled, id, className, allowCreate }: RelationPickerProps): react.JSX.Element;
1411
1431
 
1412
1432
  interface FieldEditorControlProps {
1413
1433
  field: Field;
@@ -1807,17 +1827,24 @@ interface ViewSwitcherProps {
1807
1827
  /** "Who changed this?" from a grid cell — the host opens that column's timeline. */
1808
1828
  onAskWhoChanged?: ((fieldKey: string, recordId: Uuid) => void) | undefined;
1809
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;
1810
1837
  className?: string | undefined;
1811
1838
  }
1812
1839
  /** The records this view holds: its Rule's members when it names one, the table otherwise. */
1813
- declare function useViewRecords(view: SavedViewSpec, pageSize?: number): {
1840
+ declare function useViewRecords(view: SavedViewSpec, pageSize?: number, filter?: RecordFilter$1): {
1814
1841
  rows: ReadRow$1[];
1815
1842
  loading: boolean;
1816
1843
  error: RecordsError | null;
1817
1844
  /** Where membership came from, in a sentence. A screen that hides this is hiding the view's meaning. */
1818
1845
  membership: string;
1819
1846
  };
1820
- 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;
1821
1848
 
1822
1849
  interface ViewBarProps {
1823
1850
  /** The Table whose views these are. */
@@ -2159,8 +2186,16 @@ interface DigestSchedulerProps {
2159
2186
  * view's name, so nothing in the list is called "Saved view".
2160
2187
  */
2161
2188
  subjectName?: string | null | undefined;
2162
- /** The filters of the thing being scheduled, written straight into the saved view. */
2163
- filters?: Record<string, unknown> | null | undefined;
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;
2164
2199
  onScheduled?: (() => void) | undefined;
2165
2200
  onClose?: (() => void) | undefined;
2166
2201
  className?: string | undefined;
@@ -2882,14 +2917,12 @@ interface TablePageProps {
2882
2917
  /**
2883
2918
  * WHICH FIELD THE ADDRESS WANTS THE BOARD'S COLUMNS TO BE, for this visit.
2884
2919
  *
2885
- * 🚨 THIS IS HOW A DASHBOARD NUMBER CLICKS THROUGH (lane TAILS-6, 2026-09-22).
2886
- * Until today the canvas told the owner of the business to "bind `openRecords`
2887
- * on <RecordsUiProvider>" a developer's sentence on a customer's screen
2888
- * because nothing in this app routed a bar or a slice anywhere. The store has
2889
- * no door that returns the ROWS behind an aggregate filter, so a click cannot
2890
- * honestly promise a narrowed list; what it CAN do is open this table's board
2891
- * with its columns set to the very field the chart grouped by, where the
2892
- * 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.
2893
2926
  *
2894
2927
  * It overrides the saved view's own `groupField` for this visit only, exactly
2895
2928
  * as `activeView` overrides its layout, and it is never written back.
@@ -2898,40 +2931,48 @@ interface TablePageProps {
2898
2931
  /**
2899
2932
  * WHAT THE PERSON CLICKED TO GET HERE — "Jobs by stage · Awaiting parts".
2900
2933
  *
2901
- * Present, the board says where they came from AND says plainly that it is
2902
- * showing every record grouped by that field rather than only the ones the
2903
- * number counted. A screen that quietly showed more rows than the number
2904
- * promised would be the number lying twice.
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.
2905
2939
  */
2906
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;
2907
2956
  className?: string | undefined;
2908
2957
  }
2909
- /**
2910
- * THE SENTENCE A TABLE YOU CANNOT SEE SAYS.
2911
- *
2912
- * Measured on the real screen (independent verdict, 19 September): opening a
2913
- * table's page while another organization was active left the main area's text
2914
- * "literally the empty string" after eighteen seconds — no sentence, no
2915
- * spinner, no console error. A made-up id did the same. The cause is exactly
2916
- * one line of this file: `custom.table_list` correctly answers the tables this
2917
- * person can see in THIS organization, the table is not among them, so
2918
- * `useTable` answers `{ data: null, error: null }` — not a refusal, just
2919
- * absence — and the page rendered a skeleton for ever.
2920
- *
2921
- * Absence is an ANSWER and it gets a sentence. It deliberately does not say
2922
- * whether the table exists: this person cannot see it, and which of the two it
2923
- * is is not theirs to learn.
2924
- */
2925
2958
  /**
2926
2959
  * WHAT THE BOARD SAYS TO SOMEBODY A DASHBOARD NUMBER SENT HERE.
2927
2960
  *
2928
- * It says two things and neither is optional: which number they clicked, and
2929
- * that this is every record grouped by that field rather than only the ones the
2930
- * number counted. The store has no door that returns the rows behind an
2931
- * aggregate filter, so claiming a narrowed list would be a lie — and showing a
2932
- * wider one silently would be the same lie with nobody told.
2933
- */
2934
- declare function cameFromLine(label: string, groupField: string | null): string;
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;
2935
2976
  declare const TABLE_NOT_REACHABLE: string;
2936
2977
  /**
2937
2978
  * THE VIEW EVERY TABLE HAS, AND WHY IT IS NOT OPTIONAL.
@@ -3040,7 +3081,7 @@ declare function openingView(activeView: string | null | undefined, activeDashbo
3040
3081
  layout: ViewLayout | null;
3041
3082
  unknown: string | null;
3042
3083
  };
3043
- 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;
3044
3085
 
3045
3086
  /** What the roster can tell us about a user id. `null` when it cannot. */
3046
3087
  type ResolveName = (userId: Uuid$1) => string | null;