camox 0.42.0 → 0.44.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 (99) hide show
  1. package/bin/camox.mjs +9 -1
  2. package/dist/core/components/lexical/{GradientNode.js → HighlightNode.js} +34 -34
  3. package/dist/core/components/lexical/InlineLexicalEditor.js +14 -14
  4. package/dist/core/components/lexical/InlineStylesPlugin.js +25 -9
  5. package/dist/core/components/lexical/SelectionBroadcaster.js +7 -7
  6. package/dist/core/components/lexical/SidebarLexicalEditor.js +24 -24
  7. package/dist/core/components/lexical/TextLinkPopover.js +191 -208
  8. package/dist/core/components/lexical/editorConfig.js +2 -2
  9. package/dist/core/createApp.d.ts +31 -4
  10. package/dist/core/createApp.js +2 -1
  11. package/dist/core/createBlock.js +24 -10
  12. package/dist/core/createLayout.d.ts +16 -3
  13. package/dist/core/createLayout.js +15 -15
  14. package/dist/core/derivedRoutes.js +1 -1
  15. package/dist/core/dom.d.ts +17 -0
  16. package/dist/core/dom.js +22 -0
  17. package/dist/core/editing/createEditableBlock.d.ts +135 -1
  18. package/dist/core/editing/createEditableBlock.js +209 -74
  19. package/dist/core/lib/contentType.d.ts +5 -0
  20. package/dist/core/lib/contentType.js +11 -0
  21. package/dist/core/lib/fieldTypes.js +1 -0
  22. package/dist/core/lib/iconTypes.d.ts +13 -0
  23. package/dist/core/lib/icons.d.ts +6 -0
  24. package/dist/core/lib/icons.js +130 -0
  25. package/dist/core/lib/lexicalReact.js +4 -4
  26. package/dist/core/lib/lexicalState.js +8 -8
  27. package/dist/core/lib/modifierFormats.js +2 -2
  28. package/dist/core/lib/textLinks.js +5 -2
  29. package/dist/core/lib/textStyles.d.ts +3 -3
  30. package/dist/core/lib/textStyles.js +9 -14
  31. package/dist/core/pageDestinations.js +37 -0
  32. package/dist/features/content/components/AssetCard.js +104 -68
  33. package/dist/features/og/imageResponse.node.js +11 -9
  34. package/dist/features/preview/CamoxPreview.js +148 -146
  35. package/dist/features/preview/commentsEnabled.js +2 -2
  36. package/dist/features/preview/components/AddBlockSidebar.js +1 -1
  37. package/dist/features/preview/components/AssetLightbox.js +17 -6
  38. package/dist/features/preview/components/AttachedComments.js +224 -87
  39. package/dist/features/preview/components/BlockActionsPopover.js +1 -1
  40. package/dist/features/preview/components/CommentTargetQuote.js +22 -21
  41. package/dist/features/preview/components/CreatePageModal.js +4 -4
  42. package/dist/features/preview/components/DerivedLayoutDialog.js +36 -21
  43. package/dist/features/preview/components/DerivedLayoutSidebar.js +45 -26
  44. package/dist/features/preview/components/DerivedPageInfoSidebar.js +38 -26
  45. package/dist/features/preview/components/FieldOverlayStyles.js +29 -9
  46. package/dist/features/preview/components/FieldToolbar.js +12 -12
  47. package/dist/features/preview/components/IconFieldEditor.js +231 -0
  48. package/dist/features/preview/components/ItemFieldsEditor.js +21 -14
  49. package/dist/features/preview/components/LinkFieldEditor.js +212 -209
  50. package/dist/features/preview/components/OverlayTracker.js +1 -1
  51. package/dist/features/preview/components/PageEditorSidebar.js +1 -1
  52. package/dist/features/preview/components/PageInfoSidebar.js +28 -28
  53. package/dist/features/preview/components/PagePicker.js +46 -46
  54. package/dist/features/preview/components/PageTree.js +1 -1
  55. package/dist/features/preview/components/PreviewPanel.js +1 -1
  56. package/dist/features/preview/components/PreviewToolbar.js +224 -225
  57. package/dist/features/preview/components/PublicationRow.js +8 -4
  58. package/dist/features/preview/components/SendFeedbackDialog.js +83 -0
  59. package/dist/features/preview/components/useRepeatableItemActions.js +1 -1
  60. package/dist/features/preview/overlayHighlights.js +54 -0
  61. package/dist/features/preview/previewCommentsStore.js +54 -34
  62. package/dist/features/preview/previewSelection.js +28 -23
  63. package/dist/features/preview/publication.js +10 -0
  64. package/dist/features/preview/usePageComments.js +39 -0
  65. package/dist/features/provider/CoreCamoxProvider.js +38 -6
  66. package/dist/features/provider/LocalhostPreviewProvider.js +81 -45
  67. package/dist/features/provider/components/CommandPalette.js +35 -84
  68. package/dist/features/runtime/pageApp.js +32 -28
  69. package/dist/features/runtime/pageServer.js +4 -1
  70. package/dist/features/runtime/runtime.d.ts +2 -0
  71. package/dist/features/runtime/runtime.js +45 -21
  72. package/dist/features/studio/components/EnvironmentMenu.js +76 -63
  73. package/dist/features/studio/components/Navbar.js +53 -80
  74. package/dist/features/vite/auth.js +1 -50
  75. package/dist/features/vite/iconGeneration.js +16 -0
  76. package/dist/features/vite/layoutGeneration.js +7 -3
  77. package/dist/features/vite/vite.d.ts +4 -2
  78. package/dist/features/vite/vite.js +24 -3
  79. package/dist/hooks/use-page-destinations.js +53 -0
  80. package/dist/lib/auth.js +40 -20
  81. package/dist/lib/preview-handoff.js +13 -0
  82. package/dist/lib/queries.js +12 -1
  83. package/dist/lib/server-auth-cookie.js +21 -0
  84. package/dist/lib/use-project-room.js +5 -2
  85. package/dist/studio-overlays.css +19 -15
  86. package/dist/studio.css +1 -1
  87. package/package.json +9 -4
  88. package/skills/camox/SKILL.md +38 -9
  89. package/skills/camox/references/block-definitions.md +107 -286
  90. package/skills/camox/references/cli-common.md +16 -4
  91. package/skills/camox/references/cli-content.md +158 -0
  92. package/skills/camox/references/cli-environments.md +1 -1
  93. package/skills/camox/references/dom-integrations.md +93 -0
  94. package/skills/camox/references/field-styling.md +50 -0
  95. package/skills/camox/references/layout-definitions.md +33 -147
  96. package/skills/camox/references/routed-layouts.md +71 -0
  97. package/skills/camoxify/SKILL.md +66 -0
  98. package/skills/camox/references/cli-blocks.md +0 -78
  99. package/skills/camox/references/cli-pages.md +0 -53
