@tailor-platform/app-shell 1.18.0 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app-shell-core.css +2 -2
- package/dist/app-shell.d.ts +1108 -190
- package/dist/app-shell.js +13090 -10783
- package/dist/{appshell-Y1AYCuVJ.js → appshell-B4QkQvXF.js} +519 -392
- package/dist/fonts/inter.css +7 -7
- package/dist/fonts/noto-sans-jp.css +116 -116
- package/dist/style-entry.d.ts +1 -656
- package/dist/testing.d.ts +132 -97
- package/dist/testing.js +1 -1
- package/dist/vite-plugin.d.ts +132 -97
- package/package.json +15 -10
- package/skills/app-shell-patterns/SKILL.md +38 -27
- package/skills/app-shell-patterns/references/api/aggregations.md +131 -0
- package/skills/app-shell-patterns/references/api/create-ai-gateway-client.md +32 -2
- package/skills/app-shell-patterns/references/api/define-module.md +5 -5
- package/skills/app-shell-patterns/references/api/router/use-params.md +2 -2
- package/skills/app-shell-patterns/references/api/use-ai-chat.md +46 -8
- package/skills/app-shell-patterns/references/api/use-app-shell-config.md +4 -4
- package/skills/app-shell-patterns/references/api/use-app-shell-data.md +5 -5
- package/skills/app-shell-patterns/references/api/use-app-shell.md +2 -2
- package/skills/app-shell-patterns/references/api/use-auth.md +3 -3
- package/skills/app-shell-patterns/references/api/use-route-error.md +12 -12
- package/skills/app-shell-patterns/references/components/action-panel.md +57 -23
- package/skills/app-shell-patterns/references/components/activity-card.md +3 -3
- package/skills/app-shell-patterns/references/components/ai-chat.md +37 -3
- package/skills/app-shell-patterns/references/components/app-shell.md +6 -5
- package/skills/app-shell-patterns/references/components/badge.md +7 -3
- package/skills/app-shell-patterns/references/components/card.md +2 -2
- package/skills/app-shell-patterns/references/components/data-table.md +514 -77
- package/skills/app-shell-patterns/references/components/date-picker.md +2 -2
- package/skills/app-shell-patterns/references/components/default-sidebar.md +2 -2
- package/skills/app-shell-patterns/references/components/dialog.md +8 -0
- package/skills/app-shell-patterns/references/components/filter-rail.md +10 -3
- package/skills/app-shell-patterns/references/components/form.md +19 -9
- package/skills/app-shell-patterns/references/components/global-header-layout.md +31 -8
- package/skills/app-shell-patterns/references/components/item-list.md +191 -0
- package/skills/app-shell-patterns/references/components/layout.md +24 -17
- package/skills/app-shell-patterns/references/components/select.md +18 -0
- package/skills/app-shell-patterns/references/components/sidebar-group.md +9 -7
- package/skills/app-shell-patterns/references/components/sidebar-layout.md +74 -11
- package/skills/app-shell-patterns/references/components/sidebar-section.md +8 -8
- package/skills/app-shell-patterns/references/components/tabs.md +32 -2
- package/skills/app-shell-patterns/references/components/text.md +75 -38
- package/skills/app-shell-patterns/references/components/tree.md +342 -0
- package/skills/app-shell-patterns/references/components/with-guard.md +5 -5
- package/skills/app-shell-patterns/references/concepts/authentication.md +1 -1
- package/skills/app-shell-patterns/references/concepts/routing-navigation.md +3 -3
- package/skills/app-shell-patterns/references/concepts/sidebar-navigation.md +16 -14
- package/skills/app-shell-patterns/references/concepts/styling-theming.md +28 -20
- package/skills/app-shell-patterns/references/migrations.md +22 -2
- package/skills/app-shell-patterns/references/pages/document-detail.md +32 -25
- package/skills/app-shell-patterns/references/patterns/form-modal.md +4 -1
- package/skills/app-shell-patterns/references/patterns/interaction-multi-select.md +1 -1
- package/dist/semantic-contrast.test.ts +0 -227
- package/dist/theme-bridge.test.ts +0 -182
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Value aggregations
|
|
3
|
+
description: Reusable selector-based sum, average, minimum, maximum, and non-null count over row collections, with optional result-only transformation
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- GENERATED by docs-kit from docs-src/hooks/aggregations.docs.outline.md — do not edit by hand. -->
|
|
7
|
+
|
|
8
|
+
# Value aggregations
|
|
9
|
+
|
|
10
|
+
`aggregations` creates reusable callbacks that select values from a row collection and aggregate them. Each callback accepts `{ rows: readonly TRow[] }`; the module has no dependency on React, DataTable, or column definitions.
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
import { aggregations, type Aggregation } from "@tailor-platform/app-shell";
|
|
14
|
+
|
|
15
|
+
type Line = { quantity: number | null };
|
|
16
|
+
|
|
17
|
+
const sum = aggregations.sum((row: Line) => row.quantity);
|
|
18
|
+
sum({ rows: [{ quantity: 1 }, { quantity: 2 }, { quantity: null }] }); // 3
|
|
19
|
+
sum({ rows: [{ quantity: 4 }, { quantity: 5 }] }); // 9: no previous aggregate state
|
|
20
|
+
|
|
21
|
+
const summary: Aggregation<Line, string> = aggregations.sum(
|
|
22
|
+
(row: Line) => row.quantity,
|
|
23
|
+
(value) => (value === null ? "Unavailable" : `${value} units`),
|
|
24
|
+
);
|
|
25
|
+
summary({ rows: [{ quantity: 1 }, { quantity: 2 }] }); // "3 units"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Operations
|
|
29
|
+
|
|
30
|
+
| Factory | Result | Empty or all-nullish selected values |
|
|
31
|
+
| ------------------------------ | --------------------------------------- | ------------------------------------ |
|
|
32
|
+
| `aggregations.sum(selector)` | Sum of finite numbers | `0` |
|
|
33
|
+
| `aggregations.avg(selector)` | Average of finite numbers | `null` |
|
|
34
|
+
| `aggregations.min(selector)` | Minimum finite number | `null` |
|
|
35
|
+
| `aggregations.max(selector)` | Maximum finite number | `null` |
|
|
36
|
+
| `aggregations.count(selector)` | Count of non-nullish values of any type | `0` |
|
|
37
|
+
|
|
38
|
+
A selector is required. It receives one row at a time and determines the values to aggregate. Numeric selectors must return `number | null | undefined`; count selectors may return any type. There is no automatic field lookup or connection to a column's `id`, `accessor`, or rendered content.
|
|
39
|
+
|
|
40
|
+
Each factory returns an `Aggregation<TRow, TResult>` callback. Pass a read-only or mutable row array through `{ rows }`; aggregation does not mutate it or retain state between invocations. Selectors and transformers must be pure and must not mutate rows. Exceptions from either callback propagate to the caller.
|
|
41
|
+
|
|
42
|
+
All operations skip selected `null` and `undefined` values. Numeric operations also validate selected values at runtime and return `null` for the entire result if any other value is not a finite JavaScript number, including numeric strings, booleans, dates, `NaN`, and infinities. They do not coerce values, compare dates or strings, or return a partial result. Average divides by the number of non-nullish numeric values, not the row count.
|
|
43
|
+
|
|
44
|
+
Calculations use ordinary JavaScript numbers. A non-finite calculated result returns `null`; for average, this includes an intermediate sum overflow even when the mathematical average could be finite. These functions do not provide arbitrary-precision arithmetic.
|
|
45
|
+
|
|
46
|
+
`count(selector)` counts selected `0`, `false`, and `""` as present values. It counts non-nullish values, not truthy values; use `rows.length` when every row should count.
|
|
47
|
+
|
|
48
|
+
## Optional transformer
|
|
49
|
+
|
|
50
|
+
Pass a second function to transform the final result:
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
const averageText = aggregations.avg(
|
|
54
|
+
(row: Line) => row.quantity,
|
|
55
|
+
(value) => value?.toFixed(2) ?? "–",
|
|
56
|
+
);
|
|
57
|
+
averageText({ rows: [{ quantity: 1 }, { quantity: null }, { quantity: 2 }] }); // "1.50"
|
|
58
|
+
|
|
59
|
+
const detailed = aggregations.sum(
|
|
60
|
+
(row: Line) => row.quantity,
|
|
61
|
+
(total) => ({ total, valid: total !== null }),
|
|
62
|
+
);
|
|
63
|
+
detailed({ rows: [{ quantity: 1 }, { quantity: 2 }] }); // { total: 3, valid: true }
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The transformer is called exactly once per aggregation invocation, including for empty or invalid input. Numeric transformers receive `number | null`; count transformers receive `number`. Its return type becomes the callback's result type, and may be text, JSX, an object, or another value. The aggregation library does not interpret the transformed result.
|
|
67
|
+
|
|
68
|
+
A transformer receives only the result, not rows, selected values, columns, or UI context. Filter `rows` before invoking the callback when the calculation needs a different input set.
|
|
69
|
+
|
|
70
|
+
For detached callbacks, annotate the selector's row parameter as above so the row and transformed result types can both be inferred. Row types are not restricted to objects; for example, `aggregations.sum<number>((value) => value)({ rows: [1, 2] })` returns `3`.
|
|
71
|
+
|
|
72
|
+
## Composing with business validation
|
|
73
|
+
|
|
74
|
+
An aggregation is an ordinary callback, so a wrapper can validate the supplied rows before calling it. Keep business rules in the wrapper, numeric calculation in the aggregation, and result formatting in the transformer. The transformer is not an error handler: it receives `null` for invalid numeric results but has no row context or business-error details; selector exceptions still propagate.
|
|
75
|
+
|
|
76
|
+
For example, reject mixed currencies before adding monetary amounts:
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
type MoneyLine = { amount: number | null; currency: string };
|
|
80
|
+
|
|
81
|
+
const sumAmount = aggregations.sum(
|
|
82
|
+
(row: MoneyLine) => row.amount,
|
|
83
|
+
(total) => (total === null ? "Invalid amount" : total.toLocaleString("en-US")),
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const moneyFooter = (ctx: { rows: readonly MoneyLine[] }) => {
|
|
87
|
+
const currencies = new Set(
|
|
88
|
+
ctx.rows.filter((row) => row.amount != null).map((row) => row.currency),
|
|
89
|
+
);
|
|
90
|
+
if (currencies.size > 1) {
|
|
91
|
+
return `Mixed currencies: ${[...currencies].join(", ")}`;
|
|
92
|
+
}
|
|
93
|
+
return sumAmount(ctx);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
moneyFooter({
|
|
97
|
+
rows: [
|
|
98
|
+
{ amount: 1000, currency: "JPY" },
|
|
99
|
+
{ amount: 500, currency: "JPY" },
|
|
100
|
+
],
|
|
101
|
+
}); // "1,500"
|
|
102
|
+
moneyFooter({
|
|
103
|
+
rows: [
|
|
104
|
+
{ amount: 1000, currency: "JPY" },
|
|
105
|
+
{ amount: 500, currency: "USD" },
|
|
106
|
+
],
|
|
107
|
+
}); // "Mixed currencies: JPY, USD"
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The guard ignores nullish amounts because they do not contribute to the sum. A rejected currency combination returns directly without evaluating the selector or transformer. Neither the wrapper nor the helper converts currencies or provides arbitrary-precision arithmetic.
|
|
111
|
+
|
|
112
|
+
Pass `moneyFooter` directly as a column's `footer`. See the [runnable DataTable example](../components/data-table.md#composing-with-business-validation) to toggle mixed currencies and observe the composed behavior.
|
|
113
|
+
|
|
114
|
+
## DataTable integration
|
|
115
|
+
|
|
116
|
+
Pass an aggregation callback directly to a column footer. It is an ordinary footer renderer that reads the current `ctx.rows`; DataTable needs no aggregation-specific handling:
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
import { aggregations, createColumnHelper } from "@tailor-platform/app-shell";
|
|
120
|
+
|
|
121
|
+
const { column } = createColumnHelper<{ quantity: number | null }>();
|
|
122
|
+
column({
|
|
123
|
+
id: "quantity",
|
|
124
|
+
type: "number",
|
|
125
|
+
footer: aggregations.sum((row) => row.quantity),
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Inline selectors infer the row type from the surrounding footer definition. The selector is the source of truth even when the column has an `accessor`; write the same calculation in the selector if the footer should aggregate a computed cell value.
|
|
130
|
+
|
|
131
|
+
Use a transformer for simple footer text or JSX. DataTable only accepts callback results it can render; object-producing aggregations remain available for non-UI use. For the current-page scope, loading behavior, and custom renderers, see [DataTable column footers](../components/data-table.md#column-footers).
|
|
@@ -7,7 +7,7 @@ description: Create a low-level AI Gateway client that reuses AppShell authentic
|
|
|
7
7
|
|
|
8
8
|
# createAIGatewayClient
|
|
9
9
|
|
|
10
|
-
Creates a small AI Gateway transport client for
|
|
10
|
+
Creates a small AI Gateway transport client for chat completions and local function tool calls.
|
|
11
11
|
|
|
12
12
|
## Signature
|
|
13
13
|
|
|
@@ -43,6 +43,7 @@ interface AIGatewayClient {
|
|
|
43
43
|
The iterable yields completion events:
|
|
44
44
|
|
|
45
45
|
- `text-delta` — append `event.text` to build the assistant response
|
|
46
|
+
- `tool-call` — a local function tool call requested by the model
|
|
46
47
|
- `done` — terminal event with an optional `finishReason`
|
|
47
48
|
|
|
48
49
|
## Related Types
|
|
@@ -56,11 +57,33 @@ type AIGatewayChatMessage =
|
|
|
56
57
|
| {
|
|
57
58
|
role: "assistant";
|
|
58
59
|
content?: string;
|
|
60
|
+
toolCalls?: AIGatewayToolCall[];
|
|
61
|
+
}
|
|
62
|
+
| {
|
|
63
|
+
role: "tool";
|
|
64
|
+
toolCallId: string;
|
|
65
|
+
content: string;
|
|
59
66
|
};
|
|
60
67
|
|
|
68
|
+
interface AIGatewayToolCall {
|
|
69
|
+
id: string;
|
|
70
|
+
name: string;
|
|
71
|
+
argumentsText: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface AIGatewayFunctionTool {
|
|
75
|
+
type: "function";
|
|
76
|
+
function: {
|
|
77
|
+
name: string;
|
|
78
|
+
description?: string;
|
|
79
|
+
parameters: Record<string, unknown>;
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
61
83
|
interface AIGatewayChatRequest {
|
|
62
84
|
model: string;
|
|
63
85
|
messages: AIGatewayChatMessage[];
|
|
86
|
+
tools?: AIGatewayFunctionTool[];
|
|
64
87
|
signal?: AbortSignal;
|
|
65
88
|
}
|
|
66
89
|
|
|
@@ -69,6 +92,12 @@ type AIChatCompletionEvent =
|
|
|
69
92
|
type: "text-delta";
|
|
70
93
|
text: string;
|
|
71
94
|
}
|
|
95
|
+
| {
|
|
96
|
+
type: "tool-call";
|
|
97
|
+
toolCallId: string;
|
|
98
|
+
toolName: string;
|
|
99
|
+
argumentsText: string;
|
|
100
|
+
}
|
|
72
101
|
| {
|
|
73
102
|
type: "done";
|
|
74
103
|
finishReason?: string;
|
|
@@ -106,7 +135,8 @@ console.log(text);
|
|
|
106
135
|
## Notes
|
|
107
136
|
|
|
108
137
|
- AppShell chooses the appropriate AI Gateway transport automatically
|
|
109
|
-
-
|
|
138
|
+
- Local tools are sent as normalized `type: "function"` definitions; provider-native tools are not supported
|
|
139
|
+
- The low-level API stays event-based so hooks can layer streaming UI and tool loops on top
|
|
110
140
|
- `request.signal` is passed through so callers can abort in-flight work
|
|
111
141
|
|
|
112
142
|
## Related
|
|
@@ -100,11 +100,11 @@ A module object that can be passed to the `modules` prop of `AppShell`.
|
|
|
100
100
|
### Basic Module
|
|
101
101
|
|
|
102
102
|
```typescript
|
|
103
|
-
import { defineModule } from "@tailor-platform/app-shell";
|
|
103
|
+
import { defineModule, Text } from "@tailor-platform/app-shell";
|
|
104
104
|
|
|
105
105
|
const dashboardModule = defineModule({
|
|
106
106
|
path: "dashboard",
|
|
107
|
-
component: () => <h1>Dashboard</h1>,
|
|
107
|
+
component: () => <Text.h1>Dashboard</Text.h1>,
|
|
108
108
|
resources: [],
|
|
109
109
|
});
|
|
110
110
|
```
|
|
@@ -183,14 +183,14 @@ const adminModule = defineModule({
|
|
|
183
183
|
### Module with Error Boundary
|
|
184
184
|
|
|
185
185
|
```typescript
|
|
186
|
-
import { useRouteError } from "@tailor-platform/app-shell";
|
|
186
|
+
import { Text, useRouteError } from "@tailor-platform/app-shell";
|
|
187
187
|
|
|
188
188
|
const ErrorBoundary = () => {
|
|
189
189
|
const error = useRouteError() as Error;
|
|
190
190
|
return (
|
|
191
191
|
<div>
|
|
192
|
-
<h1>Module Error</h1>
|
|
193
|
-
<p>{error.message}</p>
|
|
192
|
+
<Text.h1>Module Error</Text.h1>
|
|
193
|
+
<Text.p>{error.message}</Text.p>
|
|
194
194
|
</div>
|
|
195
195
|
);
|
|
196
196
|
};
|
|
@@ -182,7 +182,7 @@ function ProductEdit() {
|
|
|
182
182
|
|
|
183
183
|
return (
|
|
184
184
|
<form>
|
|
185
|
-
<h1>Edit Product {id}</h1>
|
|
185
|
+
<Text.h1>Edit Product {id}</Text.h1>
|
|
186
186
|
{/* form fields */}
|
|
187
187
|
</form>
|
|
188
188
|
);
|
|
@@ -203,7 +203,7 @@ function DeleteConfirmation() {
|
|
|
203
203
|
|
|
204
204
|
return (
|
|
205
205
|
<div>
|
|
206
|
-
<p>Delete product {id}?</p>
|
|
206
|
+
<Text.p>Delete product {id}?</Text.p>
|
|
207
207
|
<button onClick={handleDelete}>Confirm</button>
|
|
208
208
|
</div>
|
|
209
209
|
);
|
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: useAIChat
|
|
3
|
-
description:
|
|
3
|
+
description: AI Gateway chat hook with optional local tools
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
<!-- GENERATED by docs-kit from docs-src/hooks/use-ai-chat.docs.outline.md — do not edit by hand. -->
|
|
7
7
|
|
|
8
8
|
# useAIChat
|
|
9
9
|
|
|
10
|
-
React hook for
|
|
10
|
+
React hook for AI Gateway chat on top of `createAIGatewayClient`, with optional local tool support.
|
|
11
11
|
|
|
12
12
|
## Signature
|
|
13
13
|
|
|
14
14
|
```typescript
|
|
15
|
-
const useAIChat: (config: {
|
|
15
|
+
const useAIChat: (config: {
|
|
16
|
+
client: AIGatewayClient;
|
|
17
|
+
model: string;
|
|
18
|
+
tools?: Record<string, AILocalTool>;
|
|
19
|
+
}) => {
|
|
16
20
|
messages: AIChatMessage[];
|
|
21
|
+
toolCalls: { id: string; name: string }[];
|
|
17
22
|
status: "ready" | "submitted" | "streaming" | "error";
|
|
18
23
|
error?: Error;
|
|
19
24
|
sendMessage: (message: string) => Promise<boolean>;
|
|
@@ -33,10 +38,17 @@ interface AIChatMessage {
|
|
|
33
38
|
}
|
|
34
39
|
```
|
|
35
40
|
|
|
41
|
+
### `toolCalls`
|
|
42
|
+
|
|
43
|
+
- **Type:** `{ id: string; name: string }[]`
|
|
44
|
+
- **Description:** Tool calls requested by the model in the active flow, including earlier tool rounds of the current request. This transient list is cleared when the response finishes, is stopped, or fails; it is never added to `messages` or conversation history.
|
|
45
|
+
|
|
46
|
+
Calls become available before local tool execution. They record requested tool names, not successful execution. Arguments and results remain internal.
|
|
47
|
+
|
|
36
48
|
### `status`
|
|
37
49
|
|
|
38
50
|
- **Type:** `"ready" | "submitted" | "streaming" | "error"`
|
|
39
|
-
- **Description:** Current request state
|
|
51
|
+
- **Description:** Current request state. `submitted` covers waiting for the first text, local tool execution, and waiting for the model's next reply after a tool result. `streaming` begins when text arrives. `AIChat.Conversation` automatically shows a waiting indicator while `submitted`.
|
|
40
52
|
|
|
41
53
|
### `error`
|
|
42
54
|
|
|
@@ -54,10 +66,20 @@ interface AIChatMessage {
|
|
|
54
66
|
- **Type:** `() => void`
|
|
55
67
|
- **Description:** Aborts the current request if one is in progress
|
|
56
68
|
|
|
69
|
+
### `tools`
|
|
70
|
+
|
|
71
|
+
Register local tools under a single object using `defineAIChatTool(...)`. Their schemas must implement both [Standard Schema](https://standardschema.dev/) validation and Standard JSON Schema generation (for example, Zod 4). Provider-native tools are not supported.
|
|
72
|
+
|
|
57
73
|
## Usage
|
|
58
74
|
|
|
59
75
|
```tsx
|
|
60
|
-
import {
|
|
76
|
+
import {
|
|
77
|
+
createAuthClient,
|
|
78
|
+
createAIGatewayClient,
|
|
79
|
+
defineAIChatTool,
|
|
80
|
+
useAIChat,
|
|
81
|
+
} from "@tailor-platform/app-shell";
|
|
82
|
+
import { z } from "zod/v4";
|
|
61
83
|
|
|
62
84
|
const authClient = createAuthClient({
|
|
63
85
|
clientId: "your-client-id",
|
|
@@ -69,10 +91,23 @@ const aiClient = createAIGatewayClient({
|
|
|
69
91
|
authClient,
|
|
70
92
|
});
|
|
71
93
|
|
|
94
|
+
const lookupCustomer = defineAIChatTool({
|
|
95
|
+
description: "Look up a customer in the current workspace",
|
|
96
|
+
schema: z.object({
|
|
97
|
+
customerId: z.string(),
|
|
98
|
+
}),
|
|
99
|
+
async execute({ customerId }) {
|
|
100
|
+
return { customerId, name: "Acme Corp" };
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
|
|
72
104
|
export function ChatScreen() {
|
|
73
|
-
const { messages, sendMessage, status, stop, error } = useAIChat({
|
|
105
|
+
const { messages, toolCalls, sendMessage, status, stop, error } = useAIChat({
|
|
74
106
|
client: aiClient,
|
|
75
107
|
model: "gpt-5-mini",
|
|
108
|
+
tools: {
|
|
109
|
+
lookupCustomer,
|
|
110
|
+
},
|
|
76
111
|
});
|
|
77
112
|
|
|
78
113
|
return (
|
|
@@ -82,6 +117,9 @@ export function ChatScreen() {
|
|
|
82
117
|
{message.role}: {message.content}
|
|
83
118
|
</div>
|
|
84
119
|
))}
|
|
120
|
+
{toolCalls.map((call) => (
|
|
121
|
+
<div key={call.id}>Tool call: {call.name}</div>
|
|
122
|
+
))}
|
|
85
123
|
|
|
86
124
|
<button
|
|
87
125
|
onClick={() => void sendMessage("Hello")}
|
|
@@ -101,9 +139,9 @@ export function ChatScreen() {
|
|
|
101
139
|
## Notes
|
|
102
140
|
|
|
103
141
|
- AppShell chooses the appropriate AI Gateway transport automatically
|
|
104
|
-
-
|
|
142
|
+
- Public messages stay user/assistant text-first; tool calls and results never become conversation messages
|
|
105
143
|
- System prompts and custom history shaping should use the low-level client directly
|
|
106
|
-
- `stop()` keeps
|
|
144
|
+
- `stop()` keeps already-streamed assistant text, clears the active `toolCalls`, and ignores late events from the stopped request
|
|
107
145
|
|
|
108
146
|
## Related
|
|
109
147
|
|
|
@@ -56,15 +56,15 @@ const useAppShellConfig: () => {
|
|
|
56
56
|
### Display App Info
|
|
57
57
|
|
|
58
58
|
```typescript
|
|
59
|
-
import { useAppShellConfig } from "@tailor-platform/app-shell";
|
|
59
|
+
import { Text, useAppShellConfig } from "@tailor-platform/app-shell";
|
|
60
60
|
|
|
61
61
|
function AppInfo() {
|
|
62
62
|
const { title, configurations } = useAppShellConfig();
|
|
63
63
|
|
|
64
64
|
return (
|
|
65
65
|
<div>
|
|
66
|
-
<h2>{title}</h2>
|
|
67
|
-
<p>Base: {configurations.basePath}</p>
|
|
66
|
+
<Text.h2>{title}</Text.h2>
|
|
67
|
+
<Text.p>Base: {configurations.basePath}</Text.p>
|
|
68
68
|
</div>
|
|
69
69
|
);
|
|
70
70
|
}
|
|
@@ -96,7 +96,7 @@ function SettingsMenu() {
|
|
|
96
96
|
|
|
97
97
|
return (
|
|
98
98
|
<div>
|
|
99
|
-
<h3>Settings</h3>
|
|
99
|
+
<Text.h3>Settings</Text.h3>
|
|
100
100
|
{configurations.settingsResources.map((resource) => (
|
|
101
101
|
<button key={resource.path}>{resource.path}</button>
|
|
102
102
|
))}
|
|
@@ -28,15 +28,15 @@ const useAppShellData: () => {
|
|
|
28
28
|
### Basic Access
|
|
29
29
|
|
|
30
30
|
```typescript
|
|
31
|
-
import { useAppShellData } from "@tailor-platform/app-shell";
|
|
31
|
+
import { Text, useAppShellData } from "@tailor-platform/app-shell";
|
|
32
32
|
|
|
33
33
|
function UserProfile() {
|
|
34
34
|
const { contextData } = useAppShellData();
|
|
35
35
|
|
|
36
36
|
return (
|
|
37
37
|
<div>
|
|
38
|
-
<p>Welcome, {contextData.currentUser?.name}</p>
|
|
39
|
-
<p>Role: {contextData.currentUser?.role}</p>
|
|
38
|
+
<Text.p>Welcome, {contextData.currentUser?.name}</Text.p>
|
|
39
|
+
<Text.p>Role: {contextData.currentUser?.role}</Text.p>
|
|
40
40
|
</div>
|
|
41
41
|
);
|
|
42
42
|
}
|
|
@@ -54,8 +54,8 @@ function UserInfo() {
|
|
|
54
54
|
|
|
55
55
|
return (
|
|
56
56
|
<div>
|
|
57
|
-
<h2>{contextData.currentUser.name}</h2>
|
|
58
|
-
<p>{contextData.currentUser.email}</p>
|
|
57
|
+
<Text.h2>{contextData.currentUser.name}</Text.h2>
|
|
58
|
+
<Text.p>{contextData.currentUser.email}</Text.p>
|
|
59
59
|
</div>
|
|
60
60
|
);
|
|
61
61
|
}
|
|
@@ -120,8 +120,8 @@ function UserInfo() {
|
|
|
120
120
|
|
|
121
121
|
return (
|
|
122
122
|
<div>
|
|
123
|
-
<h2>{contextData.currentUser.name}</h2>
|
|
124
|
-
<p>{contextData.currentUser.email}</p>
|
|
123
|
+
<Text.h2>{contextData.currentUser.name}</Text.h2>
|
|
124
|
+
<Text.p>{contextData.currentUser.email}</Text.p>
|
|
125
125
|
</div>
|
|
126
126
|
);
|
|
127
127
|
}
|
|
@@ -59,7 +59,7 @@ const useAuth: () => {
|
|
|
59
59
|
### Basic Authentication Flow
|
|
60
60
|
|
|
61
61
|
```typescript
|
|
62
|
-
import { useAuth } from "@tailor-platform/app-shell";
|
|
62
|
+
import { Text, useAuth } from "@tailor-platform/app-shell";
|
|
63
63
|
|
|
64
64
|
function MyComponent() {
|
|
65
65
|
const { isAuthenticated, isReady, login, logout } = useAuth();
|
|
@@ -74,7 +74,7 @@ function MyComponent() {
|
|
|
74
74
|
|
|
75
75
|
return (
|
|
76
76
|
<div>
|
|
77
|
-
<p>You are logged in!</p>
|
|
77
|
+
<Text.p>You are logged in!</Text.p>
|
|
78
78
|
<button onClick={logout}>Log Out</button>
|
|
79
79
|
</div>
|
|
80
80
|
);
|
|
@@ -108,7 +108,7 @@ function AuthStatus() {
|
|
|
108
108
|
if (error) {
|
|
109
109
|
return (
|
|
110
110
|
<div>
|
|
111
|
-
<p>Authentication failed: {error}</p>
|
|
111
|
+
<Text.p>Authentication failed: {error}</Text.p>
|
|
112
112
|
<button onClick={login}>Retry</button>
|
|
113
113
|
</div>
|
|
114
114
|
);
|
|
@@ -26,7 +26,7 @@ const useRouteError: () => unknown;
|
|
|
26
26
|
### Basic Error Boundary
|
|
27
27
|
|
|
28
28
|
```typescript
|
|
29
|
-
import { useRouteError } from "@tailor-platform/app-shell";
|
|
29
|
+
import { Text, useRouteError } from "@tailor-platform/app-shell";
|
|
30
30
|
|
|
31
31
|
function ErrorBoundary() {
|
|
32
32
|
const error = useRouteError();
|
|
@@ -37,8 +37,8 @@ function ErrorBoundary() {
|
|
|
37
37
|
|
|
38
38
|
return (
|
|
39
39
|
<div>
|
|
40
|
-
<h1>Something went wrong</h1>
|
|
41
|
-
<p>{message}</p>
|
|
40
|
+
<Text.h1>Something went wrong</Text.h1>
|
|
41
|
+
<Text.p>{message}</Text.p>
|
|
42
42
|
<button onClick={() => window.location.reload()}>
|
|
43
43
|
Try Again
|
|
44
44
|
</button>
|
|
@@ -79,7 +79,7 @@ const productsModule = defineModule({
|
|
|
79
79
|
### Advanced Error Handling
|
|
80
80
|
|
|
81
81
|
```typescript
|
|
82
|
-
import { isRouteErrorResponse, useRouteError } from "@tailor-platform/app-shell";
|
|
82
|
+
import { isRouteErrorResponse, Text, useRouteError } from "@tailor-platform/app-shell";
|
|
83
83
|
|
|
84
84
|
function ErrorBoundary() {
|
|
85
85
|
const error = useRouteError();
|
|
@@ -88,8 +88,8 @@ function ErrorBoundary() {
|
|
|
88
88
|
if (isRouteErrorResponse(error)) {
|
|
89
89
|
return (
|
|
90
90
|
<div>
|
|
91
|
-
<h1>{error.status} {error.statusText}</h1>
|
|
92
|
-
<p>{error.data}</p>
|
|
91
|
+
<Text.h1>{error.status} {error.statusText}</Text.h1>
|
|
92
|
+
<Text.p>{error.data}</Text.p>
|
|
93
93
|
</div>
|
|
94
94
|
);
|
|
95
95
|
}
|
|
@@ -98,8 +98,8 @@ function ErrorBoundary() {
|
|
|
98
98
|
if (error instanceof Error) {
|
|
99
99
|
return (
|
|
100
100
|
<div>
|
|
101
|
-
<h1>Error</h1>
|
|
102
|
-
<p>{error.message}</p>
|
|
101
|
+
<Text.h1>Error</Text.h1>
|
|
102
|
+
<Text.p>{error.message}</Text.p>
|
|
103
103
|
<pre>{error.stack}</pre>
|
|
104
104
|
</div>
|
|
105
105
|
);
|
|
@@ -108,8 +108,8 @@ function ErrorBoundary() {
|
|
|
108
108
|
// Handle unknown errors
|
|
109
109
|
return (
|
|
110
110
|
<div>
|
|
111
|
-
<h1>Unknown Error</h1>
|
|
112
|
-
<p>Something went wrong</p>
|
|
111
|
+
<Text.h1>Unknown Error</Text.h1>
|
|
112
|
+
<Text.p>Something went wrong</Text.p>
|
|
113
113
|
</div>
|
|
114
114
|
);
|
|
115
115
|
}
|
|
@@ -129,8 +129,8 @@ function ErrorBoundary() {
|
|
|
129
129
|
|
|
130
130
|
return (
|
|
131
131
|
<div>
|
|
132
|
-
<h1>Error</h1>
|
|
133
|
-
<p>We've been notified and will fix this soon.</p>
|
|
132
|
+
<Text.h1>Error</Text.h1>
|
|
133
|
+
<Text.p>We've been notified and will fix this soon.</Text.p>
|
|
134
134
|
</div>
|
|
135
135
|
);
|
|
136
136
|
}
|
|
@@ -17,24 +17,30 @@ import { ActionPanel } from "@tailor-platform/app-shell";
|
|
|
17
17
|
|
|
18
18
|
## Basic Usage
|
|
19
19
|
|
|
20
|
+
<example-preview name="basic-usage"></example-preview>
|
|
21
|
+
|
|
20
22
|
```tsx
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
23
|
+
function BasicUsage() {
|
|
24
|
+
return (
|
|
25
|
+
<ActionPanel
|
|
26
|
+
title="Actions"
|
|
27
|
+
actions={[
|
|
28
|
+
{
|
|
29
|
+
key: "create-invoice",
|
|
30
|
+
label: "Create new sales invoice",
|
|
31
|
+
icon: <Receipt />,
|
|
32
|
+
onClick: () => {},
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
key: "view-docs",
|
|
36
|
+
label: "View documentation",
|
|
37
|
+
icon: <ExternalLink />,
|
|
38
|
+
onClick: () => {},
|
|
39
|
+
},
|
|
40
|
+
]}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
38
44
|
```
|
|
39
45
|
|
|
40
46
|
## Props
|
|
@@ -43,11 +49,12 @@ _Generated from the type surface — first-party props only._
|
|
|
43
49
|
|
|
44
50
|
### ActionPanelProps
|
|
45
51
|
|
|
46
|
-
| Prop
|
|
47
|
-
|
|
|
48
|
-
| `actions`
|
|
49
|
-
| `className?`
|
|
50
|
-
| `
|
|
52
|
+
| Prop | Type | Default | Description |
|
|
53
|
+
| --------------- | -------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
54
|
+
| `actions` | `ActionItem[]` | - | List of actions to render |
|
|
55
|
+
| `className?` | `string` | - | Additional CSS classes for the card container |
|
|
56
|
+
| `emptyContent?` | `ReactNode` | - | Content shown with consistent padding when actions is empty. Defaults to a localized "No actions available" message. Strings use the default body text styling; other React nodes render unchanged. Pass null to hide the empty content and its additional vertical spacing while keeping the card and title. |
|
|
57
|
+
| `title` | `string` | - | Card title |
|
|
51
58
|
|
|
52
59
|
## Exports
|
|
53
60
|
|
|
@@ -71,7 +78,34 @@ _Generated from the type surface — first-party props only._
|
|
|
71
78
|
- Rows are rendered as `<button type="button">` elements.
|
|
72
79
|
- `disabled` or `loading` makes the row non-interactive.
|
|
73
80
|
- When `loading` is `true`, the icon slot renders a spinner without shifting layout.
|
|
74
|
-
- Empty actions show a
|
|
81
|
+
- Empty actions show `emptyContent`, which defaults to a styled message localized using the AppShell locale: `No actions available` in English or `利用可能なアクションはありません` in Japanese.
|
|
82
|
+
|
|
83
|
+
## Custom Empty Content
|
|
84
|
+
|
|
85
|
+
Pass a string or any `ReactNode` to `emptyContent` to replace the default content when `actions` is empty. Strings use the same muted `Text` body styling as the default message. Other React nodes render directly inside `Card.Content` without a paragraph wrapper and control their own typography. `Card.Content` owns the spacing, and the title, empty content, and action icons share the same 32px horizontal inset. Action rows retain horizontal padding around their hover backgrounds and focus rings. Custom empty content is ignored when actions are present.
|
|
86
|
+
|
|
87
|
+
<example-preview name="custom-empty-content"></example-preview>
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
function CustomEmptyContent() {
|
|
91
|
+
return (
|
|
92
|
+
<ActionPanel
|
|
93
|
+
title="操作"
|
|
94
|
+
actions={[]}
|
|
95
|
+
emptyContent={
|
|
96
|
+
<div>
|
|
97
|
+
<Text.p className="text-muted-foreground">利用可能な操作はありません</Text.p>
|
|
98
|
+
<Text.p className="text-muted-foreground">
|
|
99
|
+
ステータスが変わると、操作が表示されます。
|
|
100
|
+
</Text.p>
|
|
101
|
+
</div>
|
|
102
|
+
}
|
|
103
|
+
/>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Pass `null` to hide the empty content and its additional vertical spacing while keeping the card and its title. To hide the entire panel when no actions are available, render it conditionally with `actions.length > 0`.
|
|
75
109
|
|
|
76
110
|
## With Loading State
|
|
77
111
|
|
|
@@ -105,7 +105,7 @@ import { ActivityCard, type ActivityCardBaseItem } from "@tailor-platform/app-sh
|
|
|
105
105
|
### Example
|
|
106
106
|
|
|
107
107
|
```tsx
|
|
108
|
-
import { ActivityCard, type ActivityCardBaseItem } from "@tailor-platform/app-shell";
|
|
108
|
+
import { ActivityCard, Text, type ActivityCardBaseItem } from "@tailor-platform/app-shell";
|
|
109
109
|
|
|
110
110
|
interface MyItem extends ActivityCardBaseItem {
|
|
111
111
|
kind: "approval" | "update";
|
|
@@ -123,11 +123,11 @@ const items: MyItem[] = [
|
|
|
123
123
|
{(item) =>
|
|
124
124
|
item.kind === "approval" ? (
|
|
125
125
|
<ActivityCard.Item indicator={<ApprovedIcon />}>
|
|
126
|
-
<p>{item.label}</p>
|
|
126
|
+
<Text.p>{item.label}</Text.p>
|
|
127
127
|
</ActivityCard.Item>
|
|
128
128
|
) : (
|
|
129
129
|
<ActivityCard.Item>
|
|
130
|
-
<p>{item.message}</p>
|
|
130
|
+
<Text.p>{item.message}</Text.p>
|
|
131
131
|
</ActivityCard.Item>
|
|
132
132
|
)
|
|
133
133
|
}
|