@altertable/data-app 0.64.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 (157) hide show
  1. package/AGENTS.md +40 -0
  2. package/CONTRIBUTING.md +37 -3
  3. package/README.md +2 -2
  4. package/dist/chunks/{contract-mwe7gnmh.js → contract-13j5zb3c.js} +110 -216
  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-awa2d5b1.js +272 -0
  9. package/dist/chunks/contract-awa2d5b1.js.map +12 -0
  10. package/dist/chunks/{contract-xtxza1fy.js → contract-e11m4y7f.js} +35 -71
  11. package/dist/chunks/contract-e11m4y7f.js.map +12 -0
  12. package/dist/chunks/{contract-zr3jd7mr.js → contract-h8a6f559.js} +100 -75
  13. package/dist/chunks/contract-h8a6f559.js.map +12 -0
  14. package/dist/chunks/contract-havnjmdr.js +13011 -0
  15. package/dist/chunks/contract-havnjmdr.js.map +99 -0
  16. package/dist/chunks/{contract-txjy0en2.js → contract-m6n8ctfc.js} +64 -72
  17. package/dist/chunks/contract-m6n8ctfc.js.map +10 -0
  18. package/dist/chunks/contract-pk603qj7.js +146 -0
  19. package/dist/chunks/contract-pk603qj7.js.map +10 -0
  20. package/dist/chunks/{contract-yxbjea23.js → contract-rfbakka4.js} +179 -2
  21. package/dist/chunks/contract-rfbakka4.js.map +13 -0
  22. package/dist/client/index.js +11 -6
  23. package/dist/client/index.js.map +1 -1
  24. package/dist/core/appearance.js +1 -1
  25. package/dist/core/contract.js +13 -3
  26. package/dist/core/contract.js.map +1 -1
  27. package/dist/embed/index.js +25 -13
  28. package/dist/embed/index.js.map +3 -3
  29. package/dist/local.js +1 -1
  30. package/dist/local.js.map +2 -2
  31. package/dist/react/embed/index.js +5 -2
  32. package/dist/react/embed/index.js.map +3 -3
  33. package/dist/react/index.js +2532 -11273
  34. package/dist/react/index.js.map +16 -88
  35. package/dist/react/ui/index.js +1614 -0
  36. package/dist/react/ui/index.js.map +22 -0
  37. package/dist/server.js +1 -1
  38. package/dist/server.js.map +2 -2
  39. package/dist/types/client/annotations.d.ts +23 -0
  40. package/dist/types/client/iframe.d.ts +2 -0
  41. package/dist/types/client/index.d.ts +2 -0
  42. package/dist/types/client/logger.d.ts +6 -0
  43. package/dist/types/core/annotations.d.ts +92 -0
  44. package/dist/types/core/appearance.d.ts +2 -2
  45. package/dist/types/core/bridge-endpoint.d.ts +21 -0
  46. package/dist/types/core/bridge.d.ts +137 -16
  47. package/dist/types/core/contract.d.ts +2 -0
  48. package/dist/types/core/logger.d.ts +12 -0
  49. package/dist/types/core/presentation.d.ts +2 -0
  50. package/dist/types/core/variables.d.ts +2 -5
  51. package/dist/types/embed/host.d.ts +6 -2
  52. package/dist/types/embed/index.d.ts +1 -0
  53. package/dist/types/embed/source.d.ts +1 -1
  54. package/dist/types/react/annotations/AnnotationBar.d.ts +24 -0
  55. package/dist/types/react/annotations/AnnotationControls.d.ts +10 -0
  56. package/dist/types/react/annotations/AnnotationEditor.d.ts +15 -0
  57. package/dist/types/react/annotations/AnnotationMarkers.d.ts +19 -0
  58. package/dist/types/react/annotations/AnnotationSelectionLayer.d.ts +12 -0
  59. package/dist/types/react/annotations/AnnotationTarget.d.ts +9 -0
  60. package/dist/types/react/annotations/AnnotationTooltip.d.ts +9 -0
  61. package/dist/types/react/annotations/AnnotationTrigger.d.ts +10 -0
  62. package/dist/types/react/annotations/annotation-editor-state.d.ts +56 -0
  63. package/dist/types/react/annotations/annotation-screenshot.d.ts +4 -0
  64. package/dist/types/react/annotations/annotation-targets.d.ts +33 -0
  65. package/dist/types/react/annotations/getAnnotationProps.d.ts +15 -0
  66. package/dist/types/react/annotations/styles.d.ts +3 -0
  67. package/dist/types/react/annotations/useAnnotationGeometry.d.ts +22 -0
  68. package/dist/types/react/annotations/useDataAppAnnotations.d.ts +19 -0
  69. package/dist/types/react/bindings.d.ts +110 -0
  70. package/dist/types/react/content.d.ts +24 -11
  71. package/dist/types/react/embed/bridge.d.ts +1 -1
  72. package/dist/types/react/hooks.d.ts +29 -787
  73. package/dist/types/react/index.d.ts +30 -104
  74. package/dist/types/react/source-owner.d.ts +2 -0
  75. package/dist/types/react/style-contract.d.ts +60 -0
  76. package/dist/types/react/style-validation.d.ts +2 -0
  77. package/dist/types/react/ui/AboutData.d.ts +9 -1
  78. package/dist/types/react/ui/AppHeader.d.ts +1 -2
  79. package/dist/types/react/ui/AppLayout.d.ts +3 -9
  80. package/dist/types/react/ui/AppToolbar.d.ts +6 -7
  81. package/dist/types/react/ui/AreaChart.d.ts +7 -0
  82. package/dist/types/react/ui/BarChart.d.ts +6 -0
  83. package/dist/types/react/ui/{ComparisonVisual.d.ts → Comparison.d.ts} +3 -3
  84. package/dist/types/react/ui/ContentSkeleton.d.ts +2 -0
  85. package/dist/types/react/ui/DataApp.d.ts +17 -53
  86. package/dist/types/react/ui/DataAppFrame.d.ts +42 -0
  87. package/dist/types/react/ui/DataBoundary.d.ts +4 -5
  88. package/dist/types/react/ui/DataSection.d.ts +6 -18
  89. package/dist/types/react/ui/DataSectionBoundary.d.ts +30 -0
  90. package/dist/types/react/ui/DataValue.d.ts +9 -0
  91. package/dist/types/react/ui/DataWidget.d.ts +2 -1
  92. package/dist/types/react/ui/DateTimeTooltip.d.ts +1 -1
  93. package/dist/types/react/ui/ExportControl.d.ts +4 -0
  94. package/dist/types/react/ui/HelpPopover.d.ts +3 -4
  95. package/dist/types/react/ui/InspectionContext.d.ts +2 -0
  96. package/dist/types/react/ui/InspectionProvider.d.ts +29 -0
  97. package/dist/types/react/ui/LineChart.d.ts +7 -0
  98. package/dist/types/react/ui/MetricWidget.d.ts +4 -3
  99. package/dist/types/react/ui/PieChart.d.ts +8 -0
  100. package/dist/types/react/ui/PresentStory.d.ts +2 -5
  101. package/dist/types/react/ui/RefreshControl.d.ts +1 -2
  102. package/dist/types/react/ui/ScatterChart.d.ts +17 -0
  103. package/dist/types/react/ui/SearchField.d.ts +1 -2
  104. package/dist/types/react/ui/SearchInput.d.ts +3 -1
  105. package/dist/types/react/ui/Sheet.d.ts +2 -1
  106. package/dist/types/react/ui/Skeleton.d.ts +5 -2
  107. package/dist/types/react/ui/StaticDataApp.d.ts +4 -0
  108. package/dist/types/react/ui/TableWidget.d.ts +19 -16
  109. package/dist/types/react/ui/Tabs.d.ts +6 -2
  110. package/dist/types/react/ui/TextWidget.d.ts +2 -1
  111. package/dist/types/react/ui/Toast.d.ts +8 -0
  112. package/dist/types/react/ui/Tooltip.d.ts +4 -3
  113. package/dist/types/react/ui/TrendChart.d.ts +5 -0
  114. package/dist/types/react/ui/UpdatedAt.d.ts +1 -1
  115. package/dist/types/react/ui/VisualizationWidget.d.ts +12 -13
  116. package/dist/types/react/ui/WidgetViewTabs.d.ts +1 -1
  117. package/dist/types/react/ui/chart-data.d.ts +25 -0
  118. package/dist/types/react/ui/csv-export.d.ts +19 -0
  119. package/dist/types/react/ui/data-context.d.ts +18 -7
  120. package/dist/types/react/ui/icons.d.ts +2 -0
  121. package/dist/types/react/ui/index.d.ts +74 -0
  122. package/dist/types/react/ui/metric.d.ts +1 -1
  123. package/dist/types/react/ui/presentation.d.ts +5 -1
  124. package/dist/types/react/ui/shortcuts.d.ts +7 -1
  125. package/dist/types/react/view-runtime.d.ts +29 -0
  126. package/dist/types/react/view.d.ts +21 -4
  127. package/dist/types/react/widgets.d.ts +79 -0
  128. package/dist/worker.js +381 -79
  129. package/docs/app-authoring.md +64 -30
  130. package/docs/data-context.md +89 -0
  131. package/docs/embed.md +1 -1
  132. package/docs/formatting-and-appearance.md +52 -0
  133. package/docs/hosted-apps.md +2 -15
  134. package/docs/layout.md +33 -0
  135. package/docs/local-data-apps.md +1 -1
  136. package/docs/react-embed.md +2 -2
  137. package/docs/react.md +20 -254
  138. package/docs/stories-and-export.md +52 -0
  139. package/docs/styling.md +55 -0
  140. package/docs/ui-quality.md +20 -0
  141. package/docs/ui.md +41 -0
  142. package/docs/variables.md +98 -0
  143. package/docs/views.md +127 -0
  144. package/docs/widgets.md +142 -0
  145. package/examples/starter-data-app/index.tsx +89 -44
  146. package/package.json +13 -4
  147. package/dist/chunks/contract-cxr9t12b.js +0 -18
  148. package/dist/chunks/contract-cxr9t12b.js.map +0 -10
  149. package/dist/chunks/contract-mwe7gnmh.js.map +0 -13
  150. package/dist/chunks/contract-txjy0en2.js.map +0 -10
  151. package/dist/chunks/contract-xtxza1fy.js.map +0 -12
  152. package/dist/chunks/contract-yxbjea23.js.map +0 -12
  153. package/dist/chunks/contract-zr3jd7mr.js.map +0 -12
  154. package/dist/types/react/ui/RefreshRegion.d.ts +0 -9
  155. package/dist/types/react/ui/SelectableBarChart.d.ts +0 -15
  156. package/docs/releasing.md +0 -79
  157. /package/dist/types/react/ui/{comparison.d.ts → metric-comparison.d.ts} +0 -0
