@altertable/data-app 0.65.0 → 0.66.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.
Files changed (138) hide show
  1. package/AGENTS.md +38 -1
  2. package/CONTRIBUTING.md +37 -3
  3. package/README.md +1 -1
  4. package/dist/chunks/{contract-sm7j7k95.js → contract-13j5zb3c.js} +2 -144
  5. package/dist/chunks/contract-13j5zb3c.js.map +13 -0
  6. package/dist/chunks/contract-19ckn3n7.js +78 -0
  7. package/dist/chunks/contract-19ckn3n7.js.map +10 -0
  8. package/dist/chunks/{contract-a061yj6r.js → contract-e11m4y7f.js} +34 -70
  9. package/dist/chunks/contract-e11m4y7f.js.map +12 -0
  10. package/dist/chunks/{contract-g6hky7x6.js → contract-h8a6f559.js} +8 -4
  11. package/dist/chunks/contract-h8a6f559.js.map +12 -0
  12. package/dist/chunks/contract-havnjmdr.js +13011 -0
  13. package/dist/chunks/contract-havnjmdr.js.map +99 -0
  14. package/dist/chunks/{contract-txjy0en2.js → contract-m6n8ctfc.js} +64 -72
  15. package/dist/chunks/contract-m6n8ctfc.js.map +10 -0
  16. package/dist/chunks/contract-pk603qj7.js +146 -0
  17. package/dist/chunks/contract-pk603qj7.js.map +10 -0
  18. package/dist/chunks/{contract-yxbjea23.js → contract-rfbakka4.js} +179 -2
  19. package/dist/chunks/contract-rfbakka4.js.map +13 -0
  20. package/dist/client/index.js +11 -6
  21. package/dist/client/index.js.map +1 -1
  22. package/dist/core/appearance.js +1 -1
  23. package/dist/core/contract.js +13 -3
  24. package/dist/core/contract.js.map +1 -1
  25. package/dist/embed/index.js +9 -7
  26. package/dist/embed/index.js.map +2 -2
  27. package/dist/local.js +1 -1
  28. package/dist/local.js.map +2 -2
  29. package/dist/react/embed/index.js +1 -1
  30. package/dist/react/index.js +2532 -11545
  31. package/dist/react/index.js.map +16 -91
  32. package/dist/react/ui/index.js +1614 -0
  33. package/dist/react/ui/index.js.map +22 -0
  34. package/dist/server.js +1 -1
  35. package/dist/server.js.map +2 -2
  36. package/dist/types/client/annotations.d.ts +23 -0
  37. package/dist/types/client/index.d.ts +1 -0
  38. package/dist/types/core/annotations.d.ts +92 -0
  39. package/dist/types/core/appearance.d.ts +2 -2
  40. package/dist/types/core/contract.d.ts +2 -0
  41. package/dist/types/core/presentation.d.ts +2 -0
  42. package/dist/types/core/variables.d.ts +2 -5
  43. package/dist/types/react/annotations/AnnotationBar.d.ts +24 -0
  44. package/dist/types/react/annotations/AnnotationControls.d.ts +10 -0
  45. package/dist/types/react/annotations/AnnotationEditor.d.ts +15 -0
  46. package/dist/types/react/annotations/AnnotationMarkers.d.ts +19 -0
  47. package/dist/types/react/annotations/AnnotationSelectionLayer.d.ts +12 -0
  48. package/dist/types/react/annotations/AnnotationTarget.d.ts +9 -0
  49. package/dist/types/react/annotations/AnnotationTooltip.d.ts +9 -0
  50. package/dist/types/react/annotations/AnnotationTrigger.d.ts +10 -0
  51. package/dist/types/react/annotations/annotation-editor-state.d.ts +56 -0
  52. package/dist/types/react/annotations/annotation-screenshot.d.ts +4 -0
  53. package/dist/types/react/annotations/annotation-targets.d.ts +33 -0
  54. package/dist/types/react/annotations/getAnnotationProps.d.ts +15 -0
  55. package/dist/types/react/annotations/styles.d.ts +3 -0
  56. package/dist/types/react/annotations/useAnnotationGeometry.d.ts +22 -0
  57. package/dist/types/react/annotations/useDataAppAnnotations.d.ts +19 -0
  58. package/dist/types/react/bindings.d.ts +110 -0
  59. package/dist/types/react/content.d.ts +24 -11
  60. package/dist/types/react/hooks.d.ts +29 -787
  61. package/dist/types/react/index.d.ts +30 -105
  62. package/dist/types/react/source-owner.d.ts +2 -0
  63. package/dist/types/react/style-contract.d.ts +60 -0
  64. package/dist/types/react/style-validation.d.ts +2 -0
  65. package/dist/types/react/ui/AboutData.d.ts +9 -1
  66. package/dist/types/react/ui/AppHeader.d.ts +1 -2
  67. package/dist/types/react/ui/AppLayout.d.ts +3 -9
  68. package/dist/types/react/ui/AppToolbar.d.ts +5 -8
  69. package/dist/types/react/ui/AreaChart.d.ts +7 -0
  70. package/dist/types/react/ui/BarChart.d.ts +6 -0
  71. package/dist/types/react/ui/{ComparisonVisual.d.ts → Comparison.d.ts} +3 -3
  72. package/dist/types/react/ui/ContentSkeleton.d.ts +2 -0
  73. package/dist/types/react/ui/DataApp.d.ts +17 -58
  74. package/dist/types/react/ui/DataAppFrame.d.ts +42 -0
  75. package/dist/types/react/ui/DataBoundary.d.ts +4 -5
  76. package/dist/types/react/ui/DataSection.d.ts +6 -18
  77. package/dist/types/react/ui/DataSectionBoundary.d.ts +30 -0
  78. package/dist/types/react/ui/DataValue.d.ts +9 -0
  79. package/dist/types/react/ui/DataWidget.d.ts +2 -1
  80. package/dist/types/react/ui/DateTimeTooltip.d.ts +1 -1
  81. package/dist/types/react/ui/ExportControl.d.ts +1 -1
  82. package/dist/types/react/ui/HelpPopover.d.ts +3 -4
  83. package/dist/types/react/ui/InspectionContext.d.ts +2 -0
  84. package/dist/types/react/ui/InspectionProvider.d.ts +29 -0
  85. package/dist/types/react/ui/LineChart.d.ts +7 -0
  86. package/dist/types/react/ui/MetricWidget.d.ts +4 -3
  87. package/dist/types/react/ui/PieChart.d.ts +8 -0
  88. package/dist/types/react/ui/PresentStory.d.ts +2 -5
  89. package/dist/types/react/ui/RefreshControl.d.ts +1 -2
  90. package/dist/types/react/ui/ScatterChart.d.ts +17 -0
  91. package/dist/types/react/ui/SearchField.d.ts +1 -2
  92. package/dist/types/react/ui/SearchInput.d.ts +3 -1
  93. package/dist/types/react/ui/Sheet.d.ts +2 -1
  94. package/dist/types/react/ui/Skeleton.d.ts +5 -2
  95. package/dist/types/react/ui/StaticDataApp.d.ts +4 -0
  96. package/dist/types/react/ui/TableWidget.d.ts +19 -16
  97. package/dist/types/react/ui/Tabs.d.ts +6 -2
  98. package/dist/types/react/ui/TextWidget.d.ts +2 -1
  99. package/dist/types/react/ui/Tooltip.d.ts +4 -3
  100. package/dist/types/react/ui/TrendChart.d.ts +5 -0
  101. package/dist/types/react/ui/UpdatedAt.d.ts +1 -1
  102. package/dist/types/react/ui/VisualizationWidget.d.ts +12 -13
  103. package/dist/types/react/ui/WidgetViewTabs.d.ts +1 -1
  104. package/dist/types/react/ui/chart-data.d.ts +25 -0
  105. package/dist/types/react/ui/data-context.d.ts +18 -7
  106. package/dist/types/react/ui/icons.d.ts +1 -0
  107. package/dist/types/react/ui/index.d.ts +74 -0
  108. package/dist/types/react/ui/metric.d.ts +1 -1
  109. package/dist/types/react/ui/presentation.d.ts +5 -1
  110. package/dist/types/react/ui/shortcuts.d.ts +7 -1
  111. package/dist/types/react/view-runtime.d.ts +29 -0
  112. package/dist/types/react/view.d.ts +21 -4
  113. package/dist/types/react/widgets.d.ts +79 -0
  114. package/docs/app-authoring.md +58 -50
  115. package/docs/data-context.md +89 -0
  116. package/docs/formatting-and-appearance.md +52 -0
  117. package/docs/hosted-apps.md +0 -13
  118. package/docs/layout.md +4 -2
  119. package/docs/react-embed.md +2 -2
  120. package/docs/react.md +20 -316
  121. package/docs/stories-and-export.md +52 -0
  122. package/docs/styling.md +55 -0
  123. package/docs/ui-quality.md +20 -0
  124. package/docs/ui.md +41 -0
  125. package/docs/variables.md +98 -0
  126. package/docs/views.md +127 -0
  127. package/docs/widgets.md +142 -0
  128. package/examples/starter-data-app/index.tsx +82 -55
  129. package/package.json +12 -4
  130. package/dist/chunks/contract-a061yj6r.js.map +0 -12
  131. package/dist/chunks/contract-g6hky7x6.js.map +0 -12
  132. package/dist/chunks/contract-sm7j7k95.js.map +0 -14
  133. package/dist/chunks/contract-txjy0en2.js.map +0 -10
  134. package/dist/chunks/contract-yxbjea23.js.map +0 -12
  135. package/dist/types/react/ui/RefreshRegion.d.ts +0 -9
  136. package/dist/types/react/ui/SelectableBarChart.d.ts +0 -15
  137. package/docs/releasing.md +0 -79
  138. /package/dist/types/react/ui/{comparison.d.ts → metric-comparison.d.ts} +0 -0
