camox 0.67.0 → 0.69.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/core/editing/createEditableBlock.js +2 -1
  2. package/dist/features/canvas/canvasFrameDocument.js +25 -2
  3. package/dist/features/canvas/useCanvasFrame.js +6 -2
  4. package/dist/features/preview/components/PreviewToolbar.js +77 -68
  5. package/dist/features/preview/components/previewStyles.js +11 -2
  6. package/dist/features/preview/repeatableItemTargets.js +13 -7
  7. package/dist/features/provider/AuthenticatedCamoxProvider.js +33 -28
  8. package/dist/features/renderer/RendererApp.js +202 -154
  9. package/dist/features/renderer/RendererFrames.js +87 -55
  10. package/dist/features/renderer/definitionAnswers.js +1 -0
  11. package/dist/features/renderer/rendererHost.js +4 -0
  12. package/dist/features/renderer/studioConnection.js +9 -1
  13. package/dist/features/runtime/RuntimeChrome.js +47 -61
  14. package/dist/features/runtime/pageNavigation.js +93 -17
  15. package/dist/features/runtime/rendererClient.js +13 -6
  16. package/dist/features/studio/components/ChatWidget.js +3 -4
  17. package/dist/features/studio/components/ConnectAgent.js +33 -59
  18. package/dist/features/studio/components/DraftBar.js +139 -0
  19. package/dist/features/studio/components/DraftsControl.js +129 -164
  20. package/dist/features/studio/components/LocalePicker.js +22 -4
  21. package/dist/features/studio/components/Navbar.js +3 -2
  22. package/dist/features/studio/components/ReviewDraftSheet.js +2 -21
  23. package/dist/features/studio/components/UserButton.js +7 -11
  24. package/dist/features/studio/studioStore.js +0 -13
  25. package/dist/features/studio/viewSwitch.js +64 -0
  26. package/dist/lib/auth.js +5 -1
  27. package/dist/lib/draft-scope.js +28 -15
  28. package/dist/lib/studio-reads.js +46 -13
  29. package/dist/lib/view-switch.js +64 -0
  30. package/dist/package.js +1 -1
  31. package/dist/studio.css +1 -1
  32. package/package.json +4 -4
  33. package/dist/features/studio/components/UnnamedDraftBar.js +0 -349
