camox 0.68.0 → 0.71.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 (34) hide show
  1. package/dist/core/createBlock.js +14 -7
  2. package/dist/core/hooks/useArtifactHost.js +56 -46
  3. package/dist/core/lib/artifact.d.ts +4 -0
  4. package/dist/core/lib/artifactHost.js +40 -0
  5. package/dist/core/lib/artifactRuntime.js +20 -1
  6. package/dist/features/canvas/canvasFrameDocument.js +25 -2
  7. package/dist/features/canvas/useCanvasFrame.js +6 -2
  8. package/dist/features/preview/components/previewStyles.js +11 -2
  9. package/dist/features/provider/AuthenticatedCamoxProvider.js +33 -28
  10. package/dist/features/renderer/RendererApp.js +202 -154
  11. package/dist/features/renderer/RendererFrames.js +87 -55
  12. package/dist/features/renderer/definitionAnswers.js +1 -0
  13. package/dist/features/renderer/rendererHost.js +4 -0
  14. package/dist/features/renderer/studioConnection.js +9 -1
  15. package/dist/features/runtime/pageApp.js +7 -3
  16. package/dist/features/runtime/pageNavigation.js +93 -17
  17. package/dist/features/runtime/rendererClient.js +13 -6
  18. package/dist/features/studio/agentRunToast.js +71 -0
  19. package/dist/features/studio/components/ChatWidget.js +2 -0
  20. package/dist/features/studio/components/DraftBar.js +2 -3
  21. package/dist/features/studio/components/DraftsControl.js +181 -198
  22. package/dist/features/studio/components/LocalePicker.js +22 -4
  23. package/dist/features/studio/components/ReviewDraftSheet.js +345 -568
  24. package/dist/features/studio/draftLabel.js +1 -1
  25. package/dist/features/studio/studioStore.js +5 -17
  26. package/dist/features/studio/viewSwitch.js +62 -0
  27. package/dist/lib/auth.js +5 -1
  28. package/dist/lib/draft-scope.js +28 -15
  29. package/dist/lib/studio-reads.js +46 -13
  30. package/dist/lib/view-switch.js +64 -0
  31. package/dist/package.js +1 -1
  32. package/dist/studio.css +1 -1
  33. package/package.json +6 -4
  34. package/skills/camox/references/artifacts.md +85 -1
@@ -4,7 +4,7 @@ const dayFormat = new Intl.DateTimeFormat(void 0, {
4
4
  day: "numeric"
5
5
  });
6
6
  /**
7
- * A draft's name, or for an unnamed draft its creator and creation date formatted for the
7
+ * A draft's name, or for a draft not named yet its creator and creation date formatted for the
8
8
  * viewer, e.g. "Rémi, Oct 13".
9
9
  */