package/docs/react.md CHANGED
@@ -5,11 +5,8 @@ React 19.2 or newer and React DOM 19.2 or newer are peer dependencies.
5
5
 
6
6
  ## Mount the app
7
7
 
8
- `mountDataApp({ config, component })` mounts into `#root`, sets the document
9
- title and language, attaches navigation to an available iframe transport, and
10
- installs `<DataAppProvider>`. Uncaught React rendering errors are logged and
11
- reported to the iframe host as fatal app failures. When mounting through another
12
- framework, wrap the app in `<DataAppProvider>` yourself.
8
+ Use `mountDataApp({ config, component })` to mount into `#root`. When mounting
9
+ through another framework, import `<DataAppProvider>` from `/react/ui`.
13
10
 
14
11
  ```tsx
15
12
  import { injectDataAppStyles, mountDataApp } from '@altertable/data-app/react';
@@ -18,318 +15,25 @@ injectDataAppStyles();
18
15
  mountDataApp({ config, component: App });
19
16
  ```
20
17
 
21
- Importing the package does not inject styles. For server rendering, call the
22
- injector on the client before mounting or hydrating. With a restrictive CSP,
23
- pass a permitted nonce; the first call sets it for the document. No CSS loader
24
- or separate stylesheet asset is needed.
18
+ Call `injectDataAppStyles()` before mounting; no separate stylesheet is needed.
19
+ For a restrictive CSP, pass the permitted nonce on the first call.
20
+ See [Styling](styling.md) for appearance, CSS tokens, and overrides.
25
21
 