@@ -26,7 +26,7 @@ const mirror = (message) => mirrors[message.store]?.send({
26
26
  * its cache follows the studio's (see `protocol.ts`). The studio's questions are answered from
27
27
  * `camoxApp`, the draft's code; without it, none can be.
28
28
  */
29
- function connectToStudio(handle, { queryClient, studioCache, draftScope, camoxApp }) {
29
+ function connectToStudio(handle, { queryClient, studioCache, draftScope, camoxApp, prepareFrame }) {
30
30
  let welcomed = false;
31
31
  let frames = [];
32
32
  const listeners = /* @__PURE__ */ new Set();
@@ -109,6 +109,14 @@ function connectToStudio(handle, { queryClient, studioCache, draftScope, camoxAp
109
109
  return;
110
110
  case "ask": {
111
111
  const { askId, question } = message;
112
+ if (question.type === "prepareFrame") {
113
+ (prepareFrame?.(question) ?? Promise.resolve()).catch(() => {}).then(() => studio({
114
+ type: "answer",
115
+ askId,
116
+ value: null
117
+ }));
118
+ return;
119
+ }
112
120
  if (!camoxApp) {
113
121
  studio({
114
122
  type: "answer",
@@ -3,9 +3,9 @@ import { shownRecordPage } from "../preview/pageRecord.js";
3
3
  import { StudioEditingFallback } from "../preview/components/StudioEditingFallback.js";
4
4
  import { Navbar } from "../studio/components/Navbar.js";
5
5
  import { EditingFallbackContext } from "./editingFallback.js";
6
+ import { DraftBar } from "../studio/components/DraftBar.js";
6
7
  import { PreviewToolbar } from "../preview/components/PreviewToolbar.js";
7
8
  import { AgentSidebar } from "../studio/components/AgentSidebar.js";
8
- import { UnnamedDraftBar } from "../studio/components/UnnamedDraftBar.js";
9
9
  import { FollowAgentProvider } from "../studio/FollowAgentProvider.js";
10
10
  import { c } from "react/compiler-runtime";
11
11
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -15,7 +15,7 @@ import studioCssUrl from "virtual:camox-studio-css";
15
15
  //#region src/features/runtime/RuntimeChrome.tsx
16
16
  const renderEditingFallback = ({ layoutPage }) => /* @__PURE__ */ jsx(StudioEditingFallback, { layoutPage });
17
17
  function RuntimeChrome(t0) {
18
- const $ = c(39);
18
+ const $ = c(35);
19
19
  const { input, children } = t0;
20
20
  const website = !input.routeKind;
21
21
  const hidden = useSelector(previewStore, _temp);
@@ -93,81 +93,67 @@ function RuntimeChrome(t0) {
93
93
  $[15] = t11;
94
94
  $[16] = t13;
95
95
  } else t13 = $[16];
96
- const t14 = website && hidden;
97
- const t15 = website ? "max-md:hidden" : void 0;
98
- let t16;
96
+ let t14;
99
97
  if ($[17] !== website) {
100
- t16 = /* @__PURE__ */ jsx(UnnamedDraftBar, { stacked: website });
98
+ t14 = !website && /* @__PURE__ */ jsx(DraftBar, {});
101
99
  $[17] = website;
102
- $[18] = t16;
103
- } else t16 = $[18];
104
- let t17;
105
- if ($[19] !== t14 || $[20] !== t15 || $[21] !== t16) {
106
- t17 = /* @__PURE__ */ jsx("div", {
107
- hidden: t14,
108
- className: t15,
109
- children: t16
110
- });
111
- $[19] = t14;
112
- $[20] = t15;
113
- $[21] = t16;
114
- $[22] = t17;
115
- } else t17 = $[22];
116
- let t18;
117
- if ($[23] !== t13 || $[24] !== t17 || $[25] !== t9) {
118
- t18 = /* @__PURE__ */ jsxs("div", {
100
+ $[18] = t14;
101
+ } else t14 = $[18];
102
+ let t15;
103
+ if ($[19] !== t13 || $[20] !== t14 || $[21] !== t9) {
104
+ t15 = /* @__PURE__ */ jsxs("div", {
119
105
  className: "relative flex min-h-0 min-w-0 flex-1 flex-col",
120
106
  children: [
121
107
  t9,
122
108
  t13,
123
- t17
109
+ t14
124
110
  ]
125
111
  });
126
- $[23] = t13;
112
+ $[19] = t13;
113
+ $[20] = t14;
114
+ $[21] = t9;
115
+ $[22] = t15;
116
+ } else t15 = $[22];
117
+ const t16 = !showAgentSidebar;
118
+ let t17;
119
+ if ($[23] !== t16) {
120
+ t17 = /* @__PURE__ */ jsx(AgentSidebar, { hidden: t16 });
121
+ $[23] = t16;
127
122
  $[24] = t17;
128
- $[25] = t9;
129
- $[26] = t18;
130
- } else t18 = $[26];
131
- const t19 = !showAgentSidebar;
132
- let t20;
133
- if ($[27] !== t19) {
134
- t20 = /* @__PURE__ */ jsx(AgentSidebar, { hidden: t19 });
135
- $[27] = t19;
136
- $[28] = t20;
137
- } else t20 = $[28];
138
- let t21;
139
- if ($[29] !== t18 || $[30] !== t20) {
140
- t21 = /* @__PURE__ */ jsxs("div", {
123
+ } else t17 = $[24];
124
+ let t18;
125
+ if ($[25] !== t15 || $[26] !== t17) {
126
+ t18 = /* @__PURE__ */ jsxs("div", {
141
127
  className: "relative flex min-h-0 flex-1",
142
- children: [t18, t20]
128
+ children: [t15, t17]
143
129
  });
144
- $[29] = t18;
145
- $[30] = t20;
146
- $[31] = t21;
147
- } else t21 = $[31];
148
- let t22;
149
- if ($[32] !== t21 || $[33] !== t7) {
150
- t22 = /* @__PURE__ */ jsxs("div", {
130
+ $[25] = t15;
131
+ $[26] = t17;
132
+ $[27] = t18;
133
+ } else t18 = $[27];
134
+ let t19;
135
+ if ($[28] !== t18 || $[29] !== t7) {
136
+ t19 = /* @__PURE__ */ jsxs("div", {
151
137
  className: "bg-background flex h-screen flex-col overflow-hidden",
152
- children: [t7, t21]
138
+ children: [t7, t18]
153
139
  });
154
- $[32] = t21;
155
- $[33] = t7;
156
- $[34] = t22;
157
- } else t22 = $[34];
158
- let t23;
159
- if ($[35] !== input.runtimeBasePath || $[36] !== t22 || $[37] !== website) {
160
- t23 = /* @__PURE__ */ jsxs(FollowAgentProvider, {
140
+ $[28] = t18;
141
+ $[29] = t7;
142
+ $[30] = t19;
143
+ } else t19 = $[30];
144
+ let t20;
145
+ if ($[31] !== input.runtimeBasePath || $[32] !== t19 || $[33] !== website) {
146
+ t20 = /* @__PURE__ */ jsxs(FollowAgentProvider, {
161
147
  runtimeBasePath: t1,
162
148
  website,
163
- children: [t2, t22]
149
+ children: [t2, t19]
164
150
  });
165
- $[35] = input.runtimeBasePath;
166
- $[36] = t22;
167
- $[37] = website;
168
- $[38] = t23;
169
- } else t23 = $[38];
170
- return t23;
151
+ $[31] = input.runtimeBasePath;
152
+ $[32] = t19;
153
+ $[33] = website;
154
+ $[34] = t20;
155
+ } else t20 = $[34];
156
+ return t20;
171
157
  }
172
158
  function _temp(state) {
173
159
  return state.context.isToolbarHidden;
@@ -1,7 +1,8 @@
1
- import { DraftScopeContext, draftNamedBy } from "../../lib/draft-scope.js";
1
+ import { DraftScopeContext, ShownDraftContext, draftNamedBy } from "../../lib/draft-scope.js";
2
2
  import { PageAlternatesContext, ViewLocaleContext } from "../../lib/view-locale-context.js";
3
3
  import { NavigationProvider } from "../navigation/navigation.js";
4
4
  import { fetchRuntimeJson, getNavigationTarget } from "./navigationTarget.js";
5
+ import { SWITCH_WAIT_MS, ViewSwitchDeadlineContext, ViewSwitchesContext } from "../../lib/view-switch.js";
5
6
  import { reads } from "../../lib/studio-reads.js";
6
7
  import { createPrefetchCache, listenForLinkIntent } from "./linkPrefetch.js";
7
8
  import { PageViewportContext } from "./pageViewport.js";
@@ -13,7 +14,7 @@ import { createNavigationRequestTracker } from "./navigationRequest.js";
13
14
  import { createScrollRestoration } from "./scrollRestoration.js";
14
15
  import { c } from "react/compiler-runtime";
15
16
  import * as React from "react";
16
- import { jsx } from "react/jsx-runtime";
17
+ import { jsx, jsxs } from "react/jsx-runtime";
17
18
  import { hydrate } from "@tanstack/react-query";
18
19
  import { createHead, renderDOMHead } from "@unhead/react/client";
19
20
 
@@ -63,13 +64,24 @@ function prefetchKey(pathname, draft) {
63
64
  function PageNavigationProvider({ camoxApp, children, initialInput, queryClient }) {
64
65
  const draftScope = React.useContext(DraftScopeContext);
65
66
  if (!draftScope) throw new Error("PageNavigationProvider needs a DraftScopeContext provider");
66
- const [{ input, source, generation }, setLocation] = React.useState(() => {
67
+ const [{ input, source, switched, navigation }, setLocation] = React.useState(() => {
67
68
  draftScope.adopt(initialInput);
68
69
  return {
69
70
  input: initialInput,
70
- generation: 0
71
+ navigation: 0
71
72
  };
72
73
  });
74
+ const [preparers] = React.useState(() => /* @__PURE__ */ new Set());
75
+ const [commits] = React.useState(() => /* @__PURE__ */ new Map());
76
+ const navigations = React.useRef(0);
77
+ const lastShown = React.useRef(Promise.resolve());
78
+ const switches = React.useMemo(() => ({
79
+ prepare(preparer) {
80
+ preparers.add(preparer);
81
+ return () => void preparers.delete(preparer);
82
+ },
83
+ shown: () => lastShown.current
84
+ }), [preparers]);
73
85
  const current = React.useRef(initialInput);
74
86
  const [requests] = React.useState(createNavigationRequestTracker);
75
87
  const ownsWindow = initialInput.presentation !== "studio";
@@ -88,6 +100,20 @@ function PageNavigationProvider({ camoxApp, children, initialInput, queryClient
88
100
  }, [restoration]);
89
101
  const pendingAnnouncement = React.useRef(null);
90
102
  const announcer = React.useRef(null);
103
+ const prepareSwitch = React.useCallback((next) => {
104
+ const view = {
105
+ pathname: next.pathname,
106
+ ...next.derived && { derivedLayoutId: next.derived.layoutId },
107
+ draftId: next.draftId ?? null,
108
+ locale: next.locale
109
+ };
110
+ const deadline = Date.now() + SWITCH_WAIT_MS;
111
+ const prepared = Promise.all([...preparers].map((prepare) => Promise.resolve(prepare(view)).catch(() => {})));
112
+ return {
113
+ deadline,
114
+ ready: Promise.race([prepared.then(() => {}), new Promise((resolve) => setTimeout(resolve, SWITCH_WAIT_MS))])
115
+ };
116
+ }, [preparers]);
91
117
  const loadPage = React.useCallback(async (pathname, draft, signal) => {
92
118
  const response = await fetchRuntimeJson(draftScope, "/_camox/data", initialInput.runtimeBasePath, { path: pathname }, draft, signal);
93
119
  if (!response.ok) throw new Error(`Navigation failed: ${response.status}`);
@@ -106,6 +132,7 @@ function PageNavigationProvider({ camoxApp, children, initialInput, queryClient
106
132
  const { analytics } = initialInput;
107
133
  const navigate = React.useCallback(async ({ to, replace, source, draft, pop = false, refresh = false }) => {
108
134
  const request = requests.begin();
135
+ const id = ++navigations.current;
109
136
  const href = pop ? to : draftScope.hrefFor(to, {
110
137
  from: window.location.href,
111
138
  draft
@@ -122,12 +149,20 @@ function PageNavigationProvider({ camoxApp, children, initialInput, queryClient
122
149
  if (target.url.hash && viewport) position(viewport, "fragment", target.url.href);
123
150
  return;
124
151
  }
152
+ lastShown.current = new Promise((resolve) => commits.set(id, resolve));
153
+ const settle = () => {
154
+ for (const [waiting, resolve] of commits) {
155
+ if (waiting > id) continue;
156
+ commits.delete(waiting);
157
+ resolve();
158
+ }
159
+ };
125
160
  try {
126
161
  let next = current.current;
127
162
  const targetDraft = draftNamedBy(target.url.href);
128
163
  const fetched = target.pathname !== next.pathname || refresh || targetDraft !== draftNamedBy(next.href);
129
164
  if (fetched) next = await (!refresh && prefetched.take(prefetchKey(target.pathname, targetDraft)) || loadPage(target.pathname, targetDraft, request.signal));
130
- if (!request.isCurrent()) return;
165
+ if (!request.isCurrent()) return settle();
131
166
  const switching = (next.draftId ?? null) !== (current.current.draftId ?? null) || !sameLocale(next.locale, current.current.locale);
132
167
  const positions = restoration();
133
168
  if (!pop) {
@@ -145,6 +180,7 @@ function PageNavigationProvider({ camoxApp, children, initialInput, queryClient
145
180
  };
146
181
  pendingAnnouncement.current = fetched && !refresh ? next.pathname : null;
147
182
  if (fetched) draftScope.adopt(next);
183
+ const switched = switching ? prepareSwitch(next) : void 0;
148
184
  hydrate(queryClient, switching ? next.dehydratedState : preserveStableLayoutBlockCaches(next.dehydratedState, current.current.layoutIdentity, next.layoutIdentity));
149
185
  next = {
150
186
  ...next,
@@ -162,12 +198,14 @@ function PageNavigationProvider({ camoxApp, children, initialInput, queryClient
162
198
  renderDOMHead(manager.head);
163
199
  if (ownsWindow && analytics && next.pathname !== previous.pathname) sendPageView(analytics, window);
164
200
  if (ownsWindow) document.documentElement.lang = next.locale.tag;
165
- React.startTransition(() => setLocation((location) => ({
201
+ React.startTransition(() => setLocation({
166
202
  input: next,
167
203
  source: pop ? void 0 : source,
168
- generation: location.generation + (switching ? 1 : 0)
169
- })));
204
+ switched,
205
+ navigation: id
206
+ }));
170
207
  } catch {
208
+ settle();
171
209
  if (!request.isCurrent()) return;
172
210
  if (pop) window.location.reload();
173
211
  else if (replace) window.location.replace(target.url.href);
@@ -175,12 +213,14 @@ function PageNavigationProvider({ camoxApp, children, initialInput, queryClient
175
213
  }
176
214
  }, [
177
215
  analytics,
216
+ commits,
178
217
  draftScope,
179
218
  initialInput.runtimeBasePath,
180
219
  loadPage,
181
220
  ownsWindow,
182
221
  position,
183
222
  prefetched,
223
+ prepareSwitch,
184
224
  queryClient,
185
225
  requests,
186
226
  restoration
@@ -250,6 +290,21 @@ function PageNavigationProvider({ camoxApp, children, initialInput, queryClient
250
290
  prefetched.clear();
251
291
  };
252
292
  }, [draftScope, prefetched]);
293
+ React.useLayoutEffect(() => {
294
+ for (const [waiting, resolve] of commits) {
295
+ if (waiting > navigation) continue;
296
+ commits.delete(waiting);
297
+ resolve();
298
+ }
299
+ if (switched) setLocation((location) => ({
300
+ ...location,
301
+ switched: void 0
302
+ }));
303
+ }, [
304
+ commits,
305
+ navigation,
306
+ switched
307
+ ]);
253
308
  React.useLayoutEffect(() => {
254
309
  const intent = takeScroll(input.href);
255
310
  const viewport = scroll.current?.viewport();
@@ -310,13 +365,14 @@ function PageNavigationProvider({ camoxApp, children, initialInput, queryClient
310
365
  initialInput.runtimeBasePath,
311
366
  requests
312
367
  ]);
368
+ const hydrated = React.useSyncExternalStore(noSubscription, isClient, isServer);
313
369
  const url = new URL(input.href);
314
370
  const viewLocale = useSameJson({
315
371
  locale: input.locale,
316
372
  locales: input.locales
317
373
  });
318
374
  const alternates = useSameJson(input.alternates ?? null);
319
- return /* @__PURE__ */ jsx(NavigationProvider, {
375
+ return /* @__PURE__ */ jsxs(NavigationProvider, {
320
376
  location: {
321
377
  href: input.href,
322
378
  pathname: input.pathname,
@@ -325,18 +381,38 @@ function PageNavigationProvider({ camoxApp, children, initialInput, queryClient
325
381
  source
326
382
  },
327
383
  navigate,
328
- children: /* @__PURE__ */ jsx(ViewLocaleContext, {
329
- value: viewLocale,
330
- children: /* @__PURE__ */ jsx(PageAlternatesContext, {
331
- value: alternates,
332
- children: /* @__PURE__ */ jsx(PageViewportContext, {
333
- value: pageViewport,
334
- children: /* @__PURE__ */ jsx(React.Fragment, { children: children(input) }, generation)
384
+ children: [/* @__PURE__ */ jsx(ViewSwitchesContext, {
385
+ value: switches,
386
+ children: /* @__PURE__ */ jsx(ViewSwitchDeadlineContext, {
387
+ value: switched?.deadline ?? null,
388
+ children: /* @__PURE__ */ jsx(ShownDraftContext, {
389
+ value: input.draftId ?? null,
390
+ children: /* @__PURE__ */ jsx(ViewLocaleContext, {
391
+ value: viewLocale,
392
+ children: /* @__PURE__ */ jsx(PageAlternatesContext, {
393
+ value: alternates,
394
+ children: /* @__PURE__ */ jsx(PageViewportContext, {
395
+ value: pageViewport,
396
+ children: children(input)
397
+ })
398
+ })
399
+ })
335
400
  })
336
401
  })
337
- })
402
+ }), hydrated && /* @__PURE__ */ jsx(React.Suspense, {
403
+ fallback: null,
404
+ children: /* @__PURE__ */ jsx(SwitchGate, { ready: switched?.ready })
405
+ })]
338
406
  });
339
407
  }
408
+ const noSubscription = () => () => {};
409
+ const isClient = () => true;
410
+ const isServer = () => false;
411
+ /** Suspends the switch's render until the next view is ready. */
412
+ function SwitchGate({ ready }) {
413
+ if (ready) React.use(ready);
414
+ return null;
415
+ }
340
416
 
341
417
  //#endregion
342
418
  export { PageNavigationProvider };
@@ -1,5 +1,5 @@
1
1
  import { DraftScopeContext, createDraftScope, followUrl } from "../../lib/draft-scope.js";
2
- import { StudioCacheContext, createStudioCache } from "../../lib/studio-reads.js";
2
+ import { StudioCacheContext, createStudioCache, reads } from "../../lib/studio-reads.js";
3
3
  import { getDraftReadClient } from "../../lib/api-client.js";
4
4
  import { STUDIO_HANDLE_KEY } from "../renderer/protocol.js";
5
5
  import { CoreCamoxProvider } from "../provider/CoreCamoxProvider.js";
@@ -23,7 +23,8 @@ async function startRendererWithApp(camoxApp) {
23
23
  return;
24
24
  }
25
25
  const input = readHydrationData();
26
- if (!input.apiUrl || !input.authenticationUrl || !input.projectUuid) {
26
+ const { projectUuid, runtimeBasePath } = input;
27
+ if (!input.apiUrl || !input.authenticationUrl || !projectUuid) {
27
28
  console.error("Camox: the renderer document is missing its API or project.");
28
29
  return;
29
30
  }
@@ -44,7 +45,13 @@ async function startRendererWithApp(camoxApp) {
44
45
  queryClient,
45
46
  studioCache,
46
47
  draftScope,
47
- camoxApp
48
+ camoxApp,
49
+ prepareFrame: ({ pathname, derivedLayoutId, locale }) => Promise.all([studioCache.ensure(reads.canvasFrame({
50
+ projectUuid,
51
+ runtimeBasePath,
52
+ pathname,
53
+ ...derivedLayoutId && { derivedLayoutId }
54
+ }), { locale }), codeLoaded])
48
55
  });
49
56
  const container = document.createElement("div");
50
57
  document.body.append(container);
@@ -58,14 +65,14 @@ async function startRendererWithApp(camoxApp) {
58
65
  apiUrl: input.apiUrl,
59
66
  authenticationUrl: input.authenticationUrl,
60
67
  camoxApp,
61
- projectUuid: input.projectUuid,
68
+ projectUuid,
62
69
  initialAuthenticated: true,
63
70
  children: /* @__PURE__ */ jsx(CompleteBlockEditingRuntimeProvider, { children: /* @__PURE__ */ jsx(RendererApp, {
64
71
  connection,
65
72
  camoxApp,
66
73
  codeLoaded,
67
- projectUuid: input.projectUuid,
68
- runtimeBasePath: input.runtimeBasePath
74
+ projectUuid,
75
+ runtimeBasePath
69
76
  }) })
70
77
  })
71
78
  })
@@ -9,7 +9,6 @@ import { FollowAgentContext } from "../followAgentContext.js";
9
9
  import { useThread } from "../../../lib/use-thread.js";
10
10
  import { EditorWhereContext } from "../editorWhereContext.js";
11
11
  import { useEditorSelection } from "../useEditorSelection.js";
12
- import { ConnectAgent } from "./ConnectAgent.js";
13
12
  import { ThreadComposer, useComposerCarrier } from "./ThreadComposer.js";
14
13
  import { ThreadMessages } from "./ThreadMessages.js";
15
14
  import { c } from "react/compiler-runtime";
@@ -27,7 +26,7 @@ import { EMPTY_THREAD_VIEW } from "@camox/api-contract/threads";
27
26
  * it, if it still shows Live; the card the Live turn ends on opens it too. Each message tells the
28
27
  * agent where the editor is: the page they look at, with what they selected on it unless they
29
28
  * cleared it from the composer, or the Canvas with no page selected. "Ask the agent" elsewhere in
30
- * the studio fills its composer. Under the composer, it invites the editor to connect their own AI.
29
+ * the studio fills its composer.
31
30
  */
32
31
  function ChatWidget(t0) {
33
32
  const $ = c(6);
@@ -48,9 +47,9 @@ function ChatWidget(t0) {
48
47
  children: "Agent"
49
48
  })
50
49
  });
51
- t3 = /* @__PURE__ */ jsxs("div", {
50
+ t3 = /* @__PURE__ */ jsx("div", {
52
51
  className: cn("bg-background flex min-h-0 flex-1 flex-col", CARD_BODY_EDGES),
53
- children: [/* @__PURE__ */ jsx(CurrentThread, {}), /* @__PURE__ */ jsx(ConnectAgent, {})]
52
+ children: /* @__PURE__ */ jsx(CurrentThread, {})
54
53
  });
55
54
  $[2] = t2;
56
55
  $[3] = t3;
@@ -6,12 +6,10 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
6
  import { Button } from "@camox/ui/button";
7
7
  import { Check, Copy, ExternalLink } from "lucide-react";
8
8
  import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@camox/ui/dialog";
9
- import { Avatar, AvatarFallback, AvatarGroup } from "@camox/ui/avatar";
10
9
 
11
10
  //#region src/features/studio/components/ConnectAgent.tsx
12
11
  /**
13
- * Common AI apps that take a remote MCP server, with their guide to adding one. In the logo row
14
- * each one overlaps the one before it, so the last is on top.
12
+ * Common AI apps that take a remote MCP server, with their guide to adding one.
15
13
  */
16
14
  const CLIENTS = [
17
15
  {
@@ -125,56 +123,42 @@ function GrokBotLogo(t0) {
125
123
  return t7;
126
124
  }
127
125
  /**
128
- * Under the Chat widget's composer, what invites a member to connect their own AI to Camox as a
129
- * Connected Agent (ADR 0030): the logos of common AI apps, opening a dialog with the link to
130
- * Camox's MCP server and each app's guide to adding an MCP server.
126
+ * In the navbar, what invites a member to connect their own agent to Camox as a Connected Agent
127
+ * (ADR 0030): a button opening a dialog with the link to Camox's MCP server and each common AI
128
+ * app's guide to adding an MCP server.
131
129
  */
132
130
  function ConnectAgent() {
133
- const $ = c(5);
131
+ const $ = c(4);
134
132
  const [open, setOpen] = React.useState(false);
135
133
  let t0;
136
134
  if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
137
- t0 = () => setOpen(true);
135
+ t0 = /* @__PURE__ */ jsx(Button, {
136
+ type: "button",
137
+ variant: "outline",
138
+ onClick: () => setOpen(true),
139
+ children: "Connect your agent"
140
+ });
138
141
  $[0] = t0;
139
142
  } else t0 = $[0];
140
143
  let t1;
141
144
  if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
142
- t1 = /* @__PURE__ */ jsxs("button", {
143
- type: "button",
144
- onClick: t0,
145
- className: "hover:bg-muted text-muted-foreground hover:text-foreground mx-2 mb-2 flex items-center gap-2.5 rounded-md px-2 py-1.5 text-sm transition-colors",
146
- children: [/* @__PURE__ */ jsx(AvatarGroup, { children: CLIENTS.map(_temp) }), "Connect your AI"]
147
- });
145
+ t1 = /* @__PURE__ */ jsx(DialogContent, { children: /* @__PURE__ */ jsx(ConnectAgentContent, {}) });
148
146
  $[1] = t1;
149
147
  } else t1 = $[1];
150
148
  let t2;
151
- if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
152
- t2 = /* @__PURE__ */ jsx(DialogContent, { children: /* @__PURE__ */ jsx(ConnectAgentContent, {}) });
153
- $[2] = t2;
154
- } else t2 = $[2];
155
- let t3;
156
- if ($[3] !== open) {
157
- t3 = /* @__PURE__ */ jsxs(Fragment, { children: [t1, /* @__PURE__ */ jsx(Dialog, {
149
+ if ($[2] !== open) {
150
+ t2 = /* @__PURE__ */ jsxs(Fragment, { children: [t0, /* @__PURE__ */ jsx(Dialog, {
158
151
  open,
159
152
  onOpenChange: setOpen,
160
- children: t2
153
+ children: t1
161
154
  })] });
162
- $[3] = open;
163
- $[4] = t3;
164
- } else t3 = $[4];
165
- return t3;
166
- }
167
- function _temp(client) {
168
- return /* @__PURE__ */ jsx(Avatar, {
169
- size: "sm",
170
- children: /* @__PURE__ */ jsx(AvatarFallback, {
171
- className: "bg-background text-foreground",
172
- children: /* @__PURE__ */ jsx(client.Logo, { className: "size-3.5" })
173
- })
174
- }, client.name);
155
+ $[2] = open;
156
+ $[3] = t2;
157
+ } else t2 = $[3];
158
+ return t2;
175
159
  }
176
160
  function ConnectAgentContent() {
177
- const $ = c(23);
161
+ const $ = c(22);
178
162
  const { apiUrl } = React.useContext(AuthContext);
179
163
  const url = `${apiUrl}/mcp`;
180
164
  const [copied, setCopied] = React.useState(false);
@@ -207,7 +191,7 @@ function ConnectAgentContent() {
207
191
  const copy = t2;
208
192
  let t3;
209
193
  if ($[5] === Symbol.for("react.memo_cache_sentinel")) {
210
- t3 = /* @__PURE__ */ jsxs(DialogHeader, { children: [/* @__PURE__ */ jsx(DialogTitle, { children: "Connect your AI to Camox's MCP server" }), /* @__PURE__ */ jsx(DialogDescription, { children: "Add this URL to your AI as a remote MCP server, then sign in to Camox. Your AI can then ask Camox's agent for changes, with what it knows about your business." })] });
194
+ t3 = /* @__PURE__ */ jsxs(DialogHeader, { children: [/* @__PURE__ */ jsx(DialogTitle, { children: "Connect your agent" }), /* @__PURE__ */ jsx(DialogDescription, { children: "Add this URL to your agent as an MCP server. It acts as you, but can't publish." })] });
211
195
  $[5] = t3;
212
196
  } else t3 = $[5];
213
197
  let t4;
@@ -255,44 +239,34 @@ function ConnectAgentContent() {
255
239
  if ($[18] === Symbol.for("react.memo_cache_sentinel")) {
256
240
  t9 = /* @__PURE__ */ jsx("p", {
257
241
  className: "text-muted-foreground text-xs font-medium",
258
- children: "How to add an MCP server in"
242
+ children: "How to add it in"
259
243
  });
260
244
  $[18] = t9;
261
245
  } else t9 = $[18];
262
246
  let t10;
263
- let t11;
264
247
  if ($[19] === Symbol.for("react.memo_cache_sentinel")) {
265
248
  t10 = /* @__PURE__ */ jsxs("div", {
266
249
  className: "flex flex-col gap-2",
267
250
  children: [t9, /* @__PURE__ */ jsx("ul", {
268
251
  className: "grid grid-cols-2 gap-1",
269
- children: CLIENTS.map(_temp2)
252
+ children: CLIENTS.map(_temp)
270
253
  })]
271
254
  });
272
- t11 = /* @__PURE__ */ jsx("p", {
273
- className: "text-muted-foreground text-xs",
274
- children: "It acts as you, with your permissions. It can't publish: you review and publish here."
275
- });
276
255
  $[19] = t10;
277
- $[20] = t11;
278
- } else {
279
- t10 = $[19];
280
- t11 = $[20];
281
- }
282
- let t12;
283
- if ($[21] !== t8) {
284
- t12 = /* @__PURE__ */ jsxs(Fragment, { children: [
256
+ } else t10 = $[19];
257
+ let t11;
258
+ if ($[20] !== t8) {
259
+ t11 = /* @__PURE__ */ jsxs(Fragment, { children: [
285
260
  t3,
286
261
  t8,
287
- t10,
288
- t11
262
+ t10
289
263
  ] });
290
- $[21] = t8;
291
- $[22] = t12;
292
- } else t12 = $[22];
293
- return t12;
264
+ $[20] = t8;
265
+ $[21] = t11;
266
+ } else t11 = $[21];
267
+ return t11;
294
268
  }
295
- function _temp2(client) {
269
+ function _temp(client) {
296
270
  return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("a", {
297
271
  href: client.docs,
298
272
  target: "_blank",