10
10
  function draftLabel(draft) {
@@ -5,10 +5,7 @@ const studioStore = createStore({
5
5
  context: {
6
6
  isCommandPaletteOpen: false,
7
7
  commandPalettePages: [],
8
- reviewDraftSheet: {
9
- open: false,
10
- startRenaming: false
11
- },
8
+ isReviewDraftSheetOpen: false,
12
9
  isDeleteDraftDialogOpen: false,
13
10
  localeDialog: null,
14
11
  websiteLocation: null,
@@ -48,22 +45,16 @@ const studioStore = createStore({
48
45
  commandPalettePages: context.commandPalettePages.slice(0, -1)
49
46
  };
50
47
  },
51
- openReviewDraftSheet: (context, event) => {
48
+ openReviewDraftSheet: (context) => {
52
49
  return {
53
50
  ...context,
54
- reviewDraftSheet: {
55
- open: true,
56
- startRenaming: event.startRenaming ?? false
57
- }
51
+ isReviewDraftSheetOpen: true
58
52
  };
59
53
  },
60
54
  closeReviewDraftSheet: (context) => {
61
55
  return {
62
56
  ...context,
63
- reviewDraftSheet: {
64
- open: false,
65
- startRenaming: false
66
- }
57
+ isReviewDraftSheetOpen: false
67
58
  };
68
59
  },
69
60
  openDeleteDraftDialog: (context) => {
@@ -93,10 +84,7 @@ const studioStore = createStore({
93
84
  askAgent: (context, event) => {
94
85
  return {
95
86
  ...context,
96
- reviewDraftSheet: {
97
- open: false,
98
- startRenaming: false
99
- },
87
+ isReviewDraftSheetOpen: false,
100
88
  agentAsk: {
101
89
  text: event.text,
102
90
  id: context.agentAsks + 1
@@ -0,0 +1,62 @@
1
+ import { useDraftScope } from "../../lib/draft-scope.js";
2
+ import { useViewShown } from "../../lib/view-switch.js";
3
+ import { c } from "react/compiler-runtime";
4
+ import { toast } from "@camox/ui/toaster";
5
+
6
+ //#region src/features/studio/viewSwitch.ts
7
+ /**
8
+ * Tells the editor the studio heard them while it switches to another draft or locale: the
9
+ * studio keeps showing what it showed until the next view is ready, then shows it at once.
10
+ * `switching` settles once it shows.
11
+ */
12
+ function toastSwitch(switching, messages) {
13
+ toast.promise(switching, {
14
+ ...messages,
15
+ error: "Something went wrong while switching. Reload the page to try again."
16
+ });
17
+ }
18
+ /**
19
+ * Switches to another draft or locale with `navigate`, which starts the navigation that does,
20
+ * saying so in a toast until the next view shows.
21
+ */
22
+ function useSwitchView() {
23
+ const $ = c(2);
24
+ const shown = useViewShown();
25
+ let t0;
26
+ if ($[0] !== shown) {
27
+ t0 = (navigate, messages) => toastSwitch(Promise.resolve(navigate()).then(() => shown()), messages);
28
+ $[0] = shown;
29
+ $[1] = t0;
30
+ } else t0 = $[1];
31
+ return t0;
32
+ }
33
+ /**
34
+ * Switches the studio to Live once the draft it views ended, by this member, here or in another
35
+ * tab, replacing the draft's URL entry. Called once the studio already left the draft, it does
36
+ * nothing.
37
+ */
38
+ function useLeaveDraft() {
39
+ const $ = c(3);
40
+ const draftScope = useDraftScope();
41
+ const shown = useViewShown();
42
+ let t0;
43
+ if ($[0] !== draftScope || $[1] !== shown) {
44
+ t0 = (ended) => {
45
+ if (ended && draftScope.current() !== ended.draftId) return;
46
+ draftScope.leave(ended);
47
+ if (!ended) return;
48
+ const what = `Draft ${ended.outcome}`;
49
+ toastSwitch(shown(), {
50
+ loading: `${what}. Switching to Live…`,
51
+ success: `${what}. You're now viewing Live.`
52
+ });
53
+ };
54
+ $[0] = draftScope;
55
+ $[1] = shown;
56
+ $[2] = t0;
57
+ } else t0 = $[2];
58
+ return t0;
59
+ }
60
+
61
+ //#endregion
62
+ export { useLeaveDraft, useSwitchView };
package/dist/lib/auth.js CHANGED
@@ -250,6 +250,10 @@ function useSessionState(authClient, track) {
250
250
  const subscribe = t1;
251
251
  return React.useSyncExternalStore(track ? subscribe : noSubscription, getSnapshot, getSnapshot);
252
252
  }
253
+ /** The id of the member signed in, as the session the client holds says: null without one. */
254
+ function signedInUserId(authClient) {
255
+ return authClient.$store.atoms.session.value.data?.user?.id ?? null;
256
+ }
253
257
  function useAuthState() {
254
258
  const $ = c(10);
255
259
  const { authClient, initialAuthenticated: t0, exchangingToken: t1, sessionless: t2 } = useAuthContext();
@@ -394,4 +398,4 @@ function _temp4() {
394
398
  }
395
399
 
396
400
  //#endregion
397
- export { AuthContext, createCamoxAuthClient, getAuthCookieHeader, getAuthRequestCredentials, useAuthActions, useAuthContext, useAuthState, useIsAuthenticated, useProcessOtt, useProjectUuid, useSignInRedirect };
401
+ export { AuthContext, createCamoxAuthClient, getAuthCookieHeader, getAuthRequestCredentials, signedInUserId, useAuthActions, useAuthContext, useAuthState, useIsAuthenticated, useProcessOtt, useProjectUuid, useSignInRedirect };
@@ -158,37 +158,50 @@ const DraftScopeContext = React.createContext(null);
158
158
  /**
159
159
  * The draft scope the studio is in, for handlers and effects. Render code reads the draft it
160
160
  * views from `useCurrentDraftId`: the scope object never changes, so a memoized render reading
161
- * `current()` from it would keep showing the draft of its first render.
161
+ * `current()` from it would keep showing the draft of its first render, and while a switch to
162
+ * another draft loads, `current()` already names the next one.
162
163
  */
163
164
  function useDraftScope() {
164
165
  const scope = React.useContext(DraftScopeContext);
165
166
  if (!scope) throw new Error("useDraftScope must be used inside a DraftScopeContext provider");
166
167
  return scope;
167
168
  }
169
+ /**
170
+ * The draft the page runtime's committed render shows, or null for Live: undefined outside it.
171
+ * A switch to another draft renders in a transition, so until it commits the studio keeps
172
+ * showing the draft it was in, while the scope already reads and writes the next one.
173
+ */
174
+ const ShownDraftContext = React.createContext(void 0);
168
175
  const noScope = () => null;
169
176
  const noSubscription = () => () => {};
177
+ /** The draft the render shows: the committed one in the page runtime, the scope's elsewhere. */
178
+ function useShownDraft(scope) {
179
+ const shown = React.use(ShownDraftContext);
180
+ const current = scope?.current ?? noScope;
181
+ const followed = shown === void 0 && scope ? scope.subscribe : noSubscription;
182
+ const draftId = React.useSyncExternalStore(followed, current, current);
183
+ return shown === void 0 ? draftId : shown;
184
+ }
170
185
  /** The draft the studio is in, or null for Live. The caller re-renders after every move. */
171
186
  function useCurrentDraftId() {
172
- const scope = useDraftScope();
173
- return React.useSyncExternalStore(scope.subscribe, scope.current, scope.current);
187
+ return useShownDraft(useDraftScope());
174
188
  }
175
189
  /** The draft scope and the draft it views, outside the studio too, where there is none. */
176
190
  function useOptionalDraftScope() {
177
191
  const $ = c(3);
178
192
  const scope = React.useContext(DraftScopeContext);
179
- const current = scope?.current ?? noScope;
180
- const draftId = React.useSyncExternalStore(scope?.subscribe ?? noSubscription, current, current);
181
- let t0;
182
- if ($[0] !== draftId || $[1] !== scope) {
183
- t0 = {
193
+ const t0 = useShownDraft(scope);
194
+ let t1;
195
+ if ($[0] !== scope || $[1] !== t0) {
196
+ t1 = {
184
197
  scope,
185
- draftId
198
+ draftId: t0
186
199
  };
187
- $[0] = draftId;
188
- $[1] = scope;
189
- $[2] = t0;
190
- } else t0 = $[2];
191
- return t0;
200
+ $[0] = scope;
201
+ $[1] = t0;
202
+ $[2] = t1;
203
+ } else t1 = $[2];
204
+ return t1;
192
205
  }
193
206
  /**
194
207
  * Follows the draft a browser URL names, for a studio mounted in a host application, whose
@@ -199,4 +212,4 @@ function followUrl(scope, href) {
199
212
  }
200
213
 
201
214
  //#endregion
202
- export { DraftScopeContext, createDraftScope, draftNamedBy, followUrl, useCurrentDraftId, useDraftScope, useOptionalDraftScope, withoutDraft };
215
+ export { DraftScopeContext, ShownDraftContext, createDraftScope, draftNamedBy, followUrl, useCurrentDraftId, useDraftScope, useOptionalDraftScope, withoutDraft };
@@ -2,11 +2,12 @@ import { useCurrentDraftId } from "./draft-scope.js";
2
2
  import { ViewLocaleContext } from "./view-locale-context.js";
3
3
  import { fetchRuntimeJson } from "../features/runtime/navigationTarget.js";
4
4
  import { collectFileIds } from "./normalized-data.js";
5
+ import { useSwitchHold } from "./view-switch.js";
5
6
  import { c } from "react/compiler-runtime";
6
7
  import * as React from "react";
7
8
  import { inScope, queryKeys, scopeOfKey } from "@camox/api-contract/query-keys";
8
9
  import { PATROLLER_IDS } from "@camox/api-contract/patrol";
9
- import { useQueries, useQuery, useSuspenseQuery } from "@tanstack/react-query";
10
+ import { useQueries, useQuery, useQueryClient, useSuspenseQuery } from "@tanstack/react-query";
10
11
 
11
12
  //#region src/lib/studio-reads.ts
12
13
  function entry(definition) {
@@ -505,7 +506,7 @@ function useStudioCache() {
505
506
  }
506
507
  /**
507
508
  * The draft and locale the caller's render shows, which its reads name: the read hooks re-render
508
- * after a move to another draft, and a locale switch mounts the page afresh.
509
+ * after a move to another draft or locale, reading its keys.
509
510
  */
510
511
  function useShownTarget() {
511
512
  const $ = c(3);
@@ -548,7 +549,7 @@ function useRead(read) {
548
549
  * the locale the caller's render shows, unless `locale` names another.
549
550
  */
550
551
  function useReadQuery(read, t0) {
551
- const $ = c(13);
552
+ const $ = c(18);
552
553
  let t1;
553
554
  if ($[0] !== t0) {
554
555
  t1 = t0 === void 0 ? {} : t0;
@@ -558,6 +559,7 @@ function useReadQuery(read, t0) {
558
559
  const { enabled, select, locale } = t1;
559
560
  const shown = useShownTarget();
560
561
  const cache = useStudioCache();
562
+ const queryClient = useQueryClient();
561
563
  let t2;
562
564
  if ($[2] !== locale || $[3] !== shown) {
563
565
  t2 = locale ? {
@@ -577,24 +579,38 @@ function useReadQuery(read, t0) {
577
579
  $[7] = read;
578
580
  $[8] = t3;
579
581
  } else t3 = $[8];
582
+ const options = t3;
580
583
  let t4;
581
- if ($[9] !== enabled || $[10] !== select || $[11] !== t3) {
584
+ if ($[9] !== enabled || $[10] !== options || $[11] !== select) {
582
585
  t4 = {
583
- ...t3,
586
+ ...options,
584
587
  enabled,
585
588
  select
586
589
  };
587
590
  $[9] = enabled;
588
- $[10] = select;
589
- $[11] = t3;
591
+ $[10] = options;
592
+ $[11] = select;
590
593
  $[12] = t4;
591
594
  } else t4 = $[12];
592
- return useQuery(t4);
595
+ const result = useQuery(t4);
596
+ let t5;
597
+ if ($[13] !== enabled || $[14] !== options || $[15] !== queryClient || $[16] !== result) {
598
+ t5 = () => enabled !== false && result.isPending ? queryClient.fetchQuery(options) : null;
599
+ $[13] = enabled;
600
+ $[14] = options;
601
+ $[15] = queryClient;
602
+ $[16] = result;
603
+ $[17] = t5;
604
+ } else t5 = $[17];
605
+ const hold = useSwitchHold(t5);
606
+ if (hold) throw hold;
607
+ return result;
593
608
  }
594
609
  function useReadQueries(list, combine) {
595
- const $ = c(10);
610
+ const $ = c(13);
596
611
  const inDraft = useShownTarget();
597
612
  const cache = useStudioCache();
613
+ const queryClient = useQueryClient();
598
614
  let t0;
599
615
  if ($[0] !== cache || $[1] !== inDraft || $[2] !== list) {
600
616
  let t1;
@@ -610,17 +626,34 @@ function useReadQueries(list, combine) {
610
626
  $[2] = list;
611
627
  $[3] = t0;
612
628
  } else t0 = $[3];
629
+ const queries = t0;
613
630
  let t1;
614
- if ($[7] !== combine || $[8] !== t0) {
631
+ if ($[7] !== combine || $[8] !== queries) {
615
632
  t1 = {
616
- queries: t0,
633
+ queries,
617
634
  combine
618
635
  };
619
636
  $[7] = combine;
620
- $[8] = t0;
637
+ $[8] = queries;
621
638
  $[9] = t1;
622
639
  } else t1 = $[9];
623
- return useQueries(t1);
640
+ const combined = useQueries(t1);
641
+ let t2;
642
+ if ($[10] !== queries || $[11] !== queryClient) {
643
+ t2 = () => {
644
+ const loading = queries.filter((t3) => {
645
+ const { queryKey } = t3;
646
+ return (queryClient.getQueryState(queryKey)?.status ?? "pending") === "pending";
647
+ });
648
+ return loading.length ? Promise.all(loading.map((options) => queryClient.fetchQuery(options))) : null;
649
+ };
650
+ $[10] = queries;
651
+ $[11] = queryClient;
652
+ $[12] = t2;
653
+ } else t2 = $[12];
654
+ const hold = useSwitchHold(t2);
655
+ if (hold) throw hold;
656
+ return combined;
624
657
  }
625
658
 
626
659
  //#endregion
@@ -0,0 +1,64 @@
1
+ import { c } from "react/compiler-runtime";
2
+ import * as React from "react";
3
+
4
+ //#region src/lib/view-switch.ts
5
+ const ViewSwitchesContext = React.createContext(null);
6
+ /**
7
+ * Set while a render switches to another view: the time after which it stops waiting for reads
8
+ * and commits with what it has. Null otherwise.
9
+ */
10
+ const ViewSwitchDeadlineContext = React.createContext(null);
11
+ /** How long a switch waits for the next view before showing it however far it got. */
12
+ const SWITCH_WAIT_MS = 1e4;
13
+ /** Prepares the studio's part of every switch with `preparer`, while mounted. */
14
+ function useViewPreparer(preparer) {
15
+ const $ = c(5);
16
+ const switches = React.use(ViewSwitchesContext);
17
+ const prepare = React.useEffectEvent(preparer);
18
+ let t0;
19
+ if ($[0] !== prepare || $[1] !== switches) {
20
+ t0 = () => switches?.prepare((next) => prepare(next));
21
+ $[0] = prepare;
22
+ $[1] = switches;
23
+ $[2] = t0;
24
+ } else t0 = $[2];
25
+ let t1;
26
+ if ($[3] !== switches) {
27
+ t1 = [switches];
28
+ $[3] = switches;
29
+ $[4] = t1;
30
+ } else t1 = $[4];
31
+ React.useEffect(t0, t1);
32
+ }
33
+ /**
34
+ * The last navigation started, settling once it shows: for the studio to say a switch it started,
35
+ * or one that followed what it did, is under way. Outside the page runtime, it settles at once.
36
+ */
37
+ function useViewShown() {
38
+ const $ = c(2);
39
+ const switches = React.use(ViewSwitchesContext);
40
+ let t0;
41
+ if ($[0] !== switches) {
42
+ t0 = () => switches?.shown() ?? Promise.resolve();
43
+ $[0] = switches;
44
+ $[1] = t0;
45
+ } else t0 = $[1];
46
+ return t0;
47
+ }
48
+ /**
49
+ * While a render switches views, a read still loading holds the switch back rather than show
50
+ * its loading state: the promise to suspend on until it loads or the switch stops waiting.
51
+ * Undefined when the render should show the read as it is.
52
+ */
53
+ function useSwitchHold(loading) {
54
+ const deadline = React.use(ViewSwitchDeadlineContext);
55
+ if (deadline === null) return void 0;
56
+ const left = deadline - Date.now();
57
+ if (left <= 0) return void 0;
58
+ const pending = loading();
59
+ if (!pending) return void 0;
60
+ return Promise.race([pending.catch(() => {}), new Promise((resolve) => setTimeout(resolve, left))]);
61
+ }
62
+
63
+ //#endregion
64
+ export { SWITCH_WAIT_MS, ViewSwitchDeadlineContext, ViewSwitchesContext, useSwitchHold, useViewPreparer, useViewShown };
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.68.0";
2
+ var version = "0.71.0";
3
3
 
4
4
  //#endregion
5
5
  export { version };