26
- ## Bind a view
22
+ Use `/react` for declared views, bound widgets, and layouts. Use
23
+ [`/react/ui`](ui.md) for setup/static screens and direct UI composition.
27
24
 
28
- ```tsx
29
- import { createDataClient } from '@altertable/data-app/client';
30
- import {
31
- createDataHooks,
32
- dateRangeVariable,
33
- DataApp,
34
- Grid,
35
- MetricWidget,
36
- VisualizationWidget,
37
- Ranking,
38
- } from '@altertable/data-app/react';
39
- import type { operations } from '#app/operations.ts';
40
- import { calendar } from '#app/contracts.ts';
41
- import { dataContext, trackedIdentities } from '#app/data-context.tsx';
42
- import config from '#config';
43
-
44
- const period = dateRangeVariable({
45
- key: 'period',
46
- contract: calendar,
47
- comparison: true,
48
- defaultValue: { kind: 'preset', id: 'last-30' },
49
- });
50
- const { defineDataView, useView } =
51
- createDataHooks(createDataClient<typeof operations>());
52
- const activityView = defineDataView({
53
- operation: 'activity',
54
- variables: { period },
55
- input: ({ period }) => period,
56
- date: { variable: 'period', input: input => input },
57
- isEmpty: data => data.features.length === 0,
58
- empty: { title: 'No activity in this range' },
59
- });
60
- const featureEvidence = dataContext.evidence({
61
- id: 'feature-use',
62
- queryNames: [dataContext.queryNames.activity],
63
- });
64
- const content = activityView.content(result => (
65
- <Grid columns={2}>
66
- <MetricWidget
67
- metric={trackedIdentities}
68
- reading={result.metric(data => ({
69
- current: data.count,
70
- previous: data.previousCount,
71
- }))}
72
- />
73
- <VisualizationWidget
74
- title="Feature use"
75
- evidence={featureEvidence}
76
- reading={result.select(data => data.features)}
77
- isEmpty={features => features.length === 0}
78
- empty={{ title: 'No features' }}
79
- skeleton={{ variant: 'ranking', rows: 6 }}
80
- >
81
- {features => <Ranking items={features} />}
82
- </VisualizationWidget>
83
- </Grid>
84
- ));
85
- function App() {
86
- const activity = useView(activityView);
87
- return (
88
- <DataApp
89
- config={config}
90
- dataContext={dataContext}
91
- request={activity}
92
- csvExport={({ data }) => ({
93
- filename: 'activity.csv',
94
- tables: [
95
- {
96
- name: 'Tracked identities',
97
- columns: ['Current count', 'Previous count'],
98
- rows: [[data.count, data.previousCount]],
99
- },
100
- {
101
- name: 'Feature use',
102
- columns: ['Feature ID', 'Count'],
103
- rows: data.features.map(feature => [feature.id, feature.value]),
104
- },
105
- ],
106
- })}
107
- story={({ data }) => [
108
- dataContext.finding({
109
- id: 'feature-use',
110
- headline: 'Feature use in the selected period',
111
- visual: <Ranking items={data.features} />,
112
- evidence: featureEvidence,
113
- }),
114
- ]}
115
- {...content}
116
- />
117
- );
118
- }
119
- ```
120
-
121
- The `date` binding identifies the controlling variable and extracts its range from the operation input. Nested inputs use, for example, `input: (input) => input.period`. The runtime rejects mappings that silently change the selected range or comparison. Non-date views supply `describeInput`; date views can override it when other inputs also need describing.
122
-
123
- Share the [date range contract](contract.md#shared-date-ranges) between the
124
- operation parser and the view.
125
-
126
- `useView()` generates controls for date, text and fixed-option select variables; custom controls use `result.variables.bind(name)`. `input` chooses which variables reach the operation, so local search can stay local. Hooks belong in the enclosing component.
127
-
128
- For large tables, use query-backed pagination with a stable sort and total
129
- count. Client pagination and search cover only the rows already returned.
130
-
131
- Bound `<VisualizationWidget>` and `<TableWidget>` components require `evidence` from `context.evidence(...)`. A bound `<MetricWidget>` gets evidence from its metric definition. Evidence must name at least one glossary entry or query. Static widgets may omit it.
132
-
133
- `<MetricWidget>` and `<ComparisonVisual>` both accept the same `metric` and `reading`. The comparison is enabled by the displayed result's range. The definition supplies formatting and evidence; a reading cannot override those or provide a second value. `favorableDirection` is optional; changes are neutral until the author defines whether up or down is favorable.
134
-
135
- Use the [app helpers](#reuse-app-helpers) for metric formats and values in tables,
136
- charts, and custom views.
137
-
138
- `defineDataContent()` remains available for manually managed requests. Its optional `{ date: (input) => rangeRequest }` binds comparison readings. `<DataSection>` handles independent requests. Low-level widgets, tabs and layout components remain available for custom interfaces.
139
-
140
- ## Layout
141
-
142
- Use `<Stack>` for sections, `<Grid>` for peer widgets, and `<GridItem>` for spans.
143
- They share the app layout gap and own responsive behavior. Follow the
144
- [layout contract](layout.md) for spacing ownership and composition guidance.
25
+ ## Choose a task
145
26
 
146
- ## Narrative text
147
-
148
- Use `<TextContent>` for prose within a page or custom layout, and `<TextWidget>`
149
- when the explanation belongs in a titled panel alongside other widgets.
150
-
151
- Give text a purpose: frame the question, explain how to interpret a comparison,
152
- qualify a finding, or suggest what to explore next. Choose the content for the
153
- reader's question and the decisions the exploration supports.
154
-
155
- For data-dependent text, use `reading={result.select((data, input) => ...)}` and
156
- provide `evidence`. Derive both the explanation and its scope from those displayed
157
- values so it stays consistent with the visualizations while filters change.
158
- Static instructions can use ordinary children; local filters should feed the same
159
- filtered data to the text and its related visualization.
160
-
161
- ## Reuse app helpers
162
-
163
- Use the shared helpers for common app tasks. Follow the entry points below to
164
- find their exports, types, and usage constraints.
165
-
166
- | Task | Entry point |
167
- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
168
- | Format numbers, counts, percentages, currency, date ranges, and plural labels (`pluralize`) | [Format helpers](https://github.com/altertable-ai/data-app/blob/main/src/core/format.ts) |
169
- | Choose chart colors | [Chart colors](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/chartColor.ts) |
170
- | Search items and highlight matches | [Search helpers](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/searchItems.ts) |
171
- | Read and synchronize URL query state | [URL state helpers](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/search.ts) |
172
- | Render timestamps, freshness, table shares, periods, and metric comparisons | [React exports](https://github.com/altertable-ai/data-app/blob/main/src/react/index.ts) |
173
- | Configure appearance and theme preferences | [Appearance helpers](https://github.com/altertable-ai/data-app/blob/main/src/core/appearance.ts) |
174
- | Build the app's scoped document title | [Configuration helpers](https://github.com/altertable-ai/data-app/blob/main/src/core/config.ts) |
175
-
176
- ## Register source identifiers
177
-
178
- Use `defineDataIdentifiers()` from `/react` to register exact catalog, schema,
179
- table, and field names. Use `<DataIdentifier>` in descriptions and glossary
180
- entries to render those source references consistently.
181
-
182
- ```tsx
183
- import { defineDataIdentifiers } from '@altertable/data-app/react';
184
-
185
- const identifiers = defineDataIdentifiers({
186
- tables: {
187
- events: {
188
- catalog: 'product_analytics',
189
- schema: 'analytics',
190
- name: 'events',
191
- },
192
- },
193
- columns: { identity: { table: 'events', name: 'identity_uuid' } },
194
- });
195
- const { DataIdentifier } = identifiers;
196
-
197
- <DataIdentifier id="tables.events" />;
198
- <DataIdentifier id="columns.events.identity" />;
199
- ```
27
+ | Task | Guide |
28
+ | -------------------------------------------------------- | --------------------------------------------------------- |
29
+ | Declare a view and handle loading, refresh, and failures | [Views](views.md) |
30
+ | Configure date, text, select, and dimension controls | [Variables](variables.md) |
31
+ | Render metrics, charts, tables, and narrative | [Widgets](widgets.md) |
32
+ | Compose sections and responsive grids | [Layout](layout.md) |
33
+ | Register sources, definitions, metrics, and evidence | [Data context](data-context.md) |
34
+ | Present findings and export displayed data | [Stories and export](stories-and-export.md) |
35
+ | Format values and configure appearance | [Formatting and appearance](formatting-and-appearance.md) |
36
+ | Host an iframe from React | [React embed](react-embed.md) |
200
37
 
201
- Pass `identifiers.definitions` to `createDataContext()` so the context carries the
202
- same source registry. Source identifiers name physical data; glossary entries
203
- explain its business meaning, and query evidence records how it was queried.
204
-
205
- ## Bind evidence
206
-
207
- ```tsx
208
- const queries = defineQueryNames({ activity: 'feature-activity' });
209
- // In the server operation: queryNames: queries
210
- const context = createDataContext(queries)({
211
- identifiers: identifiers.definitions,
212
- description: (
213
- <>
214
- Explore activity in <DataIdentifier id="tables.events" />.
215
- </>
216
- ),
217
- glossary: {
218
- identities: {
219
- term: 'Tracked identities',
220
- definition: (
221
- <>
222
- Distinct <DataIdentifier id="columns.events.identity" /> values.
223
- </>
224
- ),
225
- queryNames: [queries.activity],
226
- },
227
- },
228
- });
229
- const evidence = context.evidence({
230
- id: 'identities',
231
- glossaryIds: ['identities'],
232
- queryNames: [queries.activity],
233
- });
234
- const trackedIdentities = context.metric({
235
- id: 'actions',
236
- glossaryId: 'identities',
237
- label: 'Tracked identities',
238
- format: { kind: 'count' },
239
- });
240
- const finding = context.finding({
241
- id: 'activity',
242
- headline: 'What people do',
243
- visual: <ActivityChart />,
244
- evidence: { id: 'activity-evidence', queryNames: [queries.activity] },
245
- });
246
- ```
247
-
248
- Import `defineQueryNames()` from `/contract` and the context/identifier factories from `/react`. Use the same registry in `defineOperation({ queryNames: queries, ... })`. Unknown glossary/query references fail type checks and registry validation; the server also validates returned query names.
249
-
250
- ## Time views and field filters
251
-
252
- `createDataHooks(client).defineTimeView()` owns the `period` variable, calendar
253
- controls, and displayed-period label. Declare `time: { contract, defaultValue }`,
254
- an operation, `isEmpty`, and `empty`. With no additional variables, its default
255
- input is the calendar request. With additional variables, it is `{ period, ...variables }`.
256
- Supply an `input` mapper for a different operation shape and `bindings` to extract
257
- nested period or field-filter inputs. Mappings must preserve the selected values.
258
-
259
- `dimensionFilter()` from `/contract` requires exactly one option source: fixed `options` or a `facet`.
260
- Use `defineFacetFilter()` to bind a facet operation and its typed input. The generated
261
- `<DimensionPicker>` preserves cached options during refresh and failure, offers
262
- missing values separately, and retains selected values absent from a result with
263
- zero counts. `<SelectableBarChart>` can share controlled selection with the picker.
264
-
265
- ## Preserve displayed results
266
-
267
- The callback in `view.content()` receives the displayed result and its original
268
- input during refreshes and failures. Use that input when labeling the data.
269
-
270
- Previous data and evidence are retained only when the input changes within the
271
- same operation. Switching to another operation shows its own cached response or
272
- an initial loading/error state; it never inherits another operation's result.
273
-
274
- Operation and facet caches are scoped to the `DataClient` instance. Hooks created
275
- from the same client share queries; separate clients do not share results, stale
276
- data, or cancellation even under one provider. Keep client instances stable
277
- across renders to preserve their cache.
278
-
279
- ## Findings and inspection
280
-
281
- `<DataWidget>` composes a body, toolbar feedback, and footer. Widgets and their
282
- inspection sheets render the same visual and controls. Keep interactive state
283
- above both mounts when authoring custom children.
284
-
285
- When a trusted parent supplies [parent presentation](embed.md#parent-presentation),
286
- `<DataApp>` follows its resolved theme and suppresses local theme controls,
287
- including in presentations. On an embedded surface (`surface: 'embedded'`),
288
- only toolbar actions remain above the app body; the title, scope, description, and
289
- footer are omitted. Variables and request states remain available.
290
-
291
- ## Present data with stories
292
-
293
- Include a story in every analytical app. Stories turn the exploration's findings
294
- into a presentation. Set the `story` prop on `<DataApp>`
295
- to enable **Present story** in the toolbar; use `<PresentStory>` directly for a
296
- custom shell. Start from the [data app starter](../examples/starter-data-app/index.tsx).
297
-
298
- Return one to four findings with a headline, a visual, and registered source
299
- evidence. Use `context.finding()` to bind the evidence.
300
-
301
- The callback receives the displayed data and its original input, including
302
- during refresh or failure. Derive the story from that snapshot so it agrees with
303
- the visible exploration. Initial loading, empty results, and initial errors have
304
- no story to present.
305
-
306
- ## Export displayed data as CSV
307
-
308
- Every request-backed `<DataApp>` requires `story` and `csvExport`. Select all distinct named datasets
309
- from the displayed snapshot so the built-in **Export** action is available
310
- whenever analytical results are shown. Include this alongside the app's story;
311
- setup and static shells may omit it. One dataset downloads directly as CSV. Multiple
312
- datasets offer individual CSV downloads and **Export all** as a ZIP archive.
313
-
314
- Example:
315
-
316
- ```tsx
317
- <DataApp
318
- config={config}
319
- dataContext={dataContext}
320
- request={result}
321
- story={story}
322
- csvExport={({ data, input }) => ({
323
- filename: `counts-${input.groupName || 'all'}.csv`,
324
- tables: [
325
- {
326
- name: 'Sample counts',
327
- columns: ['Group', 'Sample count'],
328
- rows: data.map(row => [row.groupName, row.sampleCount]),
329
- },
330
- ],
331
- })}
332
- >
333
- {(data, input) => <Results data={data} input={input} />}
334
- </DataApp>
335
- ```
38
+ Start with [app authoring](app-authoring.md) and the
39
+ [single-file starter](../examples/starter-data-app/index.tsx).
@@ -0,0 +1,52 @@
1
+ # Stories and CSV export
2
+
3
+ Every `<DataApp>` from `/react` supplies both `story` and `datasets`, derived from
4
+ the displayed snapshot. Story and export selectors do not run before a result is available.
5
+ Setup and static screens use `<DataApp>` from `/react/ui`; they may omit both
6
+ or pass a direct `CsvExport` object.
7
+
8
+ ## Present data with stories
9
+
10
+ Set `story` on `<DataApp>` to enable **Present story** in the toolbar.
11
+ For custom shells, `/react/ui` provides `<PresentStory>`; see [direct UI composition](ui.md). Start from the [data app starter](../examples/starter-data-app/index.tsx).
12
+
13
+ Return one to four findings with a headline, a visual, and registered source
14
+ evidence. Set `evidence` to the registered dataset or metric binding. The app
15
+ validates that every finding belongs to its view.
16
+
17
+ The callback receives the displayed source and its original input, including
18
+ during refresh or failure. Derive the story from that snapshot so it agrees with
19
+ the visible exploration. Initial loading, empty results, and initial errors have
20
+ no story to present; the toolbar keeps its story action visible and disabled.
21
+
22
+ ## Export displayed data as CSV
23
+
24
+ Select all distinct named datasets from the displayed snapshot. One dataset downloads directly as CSV. Multiple
25
+ datasets offer individual CSV downloads and **Export all** as a ZIP archive.
26
+ While no displayed snapshot is available, the export action stays visible and disabled.
27
+
28
+ Declare [datasets](widgets.md#declare-datasets-and-metrics) once and pass
29
+ `datasets={[countries, otherDataset]}` to `<DataApp>`. The toolbar
30
+ exports those datasets with raw values and a safe filename derived from the
31
+ displayed scope. List every distinct dataset the app shows.
32
+
33
+ Reuse bound metrics in story visuals:
34
+
35
+ ```tsx
36
+ story={snapshot => {
37
+ const totalReading = total.read(snapshot);
38
+ return [{
39
+ id: 'total',
40
+ headline: `Tracked identities: ${formatMetric(totalReading.value.current, total.definition.format)}`,
41
+ context: activityView.scope(snapshot),
42
+ visual: <MetricWidget metric={total} source={snapshot} />,
43
+ evidence: total,
44
+ }];
45
+ }}
46
+ ```
47
+
48
+ Use the [format helpers](formatting-and-appearance.md) for story prose.
49
+
50
+ Dataset accessors return raw values; do not preformat numbers as display
51
+ labels. Null and undefined become empty cells, while measured zero stays zero.
52
+ The helpers handle quoting and spreadsheet formula protection.
@@ -0,0 +1,55 @@
1
+ # Styling
2
+
3
+ Use components and typed props first: `<Stack>` for sections, `<Grid>` for peers,
4
+ and `<TextContent>` for prose. Standard widgets use [views and bindings](widgets.md);
5
+ custom controls and direct widget shells use [`/react/ui`](ui.md).
6
+
7
+ Configure theme, palette, accent, typography, density, radius, and elevation through
8
+ `config.appearance`; see [appearance](formatting-and-appearance.md). Call
9
+ `injectDataAppStyles()` before mounting. Use one `<DataApp>` per document.
10
+
11
+ ## Customize
12
+
13
+ Add app-owned classes through `className`. Render package components rather than copying their classes onto markup;
14
+ private descendants are not customization hooks. Public names and stable roots
15
+ are listed in the [typed contract](https://github.com/altertable-ai/data-app/blob/main/src/react/style-contract.ts).
16
+ CSS comments document [inherited defaults](https://github.com/altertable-ai/data-app/blob/main/src/react/tokens.css),
17
+ [interaction overrides](https://github.com/altertable-ai/data-app/blob/main/src/react/interaction.css),
18
+ and [focus overrides](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/Focus.css).
19
+ Widget-specific overrides live beside their uses in
20
+ [metrics](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/MetricWidget.css),
21
+ [bar charts](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/BarChart.css),
22
+ [code](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/QueryList.css),
23
+ and [layout](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/Grid.css).
24
+ The `--atbl-` prefix means Altertable.
25
+
26
+ ```css
27
+ .app-action {
28
+ --atbl-control-height: 40px;
29
+ --atbl-focus-color: var(--atbl-accent);
30
+ }
31
+ ```
32
+
33
+ Set tokens on `:root` for the document or on a component for local overrides.
34
+ Inherited tokens are usable in app CSS. Optional overrides fall back where they
35
+ are consumed, so local colors, fonts, and spacing compose. Portals inherit from
36
+ their actual DOM ancestors. Normal unlayered app CSS overrides package rules.
37
+ `DataAppStyle` checks public inline custom-property names.
38
+
39
+ When overriding `--atbl-accent` directly, also choose `--atbl-on-accent` with
40
+ sufficient contrast. Prefer appearance configuration for brand and chart colors.
41
+
42
+ ## Native controls
43
+
44
+ Use package controls when possible. For a custom native control:
45
+
46
+ ```tsx
47
+ <button type="button" data-atbl-control="action" data-atbl-focus="ring">
48
+ Run
49
+ </button>
50
+ ```
51
+
52
+ These hooks provide cursor and focus styling; keep native semantics, accessible
53
+ names, disabled state, and keyboard behavior. `DataAppStyleHooks` checks hook
54
+ values. Use `inset` focus inside clipped surfaces and `group` when a wrapper owns
55
+ an input's outline. See [UI quality](ui-quality.md) for rendered verification.
@@ -0,0 +1,20 @@
1
+ # UI quality
2
+
3
+ Lead with a supported finding and visible scope. Explain each chart's purpose,
4
+ measures, units, and source evidence. Use package typography and formatters;
5
+ keep primary values prominent and preserve exact values in records and exports.
6
+
7
+ Compose with `<Stack>`, `<Grid>`, and `<TextContent>`; parents own spacing and
8
+ widgets own padding. Let labels and values wrap, and keep tables and menus
9
+ scrolling inside their frames. Use the [layout](layout.md) and [styling](styling.md)
10
+ guides for customization.
11
+
12
+ Keep static context visible during loading. Skeletonize only dynamic content;
13
+ refresh, errors, and retry retain the displayed result and scope. Loading
14
+ geometry should match ready content.
15
+
16
+ Verify a real app at phone and desktop widths, including 320px, in both themes
17
+ and with enlarged text. Exercise long labels, localized values, many filters,
18
+ and loading, empty, refresh, and error states. Check containment, contrast,
19
+ keyboard focus, date/menu navigation, modal dismissal, and reduced motion.
20
+ Inspect rendered screenshots as well as computed styles.
package/docs/ui.md ADDED
@@ -0,0 +1,41 @@
1
+ # Direct UI composition
2
+
3
+ Import from `@altertable/data-app/react/ui` for setup/static screens, custom
4
+ controls, or a deliberately custom shell. Standard data apps use declared views
5
+ and bound widgets from `/react`.
6
+
7
+ `<DataApp>` here renders a static screen and accepts optional direct CSV data.
8
+ Direct widget forms accept local values or rows. `<DataWidget>` supplies a generic
9
+ frame for deliberately custom content. Use `<GettingStarted>` for
10
+ connection setup. Fetched data belongs to a declared view; avoid inventing
11
+ request states to populate a shell.
12
+
13
+ Direct `<DateRangePicker>`, `<DimensionPicker>`, and `useAppVariables()` support
14
+ custom control ownership. The caller supplies values and change handlers.
15
+ Custom tables use `<DataTable>` and its cell helpers; controls and overlays
16
+ include `<SearchField>`, `<Combobox>`, `<Tabs>`, `<Sheet>`, and `<HelpPopover>`.
17
+
18
+ Standalone `<AboutData>`, glossary components, and `<PresentStory>` support
19
+ custom inspection and presentation. Supply registered context and evidence, and
20
+ derive findings from the displayed data. Standard widgets and `<DataApp>`
21
+ already own these experiences.
22
+
23
+ For framework mounting, `<DataAppProvider>` supplies shared requests and one inspection sheet. Wrap custom shells in it.
24
+ Call `injectDataAppStyles()` before mounting either entry; imports do not install
25
+ styles.
26
+
27
+ ## Charts
28
+
29
+ Choose a chart using the [visualization guide](widgets.md#choose-a-visualization).
30
+ Import `<BarChart>`, `<LineChart>`, `<AreaChart>`, `<PieChart>`, or `<ScatterChart>` from
31
+ `/react/ui`. Compose the visual inside `<VisualizationWidget dataset={dataset} source={result}>`
32
+ so rows, loading state, evidence, and inspection come from that dataset.
33
+ Pass ordered items with unique, nonblank IDs and finite numbers. Bar and pie
34
+ values must be nonnegative. Use `formatValue` for domain formatting.
35
+
36
+ Line and area charts show equally spaced samples; include missing periods in the
37
+ input. These charts space items evenly, so they do not represent irregular time
38
+ intervals. Distinguish a missing observation from measured zero when preparing
39
+ the samples. Pie slices represent mutually exclusive parts of one total; shares
40
+ use the sum of supplied items, so include Other when showing a subset of the
41
+ whole. Scatter points represent independent X/Y observations.
@@ -0,0 +1,98 @@
1
+ # Variables and filters
2
+
3
+ ## Time views and field filters
4
+
5
+ `createDataHooks(client).defineTimeView()` owns the `period` variable, calendar
6
+ controls, and displayed-period label. Declare `time: { contract, defaultValue }`,
7
+ an operation, `isEmpty` (a predicate), and `emptyFallback` (a title and optional
8
+ description for an empty result). With no additional variables, its default
9
+ input is the calendar request. With additional variables, it is `{ period, ...variables }`.
10
+ Supply an `input` mapper for a different operation shape and `bindings` to extract
11
+ nested period or field-filter inputs. Mappings must preserve the selected values.
12
+
13
+ `dimensionFilter()` from `/contract` requires exactly one option source: fixed `options` or a `facet`.
14
+ Use `defineFacetFilter()` to bind a facet operation and its typed input. `<DimensionPicker>` offers missing values separately and keeps selected values
15
+ available when they have zero matches.
16
+
17
+ ## Declare controls once
18
+
19
+ Use `textVariable()`, `selectVariable()`, and `dateRangeVariable()` in a view's
20
+ `variables`. `<DataApp view={view}>` generates their controls and keeps values in the URL.
21
+ Keep local search out of the operation input when it only filters loaded rows.
22
+
23
+ The [date range contract](contract.md#shared-date-ranges) owns source coverage,
24
+ maximum range, time zone, and comparison rules. `defineTimeView()` is the compact
25
+ path for a view whose primary input is a period. Use `defineDataView()` for fixed
26
+ snapshots or other input shapes.
27
+
28
+ ```ts
29
+ const activity = defineTimeView({
30
+ dataContext,
31
+ operation: 'activity',
32
+ time: { contract: calendar, defaultValue: { kind: 'preset', id: 'last-7' } },
33
+ variables: { region: textVariable({ key: 'region' }) },
34
+ input: values => ({
35
+ filters: { period: values.period, region: values.region },
36
+ }),
37
+ bindings: {
38
+ period: input => input.filters.period,
39
+ region: input => input.filters.region,
40
+ },
41
+ isEmpty: data => data.rows.length === 0,
42
+ emptyFallback: { title: 'No activity' },
43
+ });
44
+ ```
45
+
46
+ The operation, calendar, and row shape are app-owned. Bindings must extract
47
+ selected values unchanged.
48
+
49
+ ## URL state and custom controls
50
+
51
+ Relative date presets remain relative. Explicit dates use `start`, `end`, and
52
+ `compare` for the `period` variable; other date variables derive these keys from
53
+ their own key. Invalid URL values fall back to validated defaults. Do not use
54
+ reserved inspection, presentation, or navigation keys for app variables.
55
+
56
+ Choose push history for meaningful selections and replace history for typing.
57
+ Back/Forward restores the same values and controls. Use generated controls for
58
+ standard data apps. Direct pickers and standalone URL state belong to
59
+ [direct UI composition](ui.md).
60
+
61
+ ## Fixed options and query-backed facets
62
+
63
+ Declare fixed choices with `dimensionFilter()` from `/contract`:
64
+
65
+ ```ts
66
+ const region = dimensionFilter({
67
+ key: 'region',
68
+ label: 'Region',
69
+ valueType: 'string',
70
+ selection: 'multiple',
71
+ options: [{ value: 'Europe', label: 'Europe' }],
72
+ });
73
+ ```
74
+
75
+ For query-backed choices use the hooks factory's `defineFacetFilter()`. The
76
+ facet operation returns parsed dimension options. Its input mapper reads current
77
+ resolved variables, including other filters:
78
+
79
+ ```ts
80
+ const { defineFacetFilter, defineTimeView } = createDataHooks(client);
81
+ const region = defineFacetFilter({
82
+ key: 'region',
83
+ label: 'Region',
84
+ valueType: 'string',
85
+ selection: 'multiple',
86
+ facet: {
87
+ operation: 'regions',
88
+ input: values => ({ period: values.period as DateRangeRequest }),
89
+ },
90
+ });
91
+ ```
92
+
93
+ The client registry defines the `regions` operation and its typed period input;
94
+ import `DateRangeRequest` from `/contract`. Put `region` in the view's `variables`
95
+ alongside its period. Use `parseFacetOptions()` to validate facet results, and
96
+ `dimensionPredicate()` to build a bounded SQL filter from parsed selections.
97
+ For the operation's input parser, use `parseDimensionSelection(value, region)` from `/contract`.
98
+ A dimension without an explicit selection represents all members.