@agent-native/core 0.153.6 → 0.153.9

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.
@@ -0,0 +1,432 @@
1
+ ---
2
+ title: "Data & Sync"
3
+ description: "The action hooks app/ uses to read, write, and stay live-synced with the same actions the agent calls."
4
+ ---
5
+
6
+ # Data & Sync
7
+
8
+ The primary way to read and write app data from client application is through [action](/docs/actions) hooks. By embedding requests into actions, both your client and agent can interact with your application in the same way.
9
+
10
+ In the example below, a ticket is assigned to the current user manually through a dropdown, and then through the chat agent. Both the UI and agent access the same action.
11
+
12
+ <Video
13
+ src="https://cdn.builder.io/o/assets%2FYJIGb4i01jvw0SRdL5Bt%2Fa5548a35d1494c1f8a4f960fbe8d9b2c%2Fcompressed?apiKey=YJIGb4i01jvw0SRdL5Bt&token=a5548a35d1494c1f8a4f960fbe8d9b2c&alt=media&optimized=true"
14
+ alt="Example of a UI action and an agent action having the same result."
15
+ align="full"
16
+ autoplay={true}
17
+ loop={true}
18
+ width={600}
19
+ caption="Example of a UI action and an agent action having the same result."
20
+ />
21
+
22
+ ## Fetch and mutate data {#fetching-mutating}
23
+
24
+ A typical app builds its own API layer and calls it with hand-written `fetch` requests. An agent-native app skips that layer: `app/` calls the same actions the agent already has as tools, through the hooks below.
25
+
26
+ <Diagram id="doc-block-client-data-fm-1" title="Typical fetch vs. an action hook" summary="A typical component calls a route only it can reach. An agent-native component calls an action the agent can call too.">
27
+
28
+ ```html
29
+ <div class="diagram-fmcmp">
30
+ <div class="fmcmp-col">
31
+ <div class="fmcmp-label diagram-muted">Typical</div>
32
+ <div class="diagram-node">Component</div>
33
+ <div class="fmcmp-arrow diagram-muted" aria-hidden="true">&#8595;</div>
34
+ <div class="diagram-node">fetch("/api/leads/:id")</div>
35
+ <div class="fmcmp-arrow diagram-muted" aria-hidden="true">&#8595;</div>
36
+ <div class="diagram-panel" data-rough>
37
+ <strong>Your API route</strong>
38
+ <small class="diagram-muted">only the frontend can call it</small>
39
+ </div>
40
+ <div class="fmcmp-arrow diagram-muted" aria-hidden="true">&#8595;</div>
41
+ <div class="diagram-box" data-rough>Database</div>
42
+ </div>
43
+ <div class="fmcmp-vs diagram-muted">vs</div>
44
+ <div class="fmcmp-col">
45
+ <div class="fmcmp-label diagram-muted">Agent Native</div>
46
+ <div class="diagram-node">Component</div>
47
+ <div class="fmcmp-arrow diagram-muted" aria-hidden="true">&#8595;</div>
48
+ <div class="diagram-node">useActionQuery("get-lead")</div>
49
+ <div class="fmcmp-arrow diagram-muted" aria-hidden="true">&#8595;</div>
50
+ <div class="diagram-panel" data-rough>
51
+ <span class="diagram-pill accent">get-lead action</span>
52
+ <small class="diagram-muted">also an agent tool</small>
53
+ </div>
54
+ <div class="fmcmp-arrow diagram-muted" aria-hidden="true">&#8595;</div>
55
+ <div class="diagram-box" data-rough>SQL database</div>
56
+ </div>
57
+ </div>
58
+ ```
59
+
60
+ ```css
61
+ .diagram-fmcmp {
62
+ display: flex;
63
+ align-items: flex-start;
64
+ gap: 28px;
65
+ flex-wrap: wrap;
66
+ }
67
+ .fmcmp-col {
68
+ display: flex;
69
+ flex-direction: column;
70
+ align-items: center;
71
+ gap: 8px;
72
+ flex: 1;
73
+ min-width: 200px;
74
+ }
75
+ .fmcmp-label {
76
+ font-size: 11px;
77
+ text-transform: uppercase;
78
+ letter-spacing: 0.06em;
79
+ font-weight: 600;
80
+ }
81
+ .fmcmp-vs {
82
+ font-size: 13px;
83
+ font-weight: 600;
84
+ padding-top: 52px;
85
+ }
86
+ .diagram-fmcmp .diagram-panel {
87
+ display: flex;
88
+ flex-direction: column;
89
+ gap: 6px;
90
+ padding: 12px 16px;
91
+ align-items: center;
92
+ }
93
+ .fmcmp-arrow {
94
+ font-size: 18px;
95
+ line-height: 1;
96
+ }
97
+ ```
98
+
99
+ </Diagram>
100
+
101
+ ### Action hooks {#action-hooks}
102
+
103
+ There are two action hooks you are most likely to use: `useActionQuery()` and `useActionMutation()`. These two hooks provide access to your app's actions.
104
+
105
+ #### useActionQuery {#use-action-query}
106
+
107
+ <Signature
108
+ id="doc-block-client-data-1"
109
+ name="useActionQuery"
110
+ params={[
111
+ {
112
+ name: "actionName",
113
+ type: "TName",
114
+ description: "The action's registered name.",
115
+ },
116
+ {
117
+ name: "params",
118
+ type: "ActionParams<TName>",
119
+ optional: true,
120
+ description:
121
+ "The action's input, typed from its `defineAction()` schema.",
122
+ },
123
+ {
124
+ name: "options",
125
+ type: "UseQueryOptions",
126
+ optional: true,
127
+ description:
128
+ "Everything from the [useQuery options](https://tanstack.com/query/latest/docs/framework/react/reference/useQuery) except `queryKey` and `queryFn`, which the hook sets itself.",
129
+ },
130
+ ]}
131
+ returns={{
132
+ type: "UseQueryResult<TResult>",
133
+ description:
134
+ 'Cached under `["action", name, params]`. Refetches automatically whenever a mutating action completes, with no manual invalidation needed.',
135
+ }}
136
+ />
137
+
138
+ This hook is intended to be used for read-only actions. It is a thin wrapper around the [useQuery()](https://tanstack.com/query/latest/docs/framework/react/reference/useQuery) function.
139
+
140
+ In the example below, the `list-tickets` action is called. This is similar to making a GET request for all tickets.
141
+
142
+ ```ts
143
+ import { useActionQuery } from "@agent-native/core/client/hooks";
144
+
145
+ const { data, isLoading } = useActionQuery("list-tickets", {});
146
+ ```
147
+
148
+ The following example shows a request to fetch a single ticket, through the `get-ticket` action. A `ticketId` is provided to scope the query to only return a specific ticket. The additional `enabled` option is provided to ensure the query does not automatically run, and instead only runs when a `ticketId` is present.
149
+
150
+ ```ts
151
+ import { useActionQuery } from "@agent-native/core/client/hooks";
152
+
153
+ const { data, isLoading } = useActionQuery(
154
+ "get-ticket",
155
+ { ticketId: ticketId ?? "" },
156
+ { enabled: Boolean(ticketId) },
157
+ );
158
+ ```
159
+
160
+ #### useActionMutation {#use-action-mutation}
161
+
162
+ <Signature
163
+ id="doc-block-client-data-2"
164
+ name="useActionMutation"
165
+ params={[
166
+ {
167
+ name: "actionName",
168
+ type: "TName",
169
+ description: "The action's registered name.",
170
+ },
171
+ {
172
+ name: "options",
173
+ type: "UseMutationOptions",
174
+ optional: true,
175
+ description:
176
+ "Everything from the [useMutation options](https://tanstack.com/query/latest/docs/framework/react/reference/useMutation) except `mutationFn`, which the hook sets itself.",
177
+ fields: [
178
+ {
179
+ name: "method",
180
+ type: '"POST" | "PUT" | "DELETE"',
181
+ optional: true,
182
+ default: '"POST"',
183
+ description: "Overrides the default request method.",
184
+ },
185
+ {
186
+ name: "skipActionQueryInvalidation",
187
+ type: "boolean",
188
+ optional: true,
189
+ default: "false",
190
+ description: "Opts out of the automatic cache refresh on success.",
191
+ },
192
+ ],
193
+ },
194
+ ]}
195
+ returns={{
196
+ type: "UseMutationResult",
197
+ description:
198
+ 'On success, emits a change event with `source: "action"`, so every `useActionQuery()` reading affected data refetches automatically. No manual invalidation is needed.',
199
+ }}
200
+ />
201
+
202
+ This hook is intended to be used for actions that change data, like creating, updating, or sending something. It is a thin wrapper around the [useMutation()](https://tanstack.com/query/latest/docs/framework/react/reference/useMutation) function.
203
+
204
+ Unlike `useActionQuery()`, the action's input is not passed to the hook itself. It is passed to `mutate()` or `mutateAsync()` when you are ready to run the mutation.
205
+
206
+ In the example below, the `send-ticket-reply` action is called to post a reply to a ticket. Call `mutate()` from an event handler, such as a button's `onClick`.
207
+
208
+ ```ts
209
+ import { useActionMutation } from "@agent-native/core/client/hooks";
210
+
211
+ const { mutate, isPending } = useActionMutation("send-ticket-reply");
212
+
213
+ mutate({ ticketId, body: draft });
214
+ ```
215
+
216
+ The following example shows the same mutation with an `onSuccess` option. Cache invalidation happens automatically after every mutation, so `onSuccess` is only needed for local side effects, like clearing the reply composer once the reply is sent.
217
+
218
+ ```ts
219
+ import { useActionMutation } from "@agent-native/core/client/hooks";
220
+
221
+ const { mutate, isPending } = useActionMutation("send-ticket-reply", {
222
+ onSuccess: () => setDraft(""),
223
+ });
224
+
225
+ mutate({ ticketId, body: draft });
226
+ ```
227
+
228
+ ### callAction {#call-action}
229
+
230
+ <Signature
231
+ id="doc-block-client-data-3"
232
+ name="callAction"
233
+ params={[
234
+ {
235
+ name: "actionName",
236
+ type: "TName",
237
+ description: "The action's registered name.",
238
+ },
239
+ {
240
+ name: "params",
241
+ type: "ActionParams<TName>",
242
+ optional: true,
243
+ description:
244
+ "The action's input, typed from its `defineAction()` schema.",
245
+ },
246
+ {
247
+ name: "options",
248
+ type: "ClientActionCallOptions",
249
+ optional: true,
250
+ description: "Everything below is optional.",
251
+ fields: [
252
+ {
253
+ name: "method",
254
+ type: '"GET" | "POST" | "PUT" | "DELETE"',
255
+ optional: true,
256
+ default: '"POST"',
257
+ description: "Overrides the default request method.",
258
+ },
259
+ {
260
+ name: "signal",
261
+ type: "AbortSignal",
262
+ optional: true,
263
+ description: "Cancels the request.",
264
+ },
265
+ {
266
+ name: "timeoutMs",
267
+ type: "number",
268
+ optional: true,
269
+ default: "60000",
270
+ description:
271
+ "Overrides the default 60 second timeout, for actions that legitimately take longer.",
272
+ },
273
+ ],
274
+ },
275
+ ]}
276
+ returns={{
277
+ type: "Promise<TResult>",
278
+ description:
279
+ "Unlike the hooks above, there is no cache and no automatic refetch. Pair it with a manual invalidation if other UI depends on the result.",
280
+ }}
281
+ />
282
+
283
+ This function is for imperative one-off action calls outside of a React render flow, such as a plain event handler, a script, or any other non-React code path. It returns a promise directly instead of a hook result.
284
+
285
+ In the example below, the `update-ticket` action is called from a keyboard shortcut handler outside of React's render flow, assigning the ticket to a rep.
286
+
287
+ ```ts
288
+ import { callAction } from "@agent-native/core/client/hooks";
289
+
290
+ await callAction("update-ticket", {
291
+ ticketId,
292
+ assignedToEmail: "jordan@example.com",
293
+ });
294
+ ```
295
+
296
+ The following example shows the same call with a `timeoutMs` option, useful for actions that legitimately take longer than the default 60 second timeout.
297
+
298
+ ```ts
299
+ import { callAction } from "@agent-native/core/client/hooks";
300
+
301
+ await callAction(
302
+ "update-ticket",
303
+ { ticketId, assignedToEmail: "jordan@example.com" },
304
+ { timeoutMs: 120_000 },
305
+ );
306
+ ```
307
+
308
+ Every `useActionQuery()`, `useActionMutation()`, and `callAction()` request is bounded by a 60 second timeout, and timeouts surface as errors instead of retrying silently. `useActionQuery()` also cancels superseded requests automatically, using React Query's abort signal.
309
+
310
+ ## useDbSync {#usedbsync}
311
+
312
+ <Signature
313
+ id="doc-block-client-data-4"
314
+ name="useDbSync"
315
+ params={[
316
+ {
317
+ name: "options",
318
+ type: "UseDbSyncOptions",
319
+ description: "Every field is optional.",
320
+ fields: [
321
+ {
322
+ name: "queryClient",
323
+ type: "QueryClient",
324
+ optional: true,
325
+ description: "React Query client used for cache invalidation.",
326
+ },
327
+ {
328
+ name: "pollUrl",
329
+ type: "string",
330
+ optional: true,
331
+ default: '"/_agent-native/poll"',
332
+ description: "Poll endpoint URL.",
333
+ },
334
+ {
335
+ name: "sseUrl",
336
+ type: "string | false",
337
+ optional: true,
338
+ default: '"/_agent-native/events"',
339
+ description: "SSE endpoint URL. Pass `false` to use polling only.",
340
+ },
341
+ {
342
+ name: "interval",
343
+ type: "number",
344
+ optional: true,
345
+ default: "2000",
346
+ description: "Polling interval in ms.",
347
+ },
348
+ {
349
+ name: "fallbackInterval",
350
+ type: "number",
351
+ optional: true,
352
+ default: "60000",
353
+ description:
354
+ "Poll interval while SSE is connected, as a backup in case an event is missed. A disconnected tab polls at the faster `interval` (or a fixed idle cadence) instead.",
355
+ },
356
+ {
357
+ name: "pauseWhenHidden",
358
+ type: "boolean",
359
+ optional: true,
360
+ default: "false",
361
+ description:
362
+ "Pause polling when the browser tab is hidden. Hidden tabs keep SSE connected and poll no faster than every 10s while active, 60s once idle.",
363
+ },
364
+ {
365
+ name: "ignoreSource",
366
+ type: "string",
367
+ optional: true,
368
+ description:
369
+ "Per-tab request source to ignore, so a tab does not refetch from its own writes.",
370
+ },
371
+ {
372
+ name: "onEvent",
373
+ type: "(data) => void",
374
+ optional: true,
375
+ description:
376
+ "Optional callback when SSE or polling receives a change event.",
377
+ },
378
+ {
379
+ name: "actionInvalidatePredicate",
380
+ type: "(query: Query) => boolean",
381
+ optional: true,
382
+ description:
383
+ "Filters the broad invalidation an action event triggers. Rarely needed. Use it to keep an expensive active query on explicit-refresh semantics while normal source-versioned queries still react through `useChangeVersion`.",
384
+ },
385
+ {
386
+ name: "suppressActionInvalidationFor",
387
+ type: "string[]",
388
+ optional: true,
389
+ description:
390
+ "Action names whose sync events should not invalidate all action queries. Only for high-volume background actions that already perform their own narrow invalidation.",
391
+ },
392
+ {
393
+ name: "queryKeys",
394
+ type: "string[]",
395
+ optional: true,
396
+ description: "Deprecated and ignored. Kept for old call sites.",
397
+ },
398
+ ],
399
+ },
400
+ ]}
401
+ returns={{ type: "void" }}
402
+ />
403
+
404
+ This hook is intended to keep the UI in sync with database changes that happen outside of your own actions, like an agent write, a webhook, or a change made from another browser tab. It listens for changes over server-sent events, and falls back to polling when server-sent events are unavailable.
405
+
406
+ ```ts
407
+ import { useDbSync } from "@agent-native/core/client/hooks";
408
+ import { useQueryClient } from "@tanstack/react-query";
409
+
410
+ function App() {
411
+ const queryClient = useQueryClient();
412
+
413
+ useDbSync({
414
+ queryClient,
415
+ pollUrl: "/_agent-native/poll",
416
+ onEvent: (data) => console.log("Data changed:", data),
417
+ });
418
+
419
+ return <div>...</div>;
420
+ }
421
+ ```
422
+
423
+ For normal CRUD, prefer `useActionQuery()` and `useActionMutation()`. Mutating actions emit `source: "action"`, so those hooks refetch right away. Changes from the agent, a webhook, or another tab arrive through `useDbSync()` instead, over SSE or the polling fallback. If a raw `useQuery()` call needs to sync on a custom key, see [Sync Internals](/docs/client-sync-internals). If you need to write to `application_state` directly, see [Advanced](/docs/client-advanced).
424
+
425
+ ## What's next
426
+
427
+ - [**Overview**](/docs/client-overview) — the `app/` and `public/` directory layout this page's hooks live in
428
+ - [**Agent Chat**](/docs/client-agent-chat) — sending prompts and staging context for the agent
429
+ - [**Actions**](/docs/actions) — the `defineAction` surface behind every hook on this page
430
+ - [**Advanced**](/docs/client-advanced) — direct `application_state` access
431
+ - [**Sync Internals**](/docs/client-sync-internals) — syncing a raw `useQuery` with `useChangeVersion`
432
+ - [**Real-Time Sync**](/docs/key-concepts#polling-sync) — how `useDbSync` keeps the UI current
@@ -0,0 +1,27 @@
1
+ ---
2
+ title: "Entry Points"
3
+ description: "app/entry.client.tsx and app/entry.server.tsx: the thin hydration and SSR bootstrap wrappers, and when to customize streaming behavior."
4
+ ---
5
+
6
+ # Entry Points
7
+
8
+ `app/entry.client.tsx` hydrates the server-rendered HTML in the browser, and `app/entry.server.tsx` renders that HTML on the server. Both are thin wrappers generated by React Router. You rarely need to edit them.
9
+
10
+ ```ts filename="app/entry.server.tsx"
11
+ import {
12
+ createDocumentRequestHandler,
13
+ streamTimeout,
14
+ } from "@agent-native/core/server/entry-server";
15
+ import { ServerRouter } from "react-router";
16
+
17
+ export default createDocumentRequestHandler(ServerRouter);
18
+ export { streamTimeout };
19
+ ```
20
+
21
+ Reach for these only for advanced SSR customization, for example changing streaming behavior. Ordinary pages, layout, and data don't need changes here. See [Routing](/docs/client-routing) for adding pages instead.
22
+
23
+ ## What's next
24
+
25
+ - [**Overview**](/docs/client-overview) — where these files sit in `app/`
26
+ - [**Routing**](/docs/client-routing) — adding pages, the normal way to extend `app/`
27
+ - [**Advanced**](/docs/client-advanced) — the other lower-level client primitives and escape hatches
@@ -0,0 +1,206 @@
1
+ ---
2
+ title: "Overview"
3
+ description: "The app/ and public/ directories: where you build the display of an agent-native app, and the browser APIs it uses to stay in sync with the agent."
4
+ ---
5
+
6
+ # Overview
7
+
8
+ `app/` is where you build the actual display of an agent-native app: pages, layout, components, styles. `public/` holds the static assets served alongside it — icons, manifest, brand marks. This is a typical React Router v8 app, not a special agent-only surface, and it's where most development time goes.
9
+
10
+ The one architectural difference from a typical frontend: this layer has no backend-for-frontend of its own. It reads and writes data through the same actions the agent calls, so anything wired up here is automatically something the agent can also do.
11
+
12
+ <Diagram id="doc-block-client-overview-1" title="Traditional frontend vs. Agent Native" summary="A traditional frontend calls its own API layer, which only it can reach. In Agent Native, app/ and the agent both call the same actions, so every capability built here is agent-callable for free.">
13
+
14
+ ```html
15
+ <div class="diagram-cocmp">
16
+ <div class="cocmp-col">
17
+ <div class="cocmp-label diagram-muted">Traditional</div>
18
+ <div class="diagram-node">app/ (React frontend)</div>
19
+ <div class="cocmp-arrow diagram-muted" aria-hidden="true">&#8595;</div>
20
+ <div class="diagram-panel" data-rough>
21
+ <strong>API layer</strong>
22
+ <small class="diagram-muted">only the frontend calls it</small>
23
+ </div>
24
+ <div class="cocmp-arrow diagram-muted" aria-hidden="true">&#8595;</div>
25
+ <div class="diagram-box" data-rough>Database</div>
26
+ </div>
27
+ <div class="cocmp-vs diagram-muted">vs</div>
28
+ <div class="cocmp-col">
29
+ <div class="cocmp-label diagram-muted">Agent Native</div>
30
+ <div class="cocmp-callers">
31
+ <div class="diagram-node">app/ (React frontend)</div>
32
+ <div class="diagram-node">Agent</div>
33
+ </div>
34
+ <div class="cocmp-arrow diagram-muted" aria-hidden="true">&#8595;</div>
35
+ <div class="diagram-panel" data-rough>
36
+ <span class="diagram-pill accent">Actions</span
37
+ ><small class="diagram-muted">equal partners, same operations</small>
38
+ </div>
39
+ <div class="cocmp-arrow diagram-muted" aria-hidden="true">&#8595;</div>
40
+ <div class="diagram-box" data-rough>SQL database</div>
41
+ </div>
42
+ </div>
43
+ ```
44
+
45
+ ```css
46
+ .diagram-cocmp {
47
+ display: flex;
48
+ align-items: flex-start;
49
+ gap: 28px;
50
+ flex-wrap: wrap;
51
+ }
52
+ .cocmp-col {
53
+ display: flex;
54
+ flex-direction: column;
55
+ align-items: center;
56
+ gap: 8px;
57
+ flex: 1;
58
+ min-width: 180px;
59
+ }
60
+ .cocmp-label {
61
+ font-size: 11px;
62
+ text-transform: uppercase;
63
+ letter-spacing: 0.06em;
64
+ font-weight: 600;
65
+ }
66
+ .cocmp-callers {
67
+ display: flex;
68
+ gap: 8px;
69
+ flex-wrap: wrap;
70
+ justify-content: center;
71
+ }
72
+ .cocmp-vs {
73
+ font-size: 13px;
74
+ font-weight: 600;
75
+ padding-top: 52px;
76
+ }
77
+ .diagram-cocmp .diagram-panel {
78
+ display: flex;
79
+ flex-direction: column;
80
+ gap: 6px;
81
+ padding: 12px 16px;
82
+ align-self: stretch;
83
+ align-items: center;
84
+ }
85
+ .cocmp-arrow {
86
+ font-size: 18px;
87
+ line-height: 1;
88
+ }
89
+ ```
90
+
91
+ </Diagram>
92
+
93
+ ## Where the Client Fits {#role}
94
+
95
+ Browser requests, the agent loop, and external API clients all resolve to the same actions and the same SQL database — there is no separate "app backend" this layer talks to that the agent can't also reach. Building a feature here means building a UI on top of an operation the agent already has, or defining a new action and getting a UI for it at the same time. See [Actions](/docs/actions) for the operation side of that contract.
96
+
97
+ This page covers the directories you build in and the pieces of the browser runtime nothing else documents (`root.tsx`, `public/`). The React hooks that connect your components to actions and to the agent live on their own focused pages:
98
+
99
+ | Page | What it covers |
100
+ | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
101
+ | **[Data & Sync](/docs/client-data)** | `useActionQuery`, `useActionMutation`, `callAction`, and `useDbSync` |
102
+ | **[Agent Chat](/docs/client-agent-chat)** | `sendToAgentChat`, `askUserQuestion`, `openAgentSettings`, dynamic suggestions, and staged agent chat context state |
103
+ | **[Advanced](/docs/client-advanced)** | Direct `application_state` access, background chat sends, custom suggestion chips, and the MCP App host bridge |
104
+ | **[Sync Internals](/docs/client-sync-internals)** | `useChangeVersion`/`useChangeVersions` for syncing a raw `useQuery` |
105
+ | **[Entry Points](/docs/client-entry-points)** | `app/entry.client.tsx`/`app/entry.server.tsx` and SSR customization |
106
+
107
+ ## What's in app/ and public/ {#project-directories}
108
+
109
+ | Path | What it covers |
110
+ | --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
111
+ | **`app/root.tsx`** | The HTML shell and provider stack — see [The Browser Shell](#app-shell) below |
112
+ | **`app/entry.client.tsx` / `app/entry.server.tsx`** | Hydration and SSR bootstrap — see [Entry Points](/docs/client-entry-points) |
113
+ | **`app/routes.ts` + `app/routes/`** | File-based pages and navigation — see [Routing](/docs/client-routing) |
114
+ | **`app/components/`** | Your UI: shadcn-style primitives plus app-specific components — see [Toolkit UI Primitives](/docs/toolkit-ui) |
115
+ | **`app/hooks/`** | Data, sync, and agent-chat hooks — see [Data & Sync](/docs/client-data) and [Agent Chat](/docs/client-agent-chat) |
116
+ | **`app/lib/`** | Shared client utilities (app config, tab id, `cn`) — see [Utilities](#utilities) below |
117
+ | **`app/design-system.ts`** | Optional custom design system adapter — see [Custom Design Systems](/docs/custom-design-system) |
118
+ | **`app/i18n/`** | Localized UI copy catalogs — see [Internationalization](/docs/internationalization) |
119
+ | **`app/global.css`** | Base styles and the Tailwind entry point |
120
+ | **`public/`** | Static assets served as-is — see [Static Assets](#public-directory) below |
121
+
122
+ ## The Browser Shell: root.tsx and AppProviders {#app-shell}
123
+
124
+ `app/root.tsx` is the top of the React tree: the HTML document shell (`<html>`, `<head>`, `<Meta>`, `<Links>`, `<Scripts>`) plus the provider stack every route renders inside.
125
+
126
+ ```tsx filename="app/root.tsx"
127
+ import {
128
+ AppProviders,
129
+ createAgentNativeQueryClient,
130
+ } from "@agent-native/core/client/hooks";
131
+ import { useState } from "react";
132
+ import { Outlet } from "react-router";
133
+ import { i18nCatalog } from "./i18n";
134
+
135
+ export default function Root() {
136
+ const [queryClient] = useState(() => createAgentNativeQueryClient());
137
+
138
+ return (
139
+ <AppProviders queryClient={queryClient} i18n={{ catalog: i18nCatalog }}>
140
+ <Outlet />
141
+ </AppProviders>
142
+ );
143
+ }
144
+ ```
145
+
146
+ `AppProviders` is the single wrapper that wires up:
147
+
148
+ - The React Query client used by `useActionQuery`/`useActionMutation` (see [Data & Sync](/docs/client-data#fetching-mutating)).
149
+ - The i18n catalog passed as `i18n.catalog` (see [Internationalization](/docs/internationalization)).
150
+ - Client-side session checks: on a private path, an anonymous visitor is redirected to sign-in before any private UI mounts. Pass `isPublicPath` for public/SEO routes, or `sessionBypass` for a surface that authenticates another way, such as an MCP embed token. See [Authentication](/docs/authentication).
151
+
152
+ Most apps also mount `<AgentSidebar>` or `<AgentPanel>` here so every route gets the agent panel for free. See [Drop-in Agent](/docs/drop-in-agent#sidebar).
153
+
154
+ ## Static Assets: public/ {#public-directory}
155
+
156
+ Everything in `public/` is served as-is at the site root, unprocessed by the build. Every template ships:
157
+
158
+ ```text
159
+ public/
160
+ favicon.svg -> browser tab icon
161
+ icon-180.svg -> apple-touch-icon
162
+ icon-192.svg, icon-512.svg -> PWA icons referenced from manifest.json
163
+ manifest.json -> web app manifest (name, theme color, icons)
164
+ *-logo-*.svg, *-icon-*.svg -> brand marks used in the default UI
165
+ ```
166
+
167
+ `manifest.json` controls what shows up if a visitor installs the app to a home screen or app dock — name, short name, theme/background color, and the icon set:
168
+
169
+ ```json filename="public/manifest.json"
170
+ {
171
+ "name": "My App",
172
+ "short_name": "My App",
173
+ "theme_color": "#18181B",
174
+ "icons": [
175
+ { "src": "icon-192.svg", "sizes": "192x192", "type": "image/svg+xml" }
176
+ ]
177
+ }
178
+ ```
179
+
180
+ Rebrand an app by replacing these files and updating `manifest.json` and the `<link rel="icon">`/`<link rel="apple-touch-icon">` tags in `root.tsx` that reference them — there's no separate registration step. For pulling a full brand system (colors, fonts, favicon) from an existing site instead of hand-editing these, see [Brand Systems and Figma](/docs/template-design-brand-and-figma).
181
+
182
+ ## Utilities: cn {#utilities}
183
+
184
+ ```ts
185
+ import { cn } from "@agent-native/toolkit/utils";
186
+
187
+ <div className={cn(
188
+ "px-4 py-2 rounded",
189
+ isActive && "bg-primary text-primary-foreground",
190
+ className
191
+ )} />
192
+ ```
193
+
194
+ `cn` merges class names (clsx + tailwind-merge) and is the standard way to compose conditional Tailwind classes across `app/components/`.
195
+
196
+ ## What's next
197
+
198
+ - [**Data & Sync**](/docs/client-data) — action hooks and `useDbSync`
199
+ - [**Agent Chat**](/docs/client-agent-chat) — `sendToAgentChat`, `askUserQuestion`, and dynamic suggestions
200
+ - [**Advanced**](/docs/client-advanced) — application state, background chat sends, and the MCP App host bridge
201
+ - [**Sync Internals**](/docs/client-sync-internals) — `useChangeVersion` for syncing a raw `useQuery`
202
+ - [**Entry Points**](/docs/client-entry-points) — SSR bootstrap customization
203
+ - [**Routing**](/docs/client-routing) — file-based pages, dynamic params, and navigation
204
+ - [**Actions**](/docs/actions) — the `defineAction` surface behind every data hook
205
+ - [**Custom Design Systems**](/docs/custom-design-system) — swapping in your own component library via `app/design-system.ts`
206
+ - [**Authentication**](/docs/authentication) — the session checks `AppProviders` runs on private paths