@@ -0,0 +1,89 @@
1
+ # Data context and evidence
2
+
3
+ Set `dataContext: context` in each view. The app and its sections inherit the
4
+ view's description, glossary, and permitted query evidence. Render registered identifiers where `<DataIdentifier>` is used.
5
+
6
+ Physical source identifiers name inspected tables and columns. Glossary entries
7
+ explain business meaning. Query names link those definitions and displayed claims
8
+ to their execution evidence. Keep all three explicit.
9
+
10
+ ## Register source identifiers
11
+
12
+ Use `defineDataIdentifiers()` from `/react` to register exact catalog, schema,
13
+ table, and field names. Use `<DataIdentifier>` in descriptions and glossary
14
+ entries to render those source references consistently.
15
+
16
+ ```tsx
17
+ import { defineDataIdentifiers } from '@altertable/data-app/react';
18
+
19
+ const identifiers = defineDataIdentifiers({
20
+ tables: {
21
+ events: {
22
+ catalog: 'product_analytics',
23
+ schema: 'analytics',
24
+ name: 'events',
25
+ },
26
+ },
27
+ columns: { identity: { table: 'events', name: 'identity_uuid' } },
28
+ });
29
+ const { DataIdentifier } = identifiers;
30
+
31
+ <DataIdentifier id="tables.events" />;
32
+ <DataIdentifier id="columns.events.identity" />;
33
+ ```
34
+
35
+ Pass `identifiers.definitions` to `createDataContext()` to reuse the source
36
+ registry.
37
+
38
+ ## Bind evidence
39
+
40
+ ```tsx
41
+ const queries = defineQueryNames({ activity: 'feature-activity' });
42
+ // In the server operation: queryNames: queries
43
+ const context = createDataContext(queries)({
44
+ identifiers: identifiers.definitions,
45
+ description: (
46
+ <>
47
+ Explore activity in <DataIdentifier id="tables.events" />.
48
+ </>
49
+ ),
50
+ glossary: {
51
+ identities: {
52
+ term: 'Tracked identities',
53
+ definition: (
54
+ <>
55
+ Distinct <DataIdentifier id="columns.events.identity" /> values.
56
+ </>
57
+ ),
58
+ queryNames: [queries.activity],
59
+ },
60
+ },
61
+ });
62
+ const evidence = context.evidence({
63
+ id: 'identities',
64
+ glossaryIds: ['identities'],
65
+ queryNames: [queries.activity],
66
+ });
67
+ // activityView is declared with dataContext: context.
68
+ const trackedIdentities = activityView.metric(
69
+ {
70
+ id: 'identities',
71
+ glossaryId: 'identities',
72
+ format: { kind: 'count' },
73
+ },
74
+ data => ({ current: data.count })
75
+ );
76
+ const finding = context.finding({
77
+ id: 'activity',
78
+ headline: 'What people do',
79
+ visual: <ActivityChart />,
80
+ evidence: { id: 'activity-evidence', queryNames: [queries.activity] },
81
+ });
82
+ ```
83
+
84
+ Import `defineQueryNames()` from `/contract` and the context/identifier factories from `/react`. Use the same registry in `defineOperation({ queryNames: queries, ... })`.
85
+
86
+ Use `view.metric(definition, select)` to register and bind a metric in one call.
87
+ Declare dataset evidence references inside `view.dataset()`; the view registers
88
+ and validates them too.
89
+ Widgets and stories share its values and evidence; see [datasets and metrics](widgets.md#declare-datasets-and-metrics).
package/docs/embed.md CHANGED
@@ -73,7 +73,7 @@ app bundle; non-React apps use `createDataAppNavigation()` from `/client`.
73
73
 
74
74
  `attachDataAppBridge()` also supports connection mode for a host-owned iframe. Supply
75
75
  `connection: { type: 'origin', origin }` or `{ type: 'opaque', token }`, and an
76
- `onMessage` dispatcher. Both modes use the same transport. It returns `dispose()` and `setPresentation()` methods and owns source/origin checks, request
76
+ `onMessage` dispatcher. Both modes use the same transport. It returns `dispose()`, `setPresentation()`, and `setLogger()` methods and owns source/origin checks, request
77
77
  correlation, cancellation, bounded pending requests, and reconnection. Use source mode for bundle loading and token rotation. The opaque destination requires
78
78
  wildcard delivery, but incoming messages still require the exact iframe window,
79
79
  null origin, token, document, and session to match.
@@ -0,0 +1,52 @@
1
+ # Formatting and appearance
2
+
3
+ Use package helpers so a value has the same meaning in prose, tables, charts,
4
+ and stories. TypeScript and JSDoc describe options and supported values.
5
+
6
+ ## Format values
7
+
8
+ Import formatting helpers from `@altertable/data-app/format`.
9
+
10
+ | Helper | Use |
11
+ | ------------------- | ------------------------------------------------------ |
12
+ | `formatNumber()` | General numerical values |
13
+ | `formatCount()` | Nonnegative integer counts, optionally compact |
14
+ | `formatPercent()` | Ratios represented as a fraction, such as 0.12 for 12% |
15
+ | `formatMetric()` | A metric definition's count, ratio, or currency format |
16
+ | `formatDateRange()` | Inclusive calendar ranges |
17
+ | `pluralize()` | Count-dependent labels |
18
+
19
+ Missing values render distinctly from measured zero. Define metric formatting
20
+ once with `view.metric()`; comparisons derive from its displayed source. Previous
21
+ values, range labels, and favorable direction belong to that metric and displayed
22
+ input; see [data context](data-context.md) and [views](views.md).
23
+
24
+ Import `chartColor()` from `/react` to select colors from the configured palette.
25
+ `<PeriodSummary>` describes reporting periods; `<UpdatedAt>` and
26
+ `<DateTimeTooltip>` expose readable timestamps with exact-date details.
27
+ `<DataTableTimestamp>` and `<DataTableShare>` format custom table cells.
28
+
29
+ ## Configure identity and appearance
30
+
31
+ ```ts
32
+ import type { DataAppConfig } from '@altertable/data-app/config';
33
+
34
+ const config = {
35
+ title: 'Product activity',
36
+ scope: { organization: 'Acme', environment: 'Production' },
37
+ appearance: {
38
+ theme: 'system',
39
+ accentColor: '#405d47',
40
+ density: 'comfortable',
41
+ },
42
+ } satisfies DataAppConfig;
43
+ ```
44
+
45
+ Scope labels describe the configured connection; they do not grant access.
46
+ `dataAppTitle()` produces the scoped document title used by `mountDataApp()`.
47
+
48
+ Appearance controls the brand palette, typography, density, corner radius, and
49
+ elevation. Those settings apply consistently to the app instead of tuning each
50
+ widget. Theme preference is reader-owned in standalone apps. A trusted parent's
51
+ resolved theme takes precedence and hides local theme controls; see
52
+ [parent presentation](embed.md#parent-presentation).
@@ -4,7 +4,7 @@ Follow the [shared authoring flow](app-authoring.md) using [index.tsx](../exampl
4
4
  one file with public package imports; omit server files, HTML, credentials, and
5
5
  relative or app-alias imports.
6
6
 
7
- Replace the sample SQL, parsers, filters, data context, story, and configuration with
7
+ Replace the sample SQL, parsers, filters, data context, CSV export, story, and configuration with
8
8
  an exploration of the source data you inspected. The starter uses two SQL
9
9
  `VALUES` rows, so it needs no production table.
10
10
 
@@ -13,7 +13,7 @@ For execution details, see [browser-owned operations](client.md#browser-owned-op
13
13
  ## Convert a local data app
14
14
 
15
15
  1. Combine the app's operations and parsers, data context, views, story,
16
- configuration, and browser entry into one `index.tsx`, following the
16
+ CSV export, configuration, and browser entry into one `index.tsx`, following the
17
17
  [single-file starter](../examples/starter-data-app/index.tsx).
18
18
  2. Replace the HTTP client with `createDataClient({ operations })`, using the
19
19
  operation registry as a value. See [browser-owned operations](client.md#browser-owned-operations-for-bundle-apps)
@@ -24,16 +24,3 @@ For execution details, see [browser-owned operations](client.md#browser-owned-op
24
24
  4. Confirm the host can query the same catalogs, tables, and fields.
25
25
  [Verify the app](app-authoring.md#verify-the-app) in the hosted runtime against
26
26
  the local version's filters and findings.
27
-
28
- ## Preview in this repository
29
-
30
- ```fish
31
- bun install --frozen-lockfile
32
- bun run build
33
- bun browser-tests/server.ts
34
- ```
35
-
36
- Open [the starter preview](http://127.0.0.1:27418/starter-data-app).
37
- Its test host executes the sample SQL through the iframe bridge using SQLite.
38
-
39
- For checks, see [Contributing](../CONTRIBUTING.md).
package/docs/layout.md ADDED
@@ -0,0 +1,33 @@
1
+ # Layout contract
2
+
3
+ The shell owns page width, outer gutters, and header/body spacing. Use `<Stack>`
4
+ for sections, `<Grid>` for peer widgets, and `<GridItem>` for spans. Use
5
+ `<TextContent>` for prose. Widgets own their internal padding; their parents own
6
+ external spacing. Keep widget customization inside the widget so its parent can maintain consistent
7
+ spacing between sections and cards.
8
+
9
+ By default, section and widget gaps use `--atbl-layout-gap`, with density configured once
10
+ in `DataAppConfig.appearance`. The package owns wrapping and span collapse based
11
+ on the available container width and configured gap. Custom length values for
12
+ `--atbl-layout-gap`, `--atbl-space-sm`, and `--atbl-space-md` also drive span
13
+ collapse; a two-column span activates only when two minimum-width tracks fit.
14
+
15
+ ```tsx
16
+ <Stack>
17
+ <TextContent>
18
+ <h2>Activity</h2>
19
+ <p>Explore the sources behind this finding.</p>
20
+ </TextContent>
21
+ <Grid columns={3}>
22
+ <GridItem span={2}>{activityChart}</GridItem>
23
+ <GridItem>{activityMetric}</GridItem>
24
+ </Grid>
25
+ </Stack>
26
+ ```
27
+
28
+ ## Verify rendered layouts
29
+
30
+ Check computed section/widget gaps, sibling alignment, outer gutters, and
31
+ horizontal overflow at phone and desktop iframe widths. Check loading, empty,
32
+ error, ready, and stale states. Loading placeholders should use the same grid as
33
+ the ready content.
@@ -2,7 +2,7 @@
2
2
 
3
3
  Start from the CLI scaffold or the [local starter](https://github.com/altertable-ai/data-app/tree/main/examples/starter-local-data-app).
4
4
  Use the starter's README for setup and its AGENTS.md to find app-owned files.
5
- Replace the connectivity screen with the exploration and story from the
5
+ Replace the connectivity screen with the exploration, CSV export, and story from the
6
6
  [shared authoring flow](app-authoring.md).
7
7
 
8
8
  | Task | Documentation |
@@ -93,8 +93,8 @@ are mutually exclusive.
93
93
 
94
94
  ## Loading an embedded app
95
95
 
96
- Use `<DataAppSkeleton>` from `/react` while the host builds or starts an app.
97
- Call `injectDataAppShellStyles()` from `/react` before rendering the placeholder.
96
+ Use `<DataAppSkeleton>` from `/react/ui` while the host builds or starts an app.
97
+ Call `injectDataAppShellStyles()` from `/react/ui` before rendering the placeholder.
98
98
  The host owns when to show it and supplies any surrounding header or footer.
99
99
  `/react/embed` itself remains independent of UI components and styles.
100
100
 
package/docs/react.md CHANGED
@@ -5,10 +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>`. When mounting through another
11
- 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`.
12
10
 
13
11
  ```tsx
14
12
  import { injectDataAppStyles, mountDataApp } from '@altertable/data-app/react';
@@ -17,257 +15,25 @@ injectDataAppStyles();
17
15
  mountDataApp({ config, component: App });
18
16
  ```
19
17
 
20
- Importing the package does not inject styles. For server rendering, call the
21
- injector on the client before mounting or hydrating. With a restrictive CSP,
22
- pass a permitted nonce; the first call sets it for the document. No CSS loader
23
- 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.
24
21
 
25
- ## 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.
26
24
 
27
- ```tsx
28
- import { createDataClient } from '@altertable/data-app/client';
29
- import {
30
- createDataHooks,
31
- dateRangeVariable,
32
- DataApp,
33
- Grid,
34
- MetricWidget,
35
- VisualizationWidget,
36
- Ranking,
37
- } from '@altertable/data-app/react';
38
- import type { operations } from '#app/operations.ts';
39
- import { calendar } from '#app/contracts.ts';
40
- import { dataContext, trackedIdentities } from '#app/data-context.tsx';
41
- import config from '#config';
42
-
43
- const period = dateRangeVariable({
44
- key: 'period',
45
- contract: calendar,
46
- comparison: true,
47
- defaultValue: { kind: 'preset', id: 'last-30' },
48
- });
49
- const { defineDataView, useView } =
50
- createDataHooks(createDataClient<typeof operations>());
51
- const activityView = defineDataView({
52
- operation: 'activity',
53
- variables: { period },
54
- input: ({ period }) => period,
55
- date: { variable: 'period', input: input => input },
56
- isEmpty: data => data.features.length === 0,
57
- empty: { title: 'No activity in this range' },
58
- });
59
- const featureEvidence = dataContext.evidence({
60
- id: 'feature-use',
61
- queryNames: [dataContext.queryNames.activity],
62
- });
63
- const content = activityView.content(result => (
64
- <Grid columns={2}>
65
- <MetricWidget
66
- metric={trackedIdentities}
67
- reading={result.metric(data => ({
68
- current: data.count,
69
- previous: data.previousCount,
70
- }))}
71
- />
72
- <VisualizationWidget
73
- title="Feature use"
74
- evidence={featureEvidence}
75
- reading={result.select(data => data.features)}
76
- isEmpty={features => features.length === 0}
77
- empty={{ title: 'No features' }}
78
- skeleton={{ variant: 'ranking', rows: 6 }}
79
- >
80
- {features => <Ranking items={features} />}
81
- </VisualizationWidget>
82
- </Grid>
83
- ));
84
- function App() {
85
- const activity = useView(activityView);
86
- return (
87
- <DataApp
88
- config={config}
89
- dataContext={dataContext}
90
- request={activity}
91
- {...content}
92
- />
93
- );
94
- }
95
- ```
96
-
97
- 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.
98
-
99
- Share the [date range contract](contract.md#shared-date-ranges) between the
100
- operation parser and the view.
101
-
102
- `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.
103
-
104
- For large tables, use query-backed pagination with a stable sort and total
105
- count. Client pagination and search cover only the rows already returned.
106
-
107
- 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.
108
-
109
- `<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.
110
-
111
- Use the [app helpers](#reuse-app-helpers) for metric formats and values in tables,
112
- charts, and custom views.
113
-
114
- `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.
115
-
116
- ## Narrative text
117
-
118
- Use `<TextContent>` for prose within a page or custom layout, and `<TextWidget>`
119
- when the explanation belongs in a titled panel alongside other widgets.
120
-
121
- Give text a purpose: frame the question, explain how to interpret a comparison,
122
- qualify a finding, or suggest what to explore next. Choose the content for the
123
- reader's question and the decisions the exploration supports.
124
-
125
- For data-dependent text, use `reading={result.select((data, input) => ...)}` and
126
- provide `evidence`. Derive both the explanation and its scope from those displayed
127
- values so it stays consistent with the visualizations while filters change.
128
- Static instructions can use ordinary children; local filters should feed the same
129
- filtered data to the text and its related visualization.
130
-
131
- ## Reuse app helpers
132
-
133
- Use the shared helpers for common app tasks. Follow the entry points below to
134
- find their exports, types, and usage constraints.
135
-
136
- | Task | Entry point |
137
- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
138
- | 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) |
139
- | Choose chart colors | [Chart colors](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/chartColor.ts) |
140
- | Search items and highlight matches | [Search helpers](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/searchItems.ts) |
141
- | Read and synchronize URL query state | [URL state helpers](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/search.ts) |
142
- | Render timestamps, freshness, table shares, periods, and metric comparisons | [React exports](https://github.com/altertable-ai/data-app/blob/main/src/react/index.ts) |
143
- | Configure appearance and theme preferences | [Appearance helpers](https://github.com/altertable-ai/data-app/blob/main/src/core/appearance.ts) |
144
- | Build the app's scoped document title | [Configuration helpers](https://github.com/altertable-ai/data-app/blob/main/src/core/config.ts) |
145
-
146
- ## Register source identifiers
147
-
148
- Use `defineDataIdentifiers()` from `/react` to register exact catalog, schema,
149
- table, and field names. Use `<DataIdentifier>` in descriptions and glossary
150
- entries to render those source references consistently.
151
-
152
- ```tsx
153
- import { defineDataIdentifiers } from '@altertable/data-app/react';
154
-
155
- const identifiers = defineDataIdentifiers({
156
- tables: {
157
- events: {
158
- catalog: 'product_analytics',
159
- schema: 'analytics',
160
- name: 'events',
161
- },
162
- },
163
- columns: { identity: { table: 'events', name: 'identity_uuid' } },
164
- });
165
- const { DataIdentifier } = identifiers;
166
-
167
- <DataIdentifier id="tables.events" />;
168
- <DataIdentifier id="columns.events.identity" />;
169
- ```
170
-
171
- Pass `identifiers.definitions` to `createDataContext()` so the context carries the
172
- same source registry. Source identifiers name physical data; glossary entries
173
- explain its business meaning, and query evidence records how it was queried.
174
-
175
- ## Bind evidence
176
-
177
- ```tsx
178
- const queries = defineQueryNames({ activity: 'feature-activity' });
179
- // In the server operation: queryNames: queries
180
- const context = createDataContext(queries)({
181
- identifiers: identifiers.definitions,
182
- description: (
183
- <>
184
- Explore activity in <DataIdentifier id="tables.events" />.
185
- </>
186
- ),
187
- glossary: {
188
- identities: {
189
- term: 'Tracked identities',
190
- definition: (
191
- <>
192
- Distinct <DataIdentifier id="columns.events.identity" /> values.
193
- </>
194
- ),
195
- queryNames: [queries.activity],
196
- },
197
- },
198
- });
199
- const evidence = context.evidence({
200
- id: 'identities',
201
- glossaryIds: ['identities'],
202
- queryNames: [queries.activity],
203
- });
204
- const trackedIdentities = context.metric({
205
- id: 'actions',
206
- glossaryId: 'identities',
207
- label: 'Tracked identities',
208
- format: { kind: 'count' },
209
- });
210
- const finding = context.finding({
211
- id: 'activity',
212
- headline: 'What people do',
213
- visual: <ActivityChart />,
214
- evidence: { id: 'activity-evidence', queryNames: [queries.activity] },
215
- });
216
- ```
217
-
218
- 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.
219
-
220
- ## Time views and field filters
221
-
222
- `createDataHooks(client).defineTimeView()` owns the `period` variable, calendar
223
- controls, and displayed-period label. Declare `time: { contract, defaultValue }`,
224
- an operation, `isEmpty`, and `empty`. With no additional variables, its default
225
- input is the calendar request. With additional variables, it is `{ period, ...variables }`.
226
- Supply an `input` mapper for a different operation shape and `bindings` to extract
227
- nested period or field-filter inputs. Mappings must preserve the selected values.
228
-
229
- `dimensionFilter()` from `/contract` requires exactly one option source: fixed `options` or a `facet`.
230
- Use `defineFacetFilter()` to bind a facet operation and its typed input. The generated
231
- `<DimensionPicker>` preserves cached options during refresh and failure, offers
232
- missing values separately, and retains selected values absent from a result with
233
- zero counts. `<SelectableBarChart>` can share controlled selection with the picker.
234
-
235
- ## Preserve displayed results
236
-
237
- The callback in `view.content()` receives the displayed result and its original
238
- input during refreshes and failures. Use that input when labeling the data.
239
-
240
- Previous data and evidence are retained only when the input changes within the
241
- same operation. Switching to another operation shows its own cached response or
242
- an initial loading/error state; it never inherits another operation's result.
243
-
244
- Operation and facet caches are scoped to the `DataClient` instance. Hooks created
245
- from the same client share queries; separate clients do not share results, stale
246
- data, or cancellation even under one provider. Keep client instances stable
247
- across renders to preserve their cache.
248
-
249
- ## Findings and inspection
250
-
251
- `<DataWidget>` composes a body, toolbar feedback, and footer. Widgets and their
252
- inspection sheets render the same visual and controls. Keep interactive state
253
- above both mounts when authoring custom children.
254
-
255
- When a trusted parent supplies [parent presentation](embed.md#parent-presentation),
256
- `<DataApp>` follows its resolved theme and suppresses local theme controls,
257
- including in presentations. On an embedded surface (`surface: 'embedded'`),
258
- only toolbar actions remain above the app body; the title, scope, description, and
259
- footer are omitted. Variables and request states remain available.
260
-
261
- ## Present data with stories
262
-
263
- Stories turn the exploration's findings into a presentation. Set the `story` prop on `<DataApp>`
264
- to enable **Present story** in the toolbar; use `<PresentStory>` directly for a
265
- custom shell. Start from the [data app starter](../examples/starter-data-app/index.tsx).
25
+ ## Choose a task
266
26
 
267
- Return one to four findings with a headline, a visual, and registered source
268
- evidence. Use `context.finding()` to bind the evidence.
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) |
269
37
 
270
- The callback receives the displayed data and its original input, including
271
- during refresh or failure. Derive the story from that snapshot so it agrees with
272
- the visible exploration. Initial loading, empty results, and initial errors have
273
- no story to present.
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.