@@ -1,7 +1,6 @@
1
1
  import { Link as Link$1, useLocation, useNavigate } from "../../navigation/navigation.js";
2
2
  import { actionsStore } from "../../provider/actionsStore.js";
3
- import { useProjectSlug } from "../../../lib/auth.js";
4
- import { pageQueries, projectQueries } from "../../../lib/queries.js";
3
+ import { usePageDestinations } from "../../../hooks/use-page-destinations.js";
5
4
  import { PlatformLabel } from "../../../components/PlatformLabel.js";
6
5
  import { cn } from "../../../lib/utils.js";
7
6
  import { STUDIO_CONTENT_PATH } from "../routes.js";
@@ -10,7 +9,6 @@ import { EnvironmentMenu } from "./EnvironmentMenu.js";
10
9
  import { ProjectMenu } from "./ProjectMenu.js";
11
10
  import { UserButton } from "./UserButton.js";
12
11
  import { c } from "react/compiler-runtime";
13
- import { useQuery } from "@tanstack/react-query";
14
12
  import * as React from "react";
15
13
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
16
14
  import { Kbd } from "@camox/ui/kbd";
@@ -36,97 +34,72 @@ const links = [{
36
34
  ]
37
35
  }];
38
36
  const Navbar = (t0) => {
39
- const $ = c(20);
40
- if ($[0] !== "4608f306773df1a594409c84a68bc5a0c72c93c4fbda9136da94d4900e84546d") {
41
- for (let $i = 0; $i < 20; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
42
- $[0] = "4608f306773df1a594409c84a68bc5a0c72c93c4fbda9136da94d4900e84546d";
37
+ const $ = c(13);
38
+ if ($[0] !== "9182f23e40dc6d439cec2c533b4f9436ee59f885213d6d7a79d434c7aa8349c6") {
39
+ for (let $i = 0; $i < 13; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
40
+ $[0] = "9182f23e40dc6d439cec2c533b4f9436ee59f885213d6d7a79d434c7aa8349c6";
43
41
  }
44
42
  const { isPreview: t1 } = t0;
45
43
  const isPreview = t1 === void 0 ? false : t1;
46
- const projectSlug = useProjectSlug();
47
- let t2;
48
- if ($[1] !== projectSlug) {
49
- t2 = projectQueries.getBySlug(projectSlug);
50
- $[1] = projectSlug;
51
- $[2] = t2;
52
- } else t2 = $[2];
53
- const { data: project } = useQuery(t2);
54
- let t3;
55
- if ($[3] !== project?.id) {
56
- t3 = pageQueries.list(project?.id ?? 0);
57
- $[3] = project?.id;
58
- $[4] = t3;
59
- } else t3 = $[4];
60
- const t4 = !!project;
61
- let t5;
62
- if ($[5] !== t3 || $[6] !== t4) {
63
- t5 = {
64
- ...t3,
65
- enabled: t4
66
- };
67
- $[5] = t3;
68
- $[6] = t4;
69
- $[7] = t5;
70
- } else t5 = $[7];
71
- const { data: pages } = useQuery(t5);
44
+ const pages = usePageDestinations();
72
45
  const { pathname } = useLocation();
73
- let t6;
74
- if ($[8] === Symbol.for("react.memo_cache_sentinel")) {
75
- t6 = /* @__PURE__ */ jsxs("div", {
46
+ let t2;
47
+ if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
48
+ t2 = /* @__PURE__ */ jsxs("div", {
76
49
  className: "flew-row flex gap-1",
77
50
  children: [/* @__PURE__ */ jsx(ProjectMenu, {}), /* @__PURE__ */ jsx(EnvironmentMenu, {})]
78
51
  });
79
- $[8] = t6;
80
- } else t6 = $[8];
81
- let t7;
82
- if ($[9] !== isPreview || $[10] !== pages || $[11] !== pathname) {
83
- t7 = links.map((link, index) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(Link$1, {
52
+ $[1] = t2;
53
+ } else t2 = $[1];
54
+ let t3;
55
+ if ($[2] !== isPreview || $[3] !== pages || $[4] !== pathname) {
56
+ t3 = links.map((link, index) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(Link$1, {
84
57
  to: link.to,
85
58
  className: cn("flex gap-2 items-center rounded-md px-4 py-2 text-sm font-medium", "hover:bg-accent hover:text-accent-foreground outline-none transition-[color,box-shadow] focus-visible:ring-ring/50 focus-visible:ring-[3px] focus-visible:outline-1", (isPreview || pages?.some((page) => page.fullPath === pathname)) && index === 0 ? "bg-accent hover:bg-accent text-accent-foreground" : "text-muted-foreground"),
86
59
  activeProps: { className: "bg-accent hover:bg-accent text-accent-foreground!" },
87
60
  children: link.children
88
61
  }) }, link.to));
89
- $[9] = isPreview;
90
- $[10] = pages;
91
- $[11] = pathname;
92
- $[12] = t7;
93
- } else t7 = $[12];
94
- let t8;
95
- if ($[13] !== t7) {
96
- t8 = /* @__PURE__ */ jsxs("div", {
62
+ $[2] = isPreview;
63
+ $[3] = pages;
64
+ $[4] = pathname;
65
+ $[5] = t3;
66
+ } else t3 = $[5];
67
+ let t4;
68
+ if ($[6] !== t3) {
69
+ t4 = /* @__PURE__ */ jsxs("div", {
97
70
  className: "flex flex-row gap-2",
98
- children: [t6, /* @__PURE__ */ jsx("ul", {
71
+ children: [t2, /* @__PURE__ */ jsx("ul", {
99
72
  className: "flex items-center gap-1",
100
- children: t7
73
+ children: t3
101
74
  })]
102
75
  });
103
- $[13] = t7;
104
- $[14] = t8;
105
- } else t8 = $[14];
106
- let t10;
107
- let t9;
108
- if ($[15] === Symbol.for("react.memo_cache_sentinel")) {
109
- t9 = /* @__PURE__ */ jsx(SearchIcon, { className: "text-muted-foreground size-4" });
110
- t10 = /* @__PURE__ */ jsx("span", {
76
+ $[6] = t3;
77
+ $[7] = t4;
78
+ } else t4 = $[7];
79
+ let t5;
80
+ let t6;
81
+ if ($[8] === Symbol.for("react.memo_cache_sentinel")) {
82
+ t5 = /* @__PURE__ */ jsx(SearchIcon, { className: "text-muted-foreground size-4" });
83
+ t6 = /* @__PURE__ */ jsx("span", {
111
84
  className: "text-muted-foreground",
112
85
  children: "Quick find"
113
86
  });
114
- $[15] = t10;
115
- $[16] = t9;
87
+ $[8] = t5;
88
+ $[9] = t6;
116
89
  } else {
117
- t10 = $[15];
118
- t9 = $[16];
90
+ t5 = $[8];
91
+ t6 = $[9];
119
92
  }
120
- let t11;
121
- if ($[17] === Symbol.for("react.memo_cache_sentinel")) {
122
- t11 = /* @__PURE__ */ jsxs("div", {
93
+ let t7;
94
+ if ($[10] === Symbol.for("react.memo_cache_sentinel")) {
95
+ t7 = /* @__PURE__ */ jsxs("div", {
123
96
  className: "flex items-center gap-2",
124
97
  children: [/* @__PURE__ */ jsxs(Button, {
125
98
  variant: "outline",
126
99
  onClick: _temp,
127
100
  children: [
128
- t9,
129
- t10,
101
+ t5,
102
+ t6,
130
103
  /* @__PURE__ */ jsx(Kbd, {
131
104
  className: "ml-4",
132
105
  children: /* @__PURE__ */ jsx(PlatformLabel, {
@@ -137,24 +110,24 @@ const Navbar = (t0) => {
137
110
  ]
138
111
  }), /* @__PURE__ */ jsx(UserButton, {})]
139
112
  });
140
- $[17] = t11;
141
- } else t11 = $[17];
142
- let t12;
143
- if ($[18] !== t8) {
144
- t12 = /* @__PURE__ */ jsxs("nav", {
113
+ $[10] = t7;
114
+ } else t7 = $[10];
115
+ let t8;
116
+ if ($[11] !== t4) {
117
+ t8 = /* @__PURE__ */ jsxs("nav", {
145
118
  className: "bg-background relative flex items-center justify-between gap-4 border-b-2 px-2 py-2",
146
- children: [t8, t11]
119
+ children: [t4, t7]
147
120
  });
148
- $[18] = t8;
149
- $[19] = t12;
150
- } else t12 = $[19];
151
- return t12;
121
+ $[11] = t4;
122
+ $[12] = t8;
123
+ } else t8 = $[12];
124
+ return t8;
152
125
  };
153
126
  function useNavbarActions() {
154
127
  const $ = c(4);
155
- if ($[0] !== "4608f306773df1a594409c84a68bc5a0c72c93c4fbda9136da94d4900e84546d") {
128
+ if ($[0] !== "9182f23e40dc6d439cec2c533b4f9436ee59f885213d6d7a79d434c7aa8349c6") {
156
129
  for (let $i = 0; $i < 4; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
157
- $[0] = "4608f306773df1a594409c84a68bc5a0c72c93c4fbda9136da94d4900e84546d";
130
+ $[0] = "9182f23e40dc6d439cec2c533b4f9436ee59f885213d6d7a79d434c7aa8349c6";
158
131
  }
159
132
  const navigate = useNavigate();
160
133
  let t0;
@@ -1,52 +1,3 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import os from "node:os";
1
+ import { readAuthTokenForUrl, verifyOneTimeToken, writeAuthTokenForUrl } from "@camox/cli/auth";
4
2
 
5
- //#region src/features/vite/auth.ts
6
- const AUTH_DIR = path.join(os.homedir(), ".camox");
7
- const AUTH_FILE = path.join(AUTH_DIR, "auth.json");
8
- function normalizeUrl(url) {
9
- return url.replace(/\/+$/, "");
10
- }
11
- function readAllTokens() {
12
- try {
13
- return JSON.parse(fs.readFileSync(AUTH_FILE, "utf-8"));
14
- } catch {
15
- return {};
16
- }
17
- }
18
- function writeAllTokens(tokens) {
19
- fs.mkdirSync(AUTH_DIR, { recursive: true });
20
- fs.writeFileSync(AUTH_FILE, JSON.stringify(tokens, null, 2), { mode: 384 });
21
- }
22
- function readAuthTokenForUrl(authenticationUrl) {
23
- const entry = readAllTokens()[normalizeUrl(authenticationUrl)];
24
- if (entry?.token && entry?.name && typeof entry.email === "string") return entry;
25
- return null;
26
- }
27
- function writeAuthTokenForUrl(authenticationUrl, token) {
28
- const tokens = readAllTokens();
29
- tokens[normalizeUrl(authenticationUrl)] = token;
30
- writeAllTokens(tokens);
31
- }
32
- async function verifyOneTimeToken(apiUrl, token) {
33
- const response = await fetch(`${normalizeUrl(apiUrl)}/api/auth/one-time-token/verify`, {
34
- method: "POST",
35
- headers: { "Content-Type": "application/json" },
36
- body: JSON.stringify({ token })
37
- });
38
- if (!response.ok) throw new Error(`OTT verification failed: ${response.status}`);
39
- const data = await response.json();
40
- const user = data.user;
41
- if (!user?.name) throw new Error("No user info in verification response");
42
- const sessionToken = data.session?.token;
43
- if (!sessionToken) throw new Error("No session token in verification response");
44
- return {
45
- name: user.name,
46
- email: user.email ?? "",
47
- token: sessionToken
48
- };
49
- }
50
-
51
- //#endregion
52
3
  export { readAuthTokenForUrl, verifyOneTimeToken, writeAuthTokenForUrl };
@@ -0,0 +1,16 @@
1
+ import { iconCollectionIds, loadIconCollection } from "@camox/api-contract";
2
+ import { mkdirSync, writeFileSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+
5
+ //#region src/features/vite/iconGeneration.ts
6
+ async function generateIconTypes(root, prefix) {
7
+ const ids = prefix ? iconCollectionIds(await loadIconCollection(prefix)) : [];
8
+ mkdirSync(resolve(root, "src/camox"), { recursive: true });
9
+ const union = ids.map((id) => JSON.stringify(id));
10
+ const type = union.length > 1 ? `\n | ${union.join("\n | ")}` : union[0] ?? "never";
11
+ writeFileSync(resolve(root, "src/camox/icons.gen.d.ts"), `// Generated by Camox. Do not edit.\nexport {};\ndeclare global {\n interface CamoxIconRegistry {\n ids:${union.length > 1 ? "" : " "}${type};\n }\n}\n`);
12
+ return ids;
13
+ }
14
+
15
+ //#endregion
16
+ export { generateIconTypes };
@@ -3,6 +3,9 @@ import { existsSync, readFileSync, readdirSync } from "node:fs";
3
3
  import { basename, resolve } from "node:path";
4
4
 
5
5
  //#region src/features/vite/layoutGeneration.ts
6
+ function propertyName(name) {
7
+ return /^[$_\p{ID_Start}][$\u200c\u200d\p{ID_Continue}]*$/u.test(name) ? name : JSON.stringify(name);
8
+ }
6
9
  /** Keep file identities stable across moves without executing application modules. */
7
10
  function generateLayoutFiles(appRoot) {
8
11
  const directory = resolve(appRoot, "src/layouts");
@@ -28,10 +31,11 @@ export const Layout = createLayout(${JSON.stringify(id)})({
28
31
  writeIfChanged(path, source);
29
32
  const segments = id.split(".");
30
33
  const route = "/" + segments.map((segment) => segment.startsWith("$") ? `:${segment.slice(1)}` : segment).join("/");
31
- const params = segments.filter((segment) => segment.startsWith("$")).map((segment) => `${JSON.stringify(segment.slice(1))}: string`).join("; ");
32
- declarations.push(`${JSON.stringify(id)}: { route: ${JSON.stringify(route)}; params: { ${params} } };`);
34
+ const params = segments.filter((segment) => segment.startsWith("$")).map((segment) => ` ${propertyName(segment.slice(1))}: string;`);
35
+ const paramsType = params.length ? `{\n${params.join("\n")}\n }` : "{}";
36
+ declarations.push(` ${propertyName(id)}: {\n route: ${JSON.stringify(route)};\n params: ${paramsType};\n };`);
33
37
  }
34
- writeIfChanged(resolve(appRoot, "src/camox/layouts.gen.d.ts"), `// Generated by Camox.\nimport "camox/createLayout";\ndeclare module "camox/createLayout" {\n interface LayoutFileRoutes {\n ${declarations.join("\n ")}\n }\n}\n`);
38
+ writeIfChanged(resolve(appRoot, "src/camox/layouts.gen.d.ts"), `// Generated by Camox.\nimport "camox/createLayout";\ndeclare module "camox/createLayout" {\n interface LayoutFileRoutes ${declarations.length ? `{\n${declarations.join("\n")}\n }` : "{}"}\n}\n`);
35
39
  }
36
40
 
37
41
  //#endregion
@@ -23,14 +23,16 @@ type CamoxVitePlugin = Plugin & {
23
23
  interface CamoxPluginOptions {
24
24
  /** Stable, human-readable slug identifying this project (e.g. "prestigious-impala-84") */
25
25
  projectSlug: string;
26
+ /** Iconify collection available to all icon fields. */
27
+ icons?: string;
26
28
  /** Disable telemetry collection (default: false) */
27
29
  disableTelemetry?: boolean;
28
30
  /** Internal options (intended for Camox contributors in development, not for public use) */
29
31
  _internal?: {
30
32
  /** URL of the Camox API backend, used for data fetching */apiUrl?: string; /** URL of the Camox authentication backend (default: https://app.camox.dev) */
31
33
  authenticationUrl?: string; /** Show Tanstack query devtools (default: false) */
32
- enableTanstackDevtools?: boolean; /** Enable the frontend-only comments prototype (default: false). */
33
- enableComments?: boolean; /** Disable automatic code generation (route files and app file) (default: false) */
34
+ enableTanstackDevtools?: boolean; /** Enable experimental features, including feedback (default: false). */
35
+ enableExperimentalFeatures?: boolean; /** Disable automatic code generation (route files and app file) (default: false) */
34
36
  disableCodeGen?: boolean; /** Mount the Camox-owned dev runtime at this base path. Use "/" for root mode. */
35
37
  runtimeBasePath?: string;
36
38
  };
@@ -2,6 +2,7 @@ import { generateAppFile, watchAppFile } from "./appGeneration.js";
2
2
  import { readAuthTokenForUrl } from "./auth.js";
3
3
  import { watchNewBlockFiles } from "./blockBoilerplate.js";
4
4
  import { installDevAuthenticationMiddleware } from "./devAuthentication.js";
5
+ import { generateIconTypes } from "./iconGeneration.js";
5
6
  import { syncDefinitions, syncDefinitionsToApi } from "./definitionsSync.js";
6
7
  import { cleanupGeneratedRouteFiles } from "./routeGeneration.js";
7
8
  import { installRuntimeNitroRoutes, loadRuntimeDevModule, resolveRuntimeDevId } from "./runtimeDev.js";
@@ -82,7 +83,11 @@ function camox(options) {
82
83
  nitro: { setup(nitro) {
83
84
  if (nitro.options.noExternals !== true) {
84
85
  const noExternals = Array.isArray(nitro.options.noExternals) ? nitro.options.noExternals : [];
85
- nitro.options.noExternals = [...noExternals, "camox"];
86
+ nitro.options.noExternals = [
87
+ ...noExternals,
88
+ "camox",
89
+ "tslib"
90
+ ];
86
91
  }
87
92
  installRuntimeNitroRoutes(nitro, { runtimeBasePath });
88
93
  nitro.hooks.hook("compiled", ({ options }) => {
@@ -91,7 +96,8 @@ function camox(options) {
91
96
  cpSync(serverAssets, join(options.output.publicDir, "assets"), { recursive: true });
92
97
  });
93
98
  } },
94
- config(_config, env) {
99
+ async config(_config, env) {
100
+ const iconIds = await generateIconTypes(resolve(_config.root ?? process.cwd()), options.icons);
95
101
  isBuild = env.command === "build";
96
102
  isRelease = isBuild && process.env.CAMOX_INTERNAL_RELEASE === "1";
97
103
  if (isRelease) environmentName = "production";
@@ -103,9 +109,10 @@ function camox(options) {
103
109
  return {
104
110
  build: { emitAssets: true },
105
111
  define: {
112
+ __CAMOX_ICON_IDS__: JSON.stringify(iconIds),
106
113
  __CAMOX_TELEMETRY_DISABLED__: JSON.stringify(!!options.disableTelemetry),
107
114
  __ENABLE_TANSTACK_DEVTOOLS__: JSON.stringify(enableTanstackDevtools),
108
- __CAMOX_ENABLE_COMMENTS__: JSON.stringify(options._internal?.enableComments ?? false),
115
+ __CAMOX_ENABLE_EXPERIMENTAL_FEATURES__: JSON.stringify(options._internal?.enableExperimentalFeatures ?? false),
109
116
  __CAMOX_ENVIRONMENT_NAME__: JSON.stringify(environmentName),
110
117
  __CAMOX_API_URL__: JSON.stringify(apiUrl),
111
118
  __CAMOX_AUTHENTICATION_URL__: JSON.stringify(authenticationUrl),
@@ -135,6 +142,8 @@ function camox(options) {
135
142
  "camox > @base-ui/react/accordion",
136
143
  "camox > @base-ui/react/alert-dialog",
137
144
  "camox > @base-ui/react/avatar",
145
+ "camox > @base-ui/react/button",
146
+ "camox > @base-ui/react/combobox",
138
147
  "camox > @base-ui/react/dialog",
139
148
  "camox > @base-ui/react/input",
140
149
  "camox > @base-ui/react/menu",
@@ -149,9 +158,12 @@ function camox(options) {
149
158
  "camox > @base-ui/react/use-render",
150
159
  "camox > @camox/api-contract",
151
160
  "camox > @camox/api-contract/query-keys",
161
+ "camox > @camox/ui > class-variance-authority",
162
+ "camox > @camox/ui > clsx",
152
163
  "camox > @camox/ui > cmdk",
153
164
  "camox > @camox/ui > lucide-react",
154
165
  "camox > @camox/ui > sonner",
166
+ "camox > @camox/ui > tailwind-merge",
155
167
  "camox > @dnd-kit/core",
156
168
  "camox > @dnd-kit/modifiers",
157
169
  "camox > @dnd-kit/sortable",
@@ -175,7 +187,14 @@ function camox(options) {
175
187
  "camox > @sinclair/typebox",
176
188
  "camox > @takumi-rs/image-response",
177
189
  "camox > @tanstack/react-form",
190
+ "camox > @tanstack/react-query",
191
+ "camox > @tanstack/react-router",
192
+ "camox > @tanstack/react-virtual",
193
+ "camox > @unhead/react/client",
194
+ "camox > @unhead/react/server",
178
195
  "camox > @xstate/store-react",
196
+ "camox > better-auth/client/plugins",
197
+ "camox > better-auth/react",
179
198
  "camox > better-auth > @better-auth/core/env",
180
199
  "camox > better-auth > @better-auth/core/error",
181
200
  "camox > better-auth > @better-auth/core/utils/error-codes",
@@ -186,6 +205,7 @@ function camox(options) {
186
205
  "camox > fractional-indexing",
187
206
  "camox > lexical",
188
207
  "camox > lucide-react",
208
+ "camox > marked",
189
209
  "camox > @tanstack/react-query-devtools/production",
190
210
  "camox > partysocket/react"
191
211
  ] }
@@ -242,6 +262,7 @@ function camox(options) {
242
262
  root: resolvedConfig.root,
243
263
  cacheDir: resolve(resolvedConfig.root, "node_modules", ".vite-camox-temp"),
244
264
  resolve: resolvedConfig.resolve,
265
+ define: resolvedConfig.define,
245
266
  server: { middlewareMode: true },
246
267
  logLevel: "silent"
247
268
  });
@@ -0,0 +1,53 @@
1
+ import { useProjectSlug } from "../lib/auth.js";
2
+ import { getPageDestinations } from "../core/pageDestinations.js";
3
+ import { useCamoxApp } from "../features/provider/components/CamoxAppContext.js";
4
+ import { pageQueries, projectQueries } from "../lib/queries.js";
5
+ import { c } from "react/compiler-runtime";
6
+ import { useQuery } from "@tanstack/react-query";
7
+
8
+ //#region src/hooks/use-page-destinations.ts
9
+ function usePageDestinations() {
10
+ const $ = c(11);
11
+ if ($[0] !== "5104e69847ee48202a1abdefc32d4e478db3a82331029421687a5ec8f7a46eaa") {
12
+ for (let $i = 0; $i < 11; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
13
+ $[0] = "5104e69847ee48202a1abdefc32d4e478db3a82331029421687a5ec8f7a46eaa";
14
+ }
15
+ const app = useCamoxApp();
16
+ const projectSlug = useProjectSlug();
17
+ let t0;
18
+ if ($[1] !== projectSlug) {
19
+ t0 = projectQueries.getBySlug(projectSlug);
20
+ $[1] = projectSlug;
21
+ $[2] = t0;
22
+ } else t0 = $[2];
23
+ const { data: project } = useQuery(t0);
24
+ let t1;
25
+ if ($[3] !== project?.id) {
26
+ t1 = pageQueries.list(project?.id ?? 0);
27
+ $[3] = project?.id;
28
+ $[4] = t1;
29
+ } else t1 = $[4];
30
+ const t2 = !!project;
31
+ let t3;
32
+ if ($[5] !== t1 || $[6] !== t2) {
33
+ t3 = {
34
+ ...t1,
35
+ enabled: t2
36
+ };
37
+ $[5] = t1;
38
+ $[6] = t2;
39
+ $[7] = t3;
40
+ } else t3 = $[7];
41
+ const { data: pages } = useQuery(t3);
42
+ let t4;
43
+ if ($[8] !== app || $[9] !== pages) {
44
+ t4 = getPageDestinations(pages ?? [], app.getLayouts());
45
+ $[8] = app;
46
+ $[9] = pages;
47
+ $[10] = t4;
48
+ } else t4 = $[10];
49
+ return t4;
50
+ }
51
+
52
+ //#endregion
53
+ export { usePageDestinations };
package/dist/lib/auth.js CHANGED
@@ -1,14 +1,12 @@
1
1
  import { actionsStore } from "../features/provider/actionsStore.js";
2
+ import { validatePreviewTarget } from "./preview-handoff.js";
3
+ import { SERVER_AUTH_COOKIE_NAME, buildClearServerAuthCookieHeader, getServerAuthCookieHeader } from "./server-auth-cookie.js";
2
4
  import { c } from "react/compiler-runtime";
3
5
  import * as React from "react";
4
6
  import { oneTimeTokenClient, organizationClient } from "better-auth/client/plugins";
5
7
  import { createAuthClient } from "better-auth/react";
6
8
 
7
9
  //#region src/lib/auth.ts
8
- const SERVER_AUTH_COOKIE_NAME = "camox_auth_cookie";
9
- function buildClearServerAuthCookieHeader() {
10
- return `${SERVER_AUTH_COOKIE_NAME}=; Path=/; SameSite=Lax; Max-Age=0`;
11
- }
12
10
  function writeServerAuthCookie(cookie) {
13
11
  if (typeof document === "undefined") return;
14
12
  const secure = window.location.protocol === "https:" ? "; Secure" : "";
@@ -184,21 +182,32 @@ function createCamoxAuthClient(apiUrl) {
184
182
  * Process a `?ott=` one-time token from the URL before the provider mounts.
185
183
  * Verifies the token against the API backend and notifies the session store.
186
184
  *
187
- * Returns `true` once processing is complete (or if there was no OTT).
185
+ * Reports completion and explicit handoff failures instead of silently showing live content.
188
186
  */
189
- function useProcessOtt(authClient) {
187
+ function useProcessOtt(authClient, target) {
188
+ const [error, setError] = React.useState(null);
189
+ const processing = React.useRef(false);
190
190
  const [ready, setReady] = React.useState(() => {
191
191
  if (typeof window === "undefined") return true;
192
192
  return !new URL(window.location.href).searchParams.has("ott");
193
193
  });
194
194
  React.useEffect(() => {
195
- if (ready) return;
196
195
  const url = new URL(window.location.href);
196
+ const targetError = validatePreviewTarget(url.searchParams.get("camox-preview"), target);
197
+ if (targetError) {
198
+ url.searchParams.delete("ott");
199
+ window.history.replaceState({}, "", url);
200
+ setError(targetError);
201
+ setReady(true);
202
+ return;
203
+ }
204
+ if (ready || processing.current) return;
197
205
  const ott = url.searchParams.get("ott");
198
206
  if (!ott) {
199
207
  setReady(true);
200
208
  return;
201
209
  }
210
+ processing.current = true;
202
211
  url.searchParams.delete("ott");
203
212
  window.history.replaceState({}, "", url);
204
213
  (async () => {
@@ -208,18 +217,29 @@ function useProcessOtt(authClient) {
208
217
  if (!verification.data) throw new Error("OTT verification returned no session");
209
218
  window.location.reload();
210
219
  return;
211
- } catch {}
220
+ } catch {
221
+ setError("Preview sign-in failed. The link may have expired or already been used. Run camox preview again for a fresh link.");
222
+ }
212
223
  setReady(true);
213
224
  })();
214
- }, [authClient, ready]);
215
- return ready;
225
+ }, [
226
+ authClient,
227
+ ready,
228
+ target.projectSlug,
229
+ target.environmentName,
230
+ target.apiUrl
231
+ ]);
232
+ return {
233
+ ready,
234
+ error
235
+ };
216
236
  }
217
237
  const AuthContext = React.createContext(null);
218
238
  function useAuthContext() {
219
239
  const $ = c(1);
220
- if ($[0] !== "613abd4be0f81de0bf8df48c671a83e40c3e6ac86dab4c38ebcffe92268b614b") {
240
+ if ($[0] !== "2870dba5d074cbe2d9a238f9ae39bb51493bf31c0dd30caa79404003309e1525") {
221
241
  for (let $i = 0; $i < 1; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
222
- $[0] = "613abd4be0f81de0bf8df48c671a83e40c3e6ac86dab4c38ebcffe92268b614b";
242
+ $[0] = "2870dba5d074cbe2d9a238f9ae39bb51493bf31c0dd30caa79404003309e1525";
223
243
  }
224
244
  const ctx = React.useContext(AuthContext);
225
245
  if (!ctx) throw new Error("Missing CamoxProvider");
@@ -227,9 +247,9 @@ function useAuthContext() {
227
247
  }
228
248
  function useProjectSlug() {
229
249
  const $ = c(1);
230
- if ($[0] !== "613abd4be0f81de0bf8df48c671a83e40c3e6ac86dab4c38ebcffe92268b614b") {
250
+ if ($[0] !== "2870dba5d074cbe2d9a238f9ae39bb51493bf31c0dd30caa79404003309e1525") {
231
251
  for (let $i = 0; $i < 1; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
232
- $[0] = "613abd4be0f81de0bf8df48c671a83e40c3e6ac86dab4c38ebcffe92268b614b";
252
+ $[0] = "2870dba5d074cbe2d9a238f9ae39bb51493bf31c0dd30caa79404003309e1525";
233
253
  }
234
254
  return useAuthContext().projectSlug;
235
255
  }
@@ -245,18 +265,18 @@ function useAuthState() {
245
265
  }
246
266
  function useIsAuthenticated() {
247
267
  const $ = c(1);
248
- if ($[0] !== "613abd4be0f81de0bf8df48c671a83e40c3e6ac86dab4c38ebcffe92268b614b") {
268
+ if ($[0] !== "2870dba5d074cbe2d9a238f9ae39bb51493bf31c0dd30caa79404003309e1525") {
249
269
  for (let $i = 0; $i < 1; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
250
- $[0] = "613abd4be0f81de0bf8df48c671a83e40c3e6ac86dab4c38ebcffe92268b614b";
270
+ $[0] = "2870dba5d074cbe2d9a238f9ae39bb51493bf31c0dd30caa79404003309e1525";
251
271
  }
252
272
  const { isAuthenticated } = useAuthState();
253
273
  return isAuthenticated;
254
274
  }
255
275
  function useSignInRedirect(callbackHref) {
256
276
  const $ = c(4);
257
- if ($[0] !== "613abd4be0f81de0bf8df48c671a83e40c3e6ac86dab4c38ebcffe92268b614b") {
277
+ if ($[0] !== "2870dba5d074cbe2d9a238f9ae39bb51493bf31c0dd30caa79404003309e1525") {
258
278
  for (let $i = 0; $i < 4; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
259
- $[0] = "613abd4be0f81de0bf8df48c671a83e40c3e6ac86dab4c38ebcffe92268b614b";
279
+ $[0] = "2870dba5d074cbe2d9a238f9ae39bb51493bf31c0dd30caa79404003309e1525";
260
280
  }
261
281
  const { authenticationUrl } = useAuthContext();
262
282
  let t0;
@@ -278,9 +298,9 @@ function useSignInRedirect(callbackHref) {
278
298
  */
279
299
  function useAuthActions() {
280
300
  const $ = c(5);
281
- if ($[0] !== "613abd4be0f81de0bf8df48c671a83e40c3e6ac86dab4c38ebcffe92268b614b") {
301
+ if ($[0] !== "2870dba5d074cbe2d9a238f9ae39bb51493bf31c0dd30caa79404003309e1525") {
282
302
  for (let $i = 0; $i < 5; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
283
- $[0] = "613abd4be0f81de0bf8df48c671a83e40c3e6ac86dab4c38ebcffe92268b614b";
303
+ $[0] = "2870dba5d074cbe2d9a238f9ae39bb51493bf31c0dd30caa79404003309e1525";
284
304
  }
285
305
  const { authClient, authenticationUrl } = useAuthContext();
286
306
  let t0;
@@ -0,0 +1,13 @@
1
+ //#region src/lib/preview-handoff.ts
2
+ /** Validate the destination before exchanging any credential with its backend. */
3
+ function validatePreviewTarget(raw, target) {
4
+ if (raw === null) return null;
5
+ try {
6
+ const expected = JSON.parse(raw);
7
+ if (expected.projectSlug === target.projectSlug && expected.environmentName === target.environmentName && typeof expected.apiUrl === "string" && expected.apiUrl.replace(/\/+$/, "") === target.apiUrl.replace(/\/+$/, "")) return null;
8
+ } catch {}
9
+ return "Preview destination does not match the CLI project, backend, or environment. Check --cwd and --url, then run camox preview again.";
10
+ }
11
+
12
+ //#endregion
13
+ export { validatePreviewTarget };
@@ -3,6 +3,17 @@ import { getApiUrl, getEnvironmentName, getOrpc } from "./api-client.js";
3
3
  import { queryKeys } from "@camox/api-contract/query-keys";
4
4
 
5
5
  //#region src/lib/queries.ts
6
+ const commentQueries = { list: (pageId) => ({
7
+ ...getOrpc().comments.list.queryOptions({
8
+ input: { pageId },
9
+ staleTime: 3e4
10
+ }),
11
+ queryKey: [...queryKeys.comments.list(pageId), getEnvironmentName() ?? "production"]
12
+ }) };
13
+ const commentMutations = {
14
+ create: () => getOrpc().comments.create.mutationOptions(),
15
+ setResolved: () => getOrpc().comments.setResolved.mutationOptions()
16
+ };
6
17
  const pageQueries = {
7
18
  list: (projectId) => ({
8
19
  ...getOrpc().pages.list.queryOptions({
@@ -183,4 +194,4 @@ const fileMutations = {
183
194
  };
184
195
 
185
196
  //#endregion
186
- export { blockMutations, blockQueries, environmentMutations, environmentQueries, fileMutations, fileQueries, layoutQueries, pageMutations, pageQueries, projectQueries, repeatableItemMutations };
197
+ export { blockMutations, blockQueries, commentMutations, commentQueries, environmentMutations, environmentQueries, fileMutations, fileQueries, layoutQueries, pageMutations, pageQueries, projectQueries, repeatableItemMutations };
@@ -0,0 +1,21 @@
1
+ //#region src/lib/server-auth-cookie.ts
2
+ const SERVER_AUTH_COOKIE_NAME = "camox_auth_cookie";
3
+ function buildClearServerAuthCookieHeader() {
4
+ return `${SERVER_AUTH_COOKIE_NAME}=; Path=/; SameSite=Lax; Max-Age=0`;
5
+ }
6
+ /** Forward credentials to the API for validation; cookie presence is not authentication.
7
+ * Cross-domain sessions use the mirror. Same-host sessions (including localhost
8
+ * on different ports) may only have Better Auth's native, HttpOnly cookie.
9
+ */
10
+ function getServerAuthCookieHeader(headers) {
11
+ const cookies = (headers.get("Cookie") ?? "").split(";").map((part) => part.trim());
12
+ const mirror = cookies.find((part) => part.startsWith(`${SERVER_AUTH_COOKIE_NAME}=`));
13
+ if (mirror) try {
14
+ const value = decodeURIComponent(mirror.slice(18));
15
+ if (value) return value;
16
+ } catch {}
17
+ return cookies.filter((part) => part.startsWith("better-auth.session_token=") || part.startsWith("__Secure-better-auth.session_token=")).join("; ");
18
+ }
19
+
20
+ //#endregion
21
+ export { SERVER_AUTH_COOKIE_NAME, buildClearServerAuthCookieHeader, getServerAuthCookieHeader };