@altertable/data-app 0.61.0 → 0.63.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/AGENTS.md +4 -32
- package/CONTRIBUTING.md +63 -72
- package/README.md +14 -38
- package/dist/chunks/{contract-d9skd1n7.js → contract-8wdybxj7.js} +163 -16
- package/dist/chunks/contract-8wdybxj7.js.map +13 -0
- package/dist/chunks/{contract-ryyf6dme.js → contract-cxr9t12b.js} +2 -5
- package/dist/chunks/contract-cxr9t12b.js.map +10 -0
- package/dist/chunks/{contract-14vxdcrs.js → contract-farfe948.js} +17 -21
- package/dist/chunks/contract-farfe948.js.map +10 -0
- package/dist/chunks/contract-mev09s5v.js.map +2 -2
- package/dist/chunks/{contract-ehk50k7e.js → contract-tf8c3qpv.js} +34 -4
- package/dist/chunks/contract-tf8c3qpv.js.map +11 -0
- package/dist/chunks/{contract-zr4s2g7m.js → contract-tkc552ze.js} +75 -36
- package/dist/chunks/contract-tkc552ze.js.map +12 -0
- package/dist/chunks/{contract-nt819swq.js → contract-wd8qe3mt.js} +6 -1
- package/dist/chunks/{contract-nt819swq.js.map → contract-wd8qe3mt.js.map} +3 -3
- package/dist/chunks/contract-wz59z8pq.js.map +1 -1
- package/dist/chunks/{contract-7gpsee0v.js → contract-zr3jd7mr.js} +51 -22
- package/dist/chunks/contract-zr3jd7mr.js.map +12 -0
- package/dist/client/index.js +7 -6
- package/dist/client/index.js.map +1 -1
- package/dist/core/appearance.js +1 -1
- package/dist/core/contract.js +6 -4
- package/dist/core/contract.js.map +1 -1
- package/dist/embed/index.js +41 -9
- package/dist/embed/index.js.map +5 -4
- package/dist/local.js +172 -84
- package/dist/local.js.map +7 -8
- package/dist/react/embed/index.js +83 -99
- package/dist/react/embed/index.js.map +4 -5
- package/dist/react/index.js +5701 -324
- package/dist/react/index.js.map +74 -65
- package/dist/server.js +153 -81
- package/dist/server.js.map +6 -7
- package/dist/types/client/data-client.d.ts +33 -0
- package/dist/types/client/iframe.d.ts +14 -0
- package/dist/types/client/index.d.ts +4 -34
- package/dist/types/client/location.d.ts +4 -6
- package/dist/types/client/navigation.d.ts +2 -1
- package/dist/types/core/appearance.d.ts +7 -6
- package/dist/types/core/bridge.d.ts +2 -8
- package/dist/types/core/config.d.ts +1 -1
- package/dist/types/core/contract.d.ts +9 -56
- package/dist/types/core/format.d.ts +0 -1
- package/dist/types/core/messages.d.ts +11 -30
- package/dist/types/core/navigation.d.ts +11 -0
- package/dist/types/core/operation-types.d.ts +76 -0
- package/dist/types/core/operation.d.ts +21 -0
- package/dist/types/core/presentation.d.ts +7 -0
- package/dist/types/core/variables.d.ts +2 -2
- package/dist/types/embed/bridge.d.ts +12 -0
- package/dist/types/embed/host.d.ts +14 -5
- package/dist/types/embed/index.d.ts +6 -4
- package/dist/types/embed/source.d.ts +15 -0
- package/dist/types/embed/sql.d.ts +4 -0
- package/dist/types/react/content.d.ts +3 -3
- package/dist/types/react/embed/bridge.d.ts +19 -10
- package/dist/types/react/embed/index.d.ts +0 -2
- package/dist/types/react/hooks.d.ts +65 -64
- package/dist/types/react/index.d.ts +135 -2
- package/dist/types/react/injectStyles.d.ts +7 -0
- package/dist/types/react/shellStyles.d.ts +3 -0
- package/dist/types/react/styles.d.ts +8 -0
- package/dist/types/react/ui/AboutData.d.ts +0 -1
- package/dist/types/react/ui/AppFooter.d.ts +0 -1
- package/dist/types/react/ui/AppHeader.d.ts +0 -1
- package/dist/types/react/ui/AppLayout.d.ts +0 -1
- package/dist/types/react/ui/AppScope.d.ts +0 -1
- package/dist/types/react/ui/AppToolbar.d.ts +0 -1
- package/dist/types/react/ui/Breakdown.d.ts +0 -1
- package/dist/types/react/ui/Button.d.ts +0 -1
- package/dist/types/react/ui/Checkbox.d.ts +0 -1
- package/dist/types/react/ui/Combobox.d.ts +0 -1
- package/dist/types/react/ui/ComparisonVisual.d.ts +0 -1
- package/dist/types/react/ui/ContentSkeleton.d.ts +2 -5
- package/dist/types/react/ui/DataApp.d.ts +2 -2
- package/dist/types/react/ui/DataAppSkeleton.d.ts +7 -0
- package/dist/types/react/ui/DataBoundary.d.ts +0 -1
- package/dist/types/react/ui/DataSection.d.ts +2 -4
- package/dist/types/react/ui/DataTable.d.ts +2 -3
- package/dist/types/react/ui/DataViewToast.d.ts +0 -1
- package/dist/types/react/ui/DataWidget.d.ts +4 -14
- package/dist/types/react/ui/DateRangePicker.d.ts +0 -1
- package/dist/types/react/ui/DateTimeTooltip.d.ts +0 -1
- package/dist/types/react/ui/EmptyState.d.ts +2 -5
- package/dist/types/react/ui/GettingStarted.d.ts +0 -1
- package/dist/types/react/ui/GlossaryDefinition.d.ts +0 -1
- package/dist/types/react/ui/GlossaryExplanation.d.ts +0 -1
- package/dist/types/react/ui/GradientScroll.d.ts +0 -1
- package/dist/types/react/ui/Grid.d.ts +0 -1
- package/dist/types/react/ui/HelpPopover.d.ts +0 -2
- package/dist/types/react/ui/Kbd.d.ts +0 -1
- package/dist/types/react/ui/LiveControl.d.ts +0 -1
- package/dist/types/react/ui/MetricWidget.d.ts +0 -2
- package/dist/types/react/ui/PeriodSummary.d.ts +0 -1
- package/dist/types/react/ui/PresentStory.d.ts +0 -1
- package/dist/types/react/ui/QueryList.d.ts +0 -1
- package/dist/types/react/ui/Ranking.d.ts +0 -1
- package/dist/types/react/ui/RefreshControl.d.ts +0 -1
- package/dist/types/react/ui/RefreshRegion.d.ts +0 -1
- package/dist/types/react/ui/RequestHint.d.ts +0 -1
- package/dist/types/react/ui/SearchField.d.ts +0 -1
- package/dist/types/react/ui/SearchInput.d.ts +1 -2
- package/dist/types/react/ui/SearchMatch.d.ts +0 -1
- package/dist/types/react/ui/SelectableBarChart.d.ts +0 -1
- package/dist/types/react/ui/SelectionMark.d.ts +0 -1
- package/dist/types/react/ui/Sheet.d.ts +0 -1
- package/dist/types/react/ui/Skeleton.d.ts +0 -1
- package/dist/types/react/ui/Stack.d.ts +0 -1
- package/dist/types/react/ui/StatusPanel.d.ts +0 -1
- package/dist/types/react/ui/TableWidget.d.ts +2 -3
- package/dist/types/react/ui/Tabs.d.ts +0 -1
- package/dist/types/react/ui/TextContent.d.ts +4 -0
- package/dist/types/react/ui/TextWidget.d.ts +19 -0
- package/dist/types/react/ui/ThemeSelector.d.ts +0 -1
- package/dist/types/react/ui/Tooltip.d.ts +0 -1
- package/dist/types/react/ui/UpdatedAt.d.ts +0 -1
- package/dist/types/react/ui/VariableBar.d.ts +0 -1
- package/dist/types/react/ui/VisualizationWidget.d.ts +5 -13
- package/dist/types/react/ui/WidgetDisclosure.d.ts +0 -1
- package/dist/types/react/ui/WidgetViewTabs.d.ts +2 -3
- package/dist/types/react/ui/data-identifiers.d.ts +0 -1
- package/dist/types/react/ui/presentation.d.ts +19 -0
- package/dist/types/react/ui/useAppAppearance.d.ts +3 -0
- package/dist/types/react/ui/useDataAppPresentation.d.ts +3 -0
- package/dist/types/react/view-controls.d.ts +2 -2
- package/dist/types/react/view.d.ts +2 -2
- package/dist/{bootstrap.js → worker.js} +144 -12
- package/docs/app-authoring.md +60 -24
- package/docs/client.md +48 -19
- package/docs/contract.md +10 -10
- package/docs/embed.md +94 -26
- package/docs/hosted-apps.md +39 -0
- package/docs/local-data-apps.md +14 -0
- package/docs/react-embed.md +75 -24
- package/docs/react.md +118 -93
- package/docs/server-bun.md +4 -1
- package/docs/server.md +2 -2
- package/docs/worker.md +55 -0
- package/examples/starter-data-app/index.tsx +159 -0
- package/package.json +18 -12
- package/dist/chunks/contract-14vxdcrs.js.map +0 -10
- package/dist/chunks/contract-7gpsee0v.js.map +0 -11
- package/dist/chunks/contract-d9skd1n7.js.map +0 -12
- package/dist/chunks/contract-ehk50k7e.js.map +0 -10
- package/dist/chunks/contract-ryyf6dme.js.map +0 -10
- package/dist/chunks/contract-zr4s2g7m.js.map +0 -12
- package/dist/react/index.css +0 -4478
- package/dist/react.css.d.ts +0 -1
- package/dist/types/embed/shell.d.ts +0 -21
- package/dist/types/embed/standalone.d.ts +0 -1
- package/dist/types/react/embed/shell.d.ts +0 -10
- package/dist/types/react/ui/index.d.ts +0 -129
- package/docs/appearance.md +0 -30
- package/docs/bootstrap.md +0 -76
- package/docs/config.md +0 -21
- package/docs/format.md +0 -29
- package/docs/react-styles.md +0 -17
- package/docs/starter-agent-instructions.md +0 -53
package/docs/react.md
CHANGED
|
@@ -1,47 +1,26 @@
|
|
|
1
1
|
# React
|
|
2
2
|
|
|
3
3
|
Import hooks, components, and UI helpers from `@altertable/data-app/react`.
|
|
4
|
-
Import `@altertable/data-app/react/styles.css` once in the browser entry.
|
|
5
4
|
React 19.2 or newer and React DOM 19.2 or newer are peer dependencies.
|
|
6
5
|
|
|
6
|
+
## Mount the app
|
|
7
|
+
|
|
7
8
|
`mountDataApp({ config, component })` mounts into `#root`, sets the document
|
|
8
9
|
title and language, attaches navigation to an available iframe transport, and
|
|
9
|
-
installs
|
|
10
|
-
framework, wrap the app in
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
| Handle a request's loading, error, and stale data | [DataSection](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/DataSection.tsx) | DataBoundary, DataViewToast, EmptyState, StatusPanel, Skeleton |
|
|
24
|
-
| Show freshness and refresh | [UpdatedAt](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/UpdatedAt.tsx), [AppToolbar](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/AppToolbar.tsx) | RefreshRegion, LiveControl |
|
|
25
|
-
| Bind filters to the URL | [variables](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/variables.ts), [DateRangePicker](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/DateRangePicker.tsx) | Combobox, PeriodSummary, Tabs, useViewTab |
|
|
26
|
-
| Search a loaded collection | [searchItems](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/searchItems.ts), [SearchMatch](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/SearchMatch.tsx) | SearchField |
|
|
27
|
-
| Explain context, glossary, and queries | [AboutData](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/AboutData.tsx), [DataContext](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/data-context.ts) | [GlossaryDefinition](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/GlossaryDefinition.tsx), GlossaryExplanation, [defineDataIdentifiers](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/data-identifiers.tsx) |
|
|
28
|
-
| Present loaded findings | [PresentStory](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/PresentStory.tsx) | StoryFinding |
|
|
29
|
-
| Build custom controls and overlays | [Button](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/Button.tsx), [Sheet](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/Sheet.tsx) | IconButton, Tooltip, HelpPopover, Kbd |
|
|
30
|
-
|
|
31
|
-
## Bound views and widgets
|
|
32
|
-
|
|
33
|
-
| Definition | Runtime owns |
|
|
34
|
-
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
35
|
-
| `defineOperation` | Input/output validation, check inputs, query limits and cancellation; `query(name, sql)` accepts registered names and records executed evidence. |
|
|
36
|
-
| `defineDataView` | URL variables, operation input, emptiness and the primary date binding. `useView` connects the result and controls to `DataApp`. |
|
|
37
|
-
| `DataApp` | Header, variable bar, refresh state, stale-result notice and dimming, default inspection empty states. |
|
|
38
|
-
| `view.content` | One loading/ready layout. `result.select` never evaluates loading data; `result.metric` binds comparisons to the displayed input. |
|
|
39
|
-
| `context.metric` | Label, numeric format, glossary evidence and optional direction of improvement. |
|
|
40
|
-
| `WidgetViewTabs` | Valid, unique selection IDs and a required empty state per tab. |
|
|
41
|
-
|
|
42
|
-
SQL and business definitions belong to the app. Hosted adapters authorize every request; local development can use the CLI proxy. SQL disclosure also requires server permission.
|
|
43
|
-
|
|
44
|
-
A measured zero and unavailable data have different meanings. Metric readings use `null` for an unavailable previous value. The app defines whether a result is empty. `Breakdown` shows parts of a total; `Ranking` scales against its largest value. Percent formats accept ratios.
|
|
10
|
+
installs `<DataAppProvider>`. When mounting through another
|
|
11
|
+
framework, wrap the app in `<DataAppProvider>` yourself.
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { injectDataAppStyles, mountDataApp } from '@altertable/data-app/react';
|
|
15
|
+
|
|
16
|
+
injectDataAppStyles();
|
|
17
|
+
mountDataApp({ config, component: App });
|
|
18
|
+
```
|
|
19
|
+
|
|
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.
|
|
45
24
|
|
|
46
25
|
## Bind a view
|
|
47
26
|
|
|
@@ -58,7 +37,7 @@ import {
|
|
|
58
37
|
} from '@altertable/data-app/react';
|
|
59
38
|
import type { operations } from '#app/operations.ts';
|
|
60
39
|
import { calendar } from '#app/contracts.ts';
|
|
61
|
-
import { dataContext,
|
|
40
|
+
import { dataContext, trackedIdentities } from '#app/data-context.tsx';
|
|
62
41
|
import config from '#config';
|
|
63
42
|
|
|
64
43
|
const period = dateRangeVariable({
|
|
@@ -84,7 +63,7 @@ const featureEvidence = dataContext.evidence({
|
|
|
84
63
|
const content = activityView.content(result => (
|
|
85
64
|
<Grid columns={2}>
|
|
86
65
|
<MetricWidget
|
|
87
|
-
metric={
|
|
66
|
+
metric={trackedIdentities}
|
|
88
67
|
reading={result.metric(data => ({
|
|
89
68
|
current: data.count,
|
|
90
69
|
previous: data.previousCount,
|
|
@@ -117,33 +96,62 @@ function App() {
|
|
|
117
96
|
|
|
118
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.
|
|
119
98
|
|
|
120
|
-
|
|
99
|
+
Share the [date range contract](contract.md#shared-date-ranges) between the
|
|
100
|
+
operation parser and the view.
|
|
121
101
|
|
|
122
|
-
|
|
123
|
-
import { defineDateRangeContract } from '@altertable/data-app/contract';
|
|
124
|
-
export const calendar = defineDateRangeContract({
|
|
125
|
-
minDate: '2026-01-01',
|
|
126
|
-
maxRangeDays: 90,
|
|
127
|
-
timeZone: 'UTC',
|
|
128
|
-
});
|
|
129
|
-
// Server operation: input: calendar.parseRequest
|
|
130
|
-
```
|
|
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.
|
|
131
103
|
|
|
132
|
-
|
|
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.
|
|
133
106
|
|
|
134
|
-
|
|
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.
|
|
135
108
|
|
|
136
|
-
|
|
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.
|
|
137
110
|
|
|
138
|
-
|
|
111
|
+
Use the [app helpers](#reuse-app-helpers) for metric formats and values in tables,
|
|
112
|
+
charts, and custom views.
|
|
139
113
|
|
|
140
|
-
`defineDataContent` remains available for manually managed requests. Its optional `{ date: (input) => rangeRequest }` binds comparison readings.
|
|
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.
|
|
141
115
|
|
|
142
|
-
##
|
|
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.
|
|
143
151
|
|
|
144
152
|
```tsx
|
|
145
|
-
|
|
146
|
-
|
|
153
|
+
import { defineDataIdentifiers } from '@altertable/data-app/react';
|
|
154
|
+
|
|
147
155
|
const identifiers = defineDataIdentifiers({
|
|
148
156
|
tables: {
|
|
149
157
|
events: {
|
|
@@ -155,6 +163,20 @@ const identifiers = defineDataIdentifiers({
|
|
|
155
163
|
columns: { identity: { table: 'events', name: 'identity_uuid' } },
|
|
156
164
|
});
|
|
157
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
|
|
158
180
|
const context = createDataContext(queries)({
|
|
159
181
|
identifiers: identifiers.definitions,
|
|
160
182
|
description: (
|
|
@@ -179,7 +201,7 @@ const evidence = context.evidence({
|
|
|
179
201
|
glossaryIds: ['identities'],
|
|
180
202
|
queryNames: [queries.activity],
|
|
181
203
|
});
|
|
182
|
-
const
|
|
204
|
+
const trackedIdentities = context.metric({
|
|
183
205
|
id: 'actions',
|
|
184
206
|
glossaryId: 'identities',
|
|
185
207
|
label: 'Tracked identities',
|
|
@@ -193,56 +215,59 @@ const finding = context.finding({
|
|
|
193
215
|
});
|
|
194
216
|
```
|
|
195
217
|
|
|
196
|
-
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.
|
|
197
|
-
|
|
198
|
-
## Authoring constraints
|
|
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.
|
|
199
219
|
|
|
200
|
-
|
|
201
|
-
- Numeric metrics use `value={count} format={{ kind: "count" }}`. Custom formatted JSX or strings use `content={...}` instead of `value`.
|
|
202
|
-
- Supply `empty` to secondary `DataSection` requests or pass a bound `useView` result. A primary `DataApp` accepts it either from `useView` or as an explicit prop.
|
|
203
|
-
- For alternate views of the same bound result, pass `views={[{ id, label, render }]}` and `viewLabel` to `VisualizationWidget`. Its required `isEmpty` and `empty` apply to the whole result; the widget owns selection. Use `WidgetViewTabs` directly only when views have independent empty states.
|
|
204
|
-
- Variable URL keys cannot use `view`, `about`, `tab`, `present`, or `step`.
|
|
220
|
+
## Time views and field filters
|
|
205
221
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
`createDataHooks(client).defineTimeView` owns the `period` variable, calendar
|
|
222
|
+
`createDataHooks(client).defineTimeView()` owns the `period` variable, calendar
|
|
209
223
|
controls, and displayed-period label. Declare `time: { contract, defaultValue }`,
|
|
210
224
|
an operation, `isEmpty`, and `empty`. With no additional variables, its default
|
|
211
225
|
input is the calendar request. With additional variables, it is `{ period, ...variables }`.
|
|
212
226
|
Supply an `input` mapper for a different operation shape and `bindings` to extract
|
|
213
|
-
nested period or
|
|
227
|
+
nested period or field-filter inputs. Mappings must preserve the selected values.
|
|
214
228
|
|
|
215
|
-
`dimensionFilter` requires exactly one option source: fixed `options` or a `facet`.
|
|
216
|
-
Use `defineFacetFilter` to bind a facet operation and its typed input. The generated
|
|
217
|
-
|
|
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
|
|
218
232
|
missing values separately, and retains selected values absent from a result with
|
|
219
|
-
zero counts.
|
|
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.
|
|
220
248
|
|
|
221
249
|
## Findings and inspection
|
|
222
250
|
|
|
223
|
-
|
|
251
|
+
`<DataWidget>` composes a body, toolbar feedback, and footer. Widgets and their
|
|
224
252
|
inspection sheets render the same visual and controls. Keep interactive state
|
|
225
253
|
above both mounts when authoring custom children.
|
|
226
254
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
### Migration from the earlier runtime
|
|
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.
|
|
233
260
|
|
|
234
|
-
|
|
235
|
-
- `PresentStory` replaces `PlayStory`; provide `findings` with explicit `evidence`.
|
|
236
|
-
- `context.finding` replaces `context.storyStep`.
|
|
237
|
-
- Table pagination is enabled by default; use `pagination={false}` for complete tables.
|
|
261
|
+
## Present data with stories
|
|
238
262
|
|
|
239
|
-
|
|
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).
|
|
240
266
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
inspection, and narrow-layout defaults. `bun run test:browser` verifies desktop
|
|
244
|
-
and phone interactions. Fixtures are excluded from the published package.
|
|
267
|
+
Return one to four findings with a headline, a visual, and registered source
|
|
268
|
+
evidence. Use `context.finding()` to bind the evidence.
|
|
245
269
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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.
|
package/docs/server-bun.md
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# Local Bun server
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use this Bun server for local data apps. Start from the CLI scaffold or
|
|
4
|
+
[local data app starter](https://github.com/altertable-ai/data-app/tree/main/examples/starter-local-data-app).
|
|
5
|
+
|
|
6
|
+
Import `serveLocalApp()` and `localLakehouse()` from
|
|
4
7
|
`@altertable/data-app/server/bun`. This entry requires Bun; install `@types/bun`
|
|
5
8
|
when typechecking a Bun app.
|
|
6
9
|
|
package/docs/server.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Server
|
|
2
2
|
|
|
3
|
-
Import `createDataHandler` and the `RequestAccess` type from
|
|
3
|
+
Import `createDataHandler()` and the `RequestAccess` type from
|
|
4
4
|
`@altertable/data-app/server`. The handler accepts a Web `Request` and returns a
|
|
5
5
|
`Promise<Response>`. This entry can be imported in Node and Bun without loading
|
|
6
6
|
React or the Bun local-development adapter.
|
|
@@ -21,7 +21,7 @@ export const handleDataRequest = createDataHandler(
|
|
|
21
21
|
);
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
The app supplies `authenticate` and `lakehouseFor`, then routes `/api/data/*`
|
|
24
|
+
The app supplies `authenticate()` and `lakehouseFor()`, then routes `/api/data/*`
|
|
25
25
|
requests to the handler. Authorize every viewer and operation, and scope the
|
|
26
26
|
returned lakehouse to the viewer's permitted data. Origin and Fetch Metadata
|
|
27
27
|
checks reject cross-site browser requests; they do not authenticate viewers.
|
package/docs/worker.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Cloudflare Worker asset
|
|
2
|
+
|
|
3
|
+
`@altertable/data-app/worker` resolves to a precompiled, self-contained ESM Worker
|
|
4
|
+
script. Upload this one file as the deployment's main module. It includes the
|
|
5
|
+
bootstrap and HTML; no bundling, HTML generation,
|
|
6
|
+
runtime imports, or asset fetches are needed in the hosting backend.
|
|
7
|
+
|
|
8
|
+
This export is a script asset, not a Node.js or Bun API. Pin an exact package
|
|
9
|
+
version and commit the lockfile. Resolve and read the installed asset, then pass
|
|
10
|
+
its contents to your deployment tooling:
|
|
11
|
+
|
|
12
|
+
```js
|
|
13
|
+
import { readFile } from 'node:fs/promises';
|
|
14
|
+
|
|
15
|
+
const workerScript = await readFile(
|
|
16
|
+
new URL(import.meta.resolve('@altertable/data-app/worker')),
|
|
17
|
+
'utf8'
|
|
18
|
+
);
|
|
19
|
+
// Upload workerScript as worker.js, with main_module = "worker.js".
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The hosting backend retains ownership of domains, routes, deployment settings,
|
|
23
|
+
and these string bindings:
|
|
24
|
+
|
|
25
|
+
| Binding | Meaning | Example |
|
|
26
|
+
| ---------------- | -------------------------------------------------------------------------- | ------------------------------------------------------- |
|
|
27
|
+
| `DOMAIN_NAME` | Runtime domain without scheme, port, or trailing dot | `apps.example.net` |
|
|
28
|
+
| `PARENT_ORIGINS` | Space-separated trusted HTTP(S) origins; first exact origin is the default | `https://app.example.com https://*.preview.example.com` |
|
|
29
|
+
|
|
30
|
+
The Worker serves only `/` on a single preview label under `DOMAIN_NAME` matching
|
|
31
|
+
`/^(?=.{1,63}$)[a-z0-9]+(?:-[a-z0-9]+)+-app-[1-9][0-9]*$/`. Other hosts and paths
|
|
32
|
+
return 404. GET returns HTML; HEAD returns the same headers with no body. Other
|
|
33
|
+
methods return 405 with `Allow: GET, HEAD`.
|
|
34
|
+
|
|
35
|
+
`__altertable_parent` selects an **exact** parent origin only after validation
|
|
36
|
+
against `PARENT_ORIGINS`. It cannot add trust. HTTPS wildcard entries allow one
|
|
37
|
+
subdomain label, without a port, for staging previews. Their apex and deeper
|
|
38
|
+
subdomains are rejected. Exact origins cannot contain paths, trailing slashes,
|
|
39
|
+
credentials, queries, or fragments. Invalid, duplicate, or unapproved selections
|
|
40
|
+
return 403. Missing or invalid origin configuration returns 503; an absent
|
|
41
|
+
selection requires an exact default origin. Referrers and messages never supply
|
|
42
|
+
trust.
|
|
43
|
+
|
|
44
|
+
The Worker enforces a restrictive CSP with configured `frame-ancestors`,
|
|
45
|
+
`no-referrer`, and `nosniff`. App scripts load through the authenticated bridge. Apps own styling and
|
|
46
|
+
navigation. Bundle apps execute their operation registry in the browser and send
|
|
47
|
+
SQL through the [host query route](embed.md#sql-query-route); the Worker does not
|
|
48
|
+
resolve operation names or execute queries. Data requests require backend authorization.
|
|
49
|
+
|
|
50
|
+
If Terraform reads a file, copy the resolved `/worker` asset unchanged to that
|
|
51
|
+
file during deployment preparation. Configure `DOMAIN_NAME`, `PARENT_ORIGINS`,
|
|
52
|
+
domains, routes, and deployment settings in Terraform. Upgrade the host package
|
|
53
|
+
and Worker together for protocol changes.
|
|
54
|
+
Custom hosts that bundle their own bootstrap can use `startDataAppBootstrap()` from
|
|
55
|
+
[/embed](embed.md#trusted-bootstrap).
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { createDataClient } from '@altertable/data-app/client';
|
|
2
|
+
import type { DataAppConfig } from '@altertable/data-app/config';
|
|
3
|
+
import {
|
|
4
|
+
defineOperation,
|
|
5
|
+
defineQueryNames,
|
|
6
|
+
parseCount,
|
|
7
|
+
} from '@altertable/data-app/contract';
|
|
8
|
+
import {
|
|
9
|
+
createDataContext,
|
|
10
|
+
createDataHooks,
|
|
11
|
+
DataApp,
|
|
12
|
+
injectDataAppStyles,
|
|
13
|
+
mountDataApp,
|
|
14
|
+
MetricWidget,
|
|
15
|
+
textVariable,
|
|
16
|
+
} from '@altertable/data-app/react';
|
|
17
|
+
|
|
18
|
+
const queryNames = defineQueryNames({
|
|
19
|
+
sampleCountsByGroup: 'sample-counts-by-group',
|
|
20
|
+
});
|
|
21
|
+
function parseSampleCountFilter(value: unknown) {
|
|
22
|
+
if (
|
|
23
|
+
!value ||
|
|
24
|
+
typeof value !== 'object' ||
|
|
25
|
+
!('groupName' in value) ||
|
|
26
|
+
typeof value.groupName !== 'string' ||
|
|
27
|
+
value.groupName.length > 40
|
|
28
|
+
)
|
|
29
|
+
throw new Error('Expected a group name of at most 40 characters.');
|
|
30
|
+
return { groupName: value.groupName };
|
|
31
|
+
}
|
|
32
|
+
function parseSampleCounts(
|
|
33
|
+
value: unknown
|
|
34
|
+
): { groupName: string; sampleCount: number }[] {
|
|
35
|
+
if (!Array.isArray(value))
|
|
36
|
+
throw new Error('Expected sample counts by group.');
|
|
37
|
+
return value.map(row => {
|
|
38
|
+
if (!row || typeof row !== 'object' || typeof row.groupName !== 'string')
|
|
39
|
+
throw new Error('Expected a group name.');
|
|
40
|
+
return {
|
|
41
|
+
groupName: row.groupName,
|
|
42
|
+
sampleCount: parseCount(row.sampleCount),
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
const operations = {
|
|
47
|
+
sampleCountsByGroup: defineOperation({
|
|
48
|
+
queryNames,
|
|
49
|
+
input: parseSampleCountFilter,
|
|
50
|
+
output: parseSampleCounts,
|
|
51
|
+
checks: [
|
|
52
|
+
{ groupName: '' },
|
|
53
|
+
{ groupName: 'Alpha' },
|
|
54
|
+
{ groupName: 'missing' },
|
|
55
|
+
],
|
|
56
|
+
policy: { maxQueryRows: 10, maxDurationMs: 15000, exposeSql: true },
|
|
57
|
+
async run({ query }, { groupName }) {
|
|
58
|
+
// Portable sample data, not a production table. Escape the validated SQL literal.
|
|
59
|
+
const escapedGroupName = groupName.replaceAll("'", "''");
|
|
60
|
+
const queryResult = await query(
|
|
61
|
+
queryNames.sampleCountsByGroup,
|
|
62
|
+
`
|
|
63
|
+
WITH sample_counts(group_name, sample_count) AS (VALUES ('Alpha', 3), ('Beta', 0))
|
|
64
|
+
SELECT group_name, sample_count FROM sample_counts
|
|
65
|
+
WHERE '${escapedGroupName}' = '' OR group_name = '${escapedGroupName}'
|
|
66
|
+
ORDER BY group_name LIMIT 10`
|
|
67
|
+
);
|
|
68
|
+
return parseSampleCounts(
|
|
69
|
+
queryResult.rows.map(([groupName, sampleCount]) => ({
|
|
70
|
+
groupName,
|
|
71
|
+
sampleCount,
|
|
72
|
+
}))
|
|
73
|
+
);
|
|
74
|
+
},
|
|
75
|
+
}),
|
|
76
|
+
};
|
|
77
|
+
const appConfig: DataAppConfig = {
|
|
78
|
+
title: 'Sample counts',
|
|
79
|
+
scope: { organization: 'demo', environment: 'sample' },
|
|
80
|
+
appearance: { theme: 'system' },
|
|
81
|
+
};
|
|
82
|
+
const sampleDataContext = createDataContext(queryNames)({
|
|
83
|
+
description:
|
|
84
|
+
'Two SQL VALUES rows demonstrate the host query path. Replace them with inspected source data before publishing findings.',
|
|
85
|
+
glossary: {
|
|
86
|
+
sampleCount: {
|
|
87
|
+
term: 'Sample count',
|
|
88
|
+
definition: 'A fixture value: Alpha is 3 and Beta is a measured zero.',
|
|
89
|
+
queryNames: [queryNames.sampleCountsByGroup],
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
const { defineDataView, useView } = createDataHooks(
|
|
94
|
+
createDataClient({ operations })
|
|
95
|
+
);
|
|
96
|
+
const sampleCountsView = defineDataView({
|
|
97
|
+
operation: 'sampleCountsByGroup',
|
|
98
|
+
variables: {
|
|
99
|
+
groupName: textVariable({ key: 'group', label: 'Group', defaultValue: '' }),
|
|
100
|
+
},
|
|
101
|
+
input: ({ groupName }) => ({ groupName }),
|
|
102
|
+
describeInput: ({ groupName }) =>
|
|
103
|
+
groupName ? `group ${groupName}` : 'all groups',
|
|
104
|
+
isEmpty: sampleCounts => sampleCounts.length === 0,
|
|
105
|
+
empty: {
|
|
106
|
+
title: 'No matching groups',
|
|
107
|
+
description: 'Try Alpha, Beta, or clear the group filter.',
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
function App() {
|
|
111
|
+
const sampleCountsRequest = useView(sampleCountsView);
|
|
112
|
+
return (
|
|
113
|
+
<DataApp
|
|
114
|
+
config={appConfig}
|
|
115
|
+
dataContext={sampleDataContext}
|
|
116
|
+
request={sampleCountsRequest}
|
|
117
|
+
story={
|
|
118
|
+
sampleCountsRequest.snapshot?.data.length
|
|
119
|
+
? ({ data: sampleCounts, input: displayedInput }) =>
|
|
120
|
+
sampleCounts.map(({ groupName, sampleCount }) =>
|
|
121
|
+
sampleDataContext.finding({
|
|
122
|
+
id: `sample-count-${groupName}`,
|
|
123
|
+
headline: `${groupName} has ${sampleCount} samples`,
|
|
124
|
+
context: `Demonstration values for ${displayedInput.groupName || 'all groups'}.`,
|
|
125
|
+
visual: (
|
|
126
|
+
<MetricWidget
|
|
127
|
+
label="Sample count"
|
|
128
|
+
value={sampleCount}
|
|
129
|
+
format={{ kind: 'count' }}
|
|
130
|
+
/>
|
|
131
|
+
),
|
|
132
|
+
visualKind: 'metric',
|
|
133
|
+
evidence: {
|
|
134
|
+
id: `sample-count-${groupName}`,
|
|
135
|
+
glossaryIds: ['sampleCount'],
|
|
136
|
+
},
|
|
137
|
+
})
|
|
138
|
+
)
|
|
139
|
+
: undefined
|
|
140
|
+
}
|
|
141
|
+
>
|
|
142
|
+
{(sampleCounts, displayedInput) => (
|
|
143
|
+
<section aria-label="Sample results">
|
|
144
|
+
<h2>Sample counts</h2>
|
|
145
|
+
<p>Showing {displayedInput.groupName || 'all groups'}</p>
|
|
146
|
+
<ul>
|
|
147
|
+
{sampleCounts.map(({ groupName, sampleCount }) => (
|
|
148
|
+
<li key={groupName}>
|
|
149
|
+
{groupName}: {sampleCount}
|
|
150
|
+
</li>
|
|
151
|
+
))}
|
|
152
|
+
</ul>
|
|
153
|
+
</section>
|
|
154
|
+
)}
|
|
155
|
+
</DataApp>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
injectDataAppStyles();
|
|
159
|
+
mountDataApp({ config: appConfig, component: App });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@altertable/data-app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.63.0",
|
|
4
4
|
"description": "Contracts, transport, and React UI for Altertable data apps",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"altertable",
|
|
@@ -23,16 +23,18 @@
|
|
|
23
23
|
"url": "git+https://github.com/altertable-ai/data-app.git"
|
|
24
24
|
},
|
|
25
25
|
"workspaces": [
|
|
26
|
-
"examples/starter"
|
|
26
|
+
"examples/starter-local-data-app"
|
|
27
27
|
],
|
|
28
28
|
"files": [
|
|
29
29
|
"dist",
|
|
30
30
|
"docs",
|
|
31
|
+
"examples/starter-data-app",
|
|
31
32
|
"README.md",
|
|
32
33
|
"CONTRIBUTING.md",
|
|
33
34
|
"AGENTS.md"
|
|
34
35
|
],
|
|
35
36
|
"type": "module",
|
|
37
|
+
"sideEffects": false,
|
|
36
38
|
"exports": {
|
|
37
39
|
"./contract": {
|
|
38
40
|
"types": "./dist/types/core/contract.d.ts",
|
|
@@ -66,16 +68,12 @@
|
|
|
66
68
|
"types": "./dist/types/react/index.d.ts",
|
|
67
69
|
"import": "./dist/react/index.js"
|
|
68
70
|
},
|
|
69
|
-
"./react/styles.css": {
|
|
70
|
-
"types": "./dist/react.css.d.ts",
|
|
71
|
-
"default": "./dist/react/index.css"
|
|
72
|
-
},
|
|
73
71
|
"./embed": {
|
|
74
72
|
"types": "./dist/types/embed/index.d.ts",
|
|
75
73
|
"import": "./dist/embed/index.js"
|
|
76
74
|
},
|
|
77
|
-
"./
|
|
78
|
-
"default": "./dist/
|
|
75
|
+
"./worker": {
|
|
76
|
+
"default": "./dist/worker.js"
|
|
79
77
|
},
|
|
80
78
|
"./react/embed": {
|
|
81
79
|
"types": "./dist/types/react/embed/index.d.ts",
|
|
@@ -88,17 +86,20 @@
|
|
|
88
86
|
},
|
|
89
87
|
"scripts": {
|
|
90
88
|
"build": "bun run scripts/build.ts && tsc -p tsconfig.build.json && bun run scripts/build-declarations.ts",
|
|
91
|
-
"typecheck": "tsc --noEmit",
|
|
92
|
-
"lint": "oxlint --type-aware src tests scripts browser-tests",
|
|
89
|
+
"typecheck": "bun install --frozen-lockfile && tsc --noEmit",
|
|
90
|
+
"lint": "oxlint --type-aware src tests scripts browser-tests examples/starter-data-app",
|
|
93
91
|
"format": "oxfmt .",
|
|
94
92
|
"format:check": "oxfmt --check .",
|
|
95
93
|
"test": "bun test ./tests",
|
|
96
|
-
"check": "bun run build && bun run typecheck && bun run lint && bun run format:check && bun run test && bun run test:package && bun run test:starter",
|
|
94
|
+
"check": "bun run build && bun run typecheck && bun run lint && bun run lint:md && bun run check:links && bun run format:check && bun run test && bun run test:package && bun run test:starter",
|
|
97
95
|
"test:package": "bun run scripts/check-package.ts",
|
|
98
96
|
"check:workflows": "bash scripts/check-workflows.sh",
|
|
99
97
|
"test:browser": "playwright test --config browser-tests/playwright.config.ts",
|
|
100
98
|
"lint:fix": "oxlint --type-aware --fix src tests scripts browser-tests",
|
|
101
|
-
"test:starter": "bun run scripts/check-starter.ts"
|
|
99
|
+
"test:starter": "bun run scripts/check-starter.ts",
|
|
100
|
+
"lint:md": "markdownlint-cli2",
|
|
101
|
+
"check:links": "remark . --ext md --rc-path .remarkrc.json --frail --no-stdout",
|
|
102
|
+
"check:links:external": "remark . --ext md --rc-path .remarkrc-external.json --frail --no-stdout"
|
|
102
103
|
},
|
|
103
104
|
"dependencies": {
|
|
104
105
|
"@floating-ui/react": "0.27.20",
|
|
@@ -113,11 +114,16 @@
|
|
|
113
114
|
"@types/bun": "1.4.2",
|
|
114
115
|
"@types/react": "19.3.0",
|
|
115
116
|
"@types/react-dom": "19.3.0",
|
|
117
|
+
"markdownlint-cli2": "0.23.3",
|
|
116
118
|
"oxfmt": "0.70.0",
|
|
117
119
|
"oxlint": "1.85.0",
|
|
118
120
|
"oxlint-tsgolint": "7.0.2003",
|
|
119
121
|
"react": "19.3.0",
|
|
120
122
|
"react-dom": "19.3.0",
|
|
123
|
+
"remark-cli": "12.0.1",
|
|
124
|
+
"remark-gfm": "4.0.1",
|
|
125
|
+
"remark-lint-no-dead-urls": "2.0.1",
|
|
126
|
+
"remark-validate-links": "13.1.0",
|
|
121
127
|
"typescript": "7.0.2"
|
|
122
128
|
},
|
|
123
129
|
"peerDependencies": {
|