forgecad 0.6.3 → 0.7.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 (193) hide show
  1. package/README.md +2 -11
  2. package/dist/assets/{AdminPage-CeqCUUgu.js → AdminPage-DAu1C1ST.js} +250 -151
  3. package/dist/assets/{BlogPage-P_AJP0v9.js → BlogPage-CJEXL_zJ.js} +94 -70
  4. package/dist/assets/{DocsPage-CKRV2iq2.js → DocsPage-Gc_BCdqC.js} +269 -143
  5. package/dist/assets/EditorApp-D9bJvtf7.js +11338 -0
  6. package/dist/assets/{EditorApp-CnC2k4cW.css → EditorApp-DG1-oUSV.css} +459 -87
  7. package/dist/assets/{EmbedViewer-DBlzmQ5i.js → EmbedViewer-CEO8XbV8.js} +2 -4
  8. package/dist/assets/LandingPage-CdCuEOdC.js +451 -0
  9. package/dist/assets/PricingPage-BSrxu6d7.js +232 -0
  10. package/dist/assets/{SettingsPage-BqCh9JcC.js → SettingsPage-FUCSIRq6.js} +129 -5
  11. package/dist/assets/{evalWorker-Ql-aKwLA.js → evalWorker-KoR0SNKq.js} +6770 -2914
  12. package/dist/assets/{index-2hfs_ub0.css → index-CyVd1D4D.css} +227 -53
  13. package/dist/assets/{Viewport-CoB46f5R.js → index-wTEK39at.js} +31385 -6439
  14. package/dist/assets/{javascript-DCxGoE5Y.js → javascript-DAl8Gmyo.js} +1 -1
  15. package/dist/assets/{manifold-CqNMHHKO.js → manifold-B1sGWdYk.js} +4 -3
  16. package/dist/assets/{manifold-Cce9wRFz.js → manifold-D7o0N50J.js} +1 -1
  17. package/dist/assets/{manifold-D6BeHIOo.js → manifold-G5sBaXzi.js} +1 -1
  18. package/dist/assets/{reportWorker-sFEFonXf.js → reportWorker-DYcRHhv9.js} +6798 -3341
  19. package/dist/assets/{vendor-react-Dt7-aaJH.js → vendor-react-CG3i_wp0.js} +65 -8
  20. package/dist/docs-raw/generated/assembly.md +691 -112
  21. package/dist/docs-raw/generated/concepts.md +1225 -1400
  22. package/dist/docs-raw/generated/core.md +464 -1412
  23. package/dist/docs-raw/generated/curves.md +593 -117
  24. package/dist/docs-raw/generated/lib.md +38 -748
  25. package/dist/docs-raw/generated/output.md +139 -245
  26. package/dist/docs-raw/generated/sheet-metal.md +473 -21
  27. package/dist/docs-raw/generated/sketch.md +553 -349
  28. package/dist/docs-raw/generated/viewport.md +345 -303
  29. package/dist/docs-raw/generated/wood.md +104 -0
  30. package/dist/index.html +2 -2
  31. package/dist/sitemap.xml +6 -6
  32. package/dist-cli/chunk-PZ5AY32C.js +10 -0
  33. package/dist-cli/chunk-PZ5AY32C.js.map +1 -0
  34. package/dist-cli/forgecad.js +9435 -5407
  35. package/dist-cli/forgecad.js.map +1 -0
  36. package/dist-cli/solver-FV7TJZGI.js +365 -0
  37. package/dist-cli/solver-FV7TJZGI.js.map +1 -0
  38. package/dist-skill/CONTEXT.md +3186 -7145
  39. package/dist-skill/SKILL-dev.md +21 -63
  40. package/dist-skill/SKILL.md +12 -56
  41. package/dist-skill/docs/API/core/concepts.md +16 -98
  42. package/dist-skill/docs/CLI/export.md +91 -0
  43. package/dist-skill/docs/CLI/projects.md +107 -0
  44. package/dist-skill/docs/CLI/studio_publishing.md +52 -0
  45. package/dist-skill/docs/CLI/validation.md +66 -0
  46. package/dist-skill/docs/generated/assembly.md +691 -112
  47. package/dist-skill/docs/generated/core.md +464 -1412
  48. package/dist-skill/docs/generated/curves.md +593 -117
  49. package/dist-skill/docs/generated/lib.md +38 -748
  50. package/dist-skill/docs/generated/output.md +139 -245
  51. package/dist-skill/docs/generated/sheet-metal.md +473 -21
  52. package/dist-skill/docs/generated/sketch.md +553 -349
  53. package/dist-skill/docs/generated/viewport.md +345 -303
  54. package/dist-skill/docs/generated/wood.md +104 -0
  55. package/dist-skill/docs/guides/coordinate-system.md +11 -17
  56. package/dist-skill/docs/guides/geometry-conventions.md +13 -70
  57. package/dist-skill/docs/guides/modeling-recipes.md +22 -195
  58. package/dist-skill/docs/guides/positioning.md +88 -147
  59. package/dist-skill/docs-dev/API/core/concepts.md +51 -0
  60. package/dist-skill/docs-dev/API/core/sdf-advanced.md +92 -0
  61. package/dist-skill/docs-dev/API/core/sdf-primitives.md +58 -0
  62. package/dist-skill/docs-dev/API/core/sdf-workflow.md +42 -0
  63. package/dist-skill/docs-dev/CLI/export.md +91 -0
  64. package/dist-skill/docs-dev/CLI/projects.md +107 -0
  65. package/dist-skill/docs-dev/CLI/studio_publishing.md +52 -0
  66. package/dist-skill/docs-dev/CLI/validation.md +66 -0
  67. package/dist-skill/{docs → docs-dev}/blueprint-first.md +5 -0
  68. package/dist-skill/{docs → docs-dev}/coding-best-practices.md +6 -8
  69. package/dist-skill/{docs → docs-dev}/coding.md +1 -3
  70. package/dist-skill/docs-dev/generated/assembly.md +771 -0
  71. package/dist-skill/docs-dev/generated/core.md +775 -0
  72. package/dist-skill/docs-dev/generated/curves.md +688 -0
  73. package/dist-skill/docs-dev/generated/lib.md +50 -0
  74. package/dist-skill/docs-dev/generated/output.md +234 -0
  75. package/dist-skill/docs-dev/generated/sheet-metal.md +506 -0
  76. package/dist-skill/docs-dev/generated/sketch.md +801 -0
  77. package/dist-skill/docs-dev/generated/viewport.md +486 -0
  78. package/dist-skill/docs-dev/generated/wood.md +104 -0
  79. package/dist-skill/docs-dev/guides/coordinate-system.md +46 -0
  80. package/dist-skill/docs-dev/guides/geometry-conventions.md +52 -0
  81. package/dist-skill/docs-dev/guides/modeling-recipes.md +77 -0
  82. package/dist-skill/docs-dev/guides/positioning.md +151 -0
  83. package/dist-skill/{docs → docs-dev}/guides/skill-maintenance.md +21 -10
  84. package/dist-skill/{docs → docs-dev}/internals/compiler.md +5 -6
  85. package/dist-skill/{docs → docs-dev}/internals/constraint-solver-quality.md +0 -1
  86. package/dist-skill/{docs → docs-dev}/internals/constraint-solver.md +0 -1
  87. package/dist-skill/{docs → docs-dev}/internals/sketch-2d-pipeline.md +2 -3
  88. package/examples/api/attachTo-basics.forge.js +5 -5
  89. package/examples/api/boolean-operations.forge.js +3 -3
  90. package/examples/api/bounding-box-visualizer.forge.js +2 -2
  91. package/examples/api/clone-duplicate.forge.js +1 -1
  92. package/examples/api/colors-union-vs-array.forge.js +6 -6
  93. package/examples/api/connector-assembly.forge.js +4 -4
  94. package/examples/api/connector-basics.forge.js +2 -2
  95. package/examples/api/extrude-options.forge.js +4 -10
  96. package/examples/api/feature-created-faces.forge.js +6 -10
  97. package/examples/api/fillet-showcase.forge.js +1 -1
  98. package/examples/api/folded-service-panel-cover.forge.js +2 -2
  99. package/examples/api/group-test.forge.js +1 -1
  100. package/examples/api/group-vs-union.forge.js +1 -1
  101. package/examples/api/highlight-debug.forge.js +4 -0
  102. package/examples/api/js-module-pillars.js +1 -1
  103. package/examples/api/js-module-scene.js +2 -2
  104. package/examples/api/mesh-import-slats.forge.js +1 -1
  105. package/examples/api/pointAlong-orientation.forge.js +1 -1
  106. package/examples/api/profile-2020-b-slot6.forge.js +0 -1
  107. package/examples/api/route-perimeter-flange.forge.js +1 -1
  108. package/examples/api/sdf-rover-demo.forge.js +10 -10
  109. package/examples/api/sketch-on-face-demo.forge.js +2 -2
  110. package/examples/api/sketch-regions.forge.js +4 -4
  111. package/examples/api/transition-curves.forge.js +1 -1
  112. package/examples/api/variable-sweep-pure-sdf-test.forge.js +162 -0
  113. package/examples/api/variable-sweep-test.forge.js +2 -2
  114. package/examples/api/wood-joinery.forge.js +60 -0
  115. package/examples/compiler-corpus/enclosure-shell-cuts.forge.js +3 -3
  116. package/examples/compiler-corpus/fastener-plate-variants.forge.js +2 -2
  117. package/examples/experiments/drone-arm.forge.js +53 -0
  118. package/examples/furniture/adjustable-table.forge.js +2 -2
  119. package/examples/furniture/bathroom.forge.js +11 -11
  120. package/examples/furniture/chair.forge.js +1 -1
  121. package/examples/generative/crystal-growth.forge.js +2 -2
  122. package/examples/generative/frost-spires.forge.js +3 -3
  123. package/examples/generative/golden-spiral-tower.forge.js +3 -3
  124. package/examples/mechanical/3d-printer.forge.js +28 -28
  125. package/examples/mechanical/5-finger-robot-hand.forge.js +15 -15
  126. package/examples/mechanical/airplane-propeller.forge.js +2 -2
  127. package/examples/mechanical/fillet-enclosure.forge.js +1 -1
  128. package/examples/mechanical/headphone-hanger-v2.forge.js +2 -2
  129. package/examples/mechanical/robot_hand.forge.js +15 -15
  130. package/examples/mechanical/robot_hand_2.forge.js +9 -9
  131. package/examples/products/bottle.forge.js +1 -1
  132. package/examples/products/chess-set.forge.js +19 -19
  133. package/examples/products/classical-piano.forge.js +11 -11
  134. package/examples/products/clock.forge.js +12 -12
  135. package/examples/products/iphone.forge.js +8 -8
  136. package/examples/products/laptop.forge.js +15 -15
  137. package/examples/products/liquid-soap-dispenser.forge.js +18 -18
  138. package/examples/products/origami-fish.forge.js +8 -6
  139. package/examples/products/spiderman-cake.forge.js +4 -4
  140. package/examples/toolbox/bolted-joint.forge.js +2 -2
  141. package/package.json +7 -4
  142. package/dist/assets/EditorApp-B-vQvgam.js +0 -9888
  143. package/dist/assets/LandingPage-C5n9hDXI.js +0 -322
  144. package/dist/assets/PublishedModelPage-Dt7PCVBj.js +0 -146
  145. package/dist/assets/__vite-browser-external-CURh0WXD.js +0 -8
  146. package/dist/assets/deserializeRunResult-BLAFoiE0.js +0 -19365
  147. package/dist/assets/index-1CYp3zUp.js +0 -1455
  148. package/dist/docs-raw/CLI.md +0 -865
  149. package/dist-skill/docs/API/API.md +0 -1666
  150. package/dist-skill/docs/API/README.md +0 -37
  151. package/dist-skill/docs/API/assembly/assembly.md +0 -617
  152. package/dist-skill/docs/API/core/edge-queries.md +0 -130
  153. package/dist-skill/docs/API/core/parameters.md +0 -122
  154. package/dist-skill/docs/API/core/reserved-terms.md +0 -137
  155. package/dist-skill/docs/API/core/sdf.md +0 -326
  156. package/dist-skill/docs/API/core/skill-cli.md +0 -194
  157. package/dist-skill/docs/API/core/skill-guide.md +0 -205
  158. package/dist-skill/docs/API/core/specs.md +0 -186
  159. package/dist-skill/docs/API/core/topology.md +0 -372
  160. package/dist-skill/docs/API/entities.md +0 -268
  161. package/dist-skill/docs/API/output/bom.md +0 -58
  162. package/dist-skill/docs/API/output/brep-export.md +0 -87
  163. package/dist-skill/docs/API/output/dimensions.md +0 -67
  164. package/dist-skill/docs/API/output/export.md +0 -110
  165. package/dist-skill/docs/API/output/gcode.md +0 -195
  166. package/dist-skill/docs/API/runtime/viewport.md +0 -420
  167. package/dist-skill/docs/API/sheet-metal/sheet-metal.md +0 -185
  168. package/dist-skill/docs/API/sketch/anchor.md +0 -37
  169. package/dist-skill/docs/API/sketch/booleans.md +0 -91
  170. package/dist-skill/docs/API/sketch/core.md +0 -73
  171. package/dist-skill/docs/API/sketch/extrude.md +0 -62
  172. package/dist-skill/docs/API/sketch/on-face.md +0 -104
  173. package/dist-skill/docs/API/sketch/operations.md +0 -78
  174. package/dist-skill/docs/API/sketch/path.md +0 -75
  175. package/dist-skill/docs/API/sketch/primitives.md +0 -146
  176. package/dist-skill/docs/API/sketch/regions.md +0 -80
  177. package/dist-skill/docs/API/sketch/text.md +0 -108
  178. package/dist-skill/docs/API/sketch/transforms.md +0 -65
  179. package/dist-skill/docs/API/toolbox/fasteners.md +0 -129
  180. package/dist-skill/docs/CLI.md +0 -865
  181. package/dist-skill/docs/INDEX.md +0 -94
  182. package/dist-skill/docs/RELEASING.md +0 -55
  183. package/dist-skill/docs/cli-monetization.md +0 -111
  184. package/dist-skill/docs/deployment.md +0 -281
  185. package/dist-skill/docs/generated/concepts.md +0 -2112
  186. package/dist-skill/docs/internals/shape-from-slices.md +0 -152
  187. package/dist-skill/docs/platform/admin.md +0 -45
  188. package/dist-skill/docs/platform/architecture.md +0 -79
  189. package/dist-skill/docs/platform/auth.md +0 -110
  190. package/dist-skill/docs/platform/email.md +0 -67
  191. package/dist-skill/docs/platform/projects.md +0 -111
  192. package/dist-skill/docs/platform/sharing.md +0 -90
  193. package/dist-skill/docs/runbook.md +0 -345
package/README.md CHANGED
@@ -107,14 +107,12 @@ When an AI model is asked to generate ForgeCAD models, require this workflow:
107
107
  1. Read `docs/permanent/API/model-building/README.md` first.
108
108
  2. Read every file listed there.
109
109
  3. Read the relevant files in `examples/api/` next.
110
- 4. If the task is exploratory, unfamiliar, or likely to need debugging, start in a `.forge-notebook.json` and iterate there first.
111
- 5. Only then stabilize the result as `.forge.js`, or keep using the notebook when iteration is still active.
112
- 6. Read `docs/permanent/API/runtime/` or `docs/permanent/API/output/` only if the task explicitly needs viewport behavior, reporting, or export.
110
+ 4. Read `docs/permanent/API/runtime/` or `docs/permanent/API/output/` only if the task explicitly needs viewport behavior, reporting, or export.
113
111
 
114
112
  Use this instruction in prompts to avoid missing API capabilities or producing invalid model code:
115
113
 
116
114
  ```text
117
- Before generating any ForgeCAD model code, read docs/permanent/API/model-building/README.md, then every file it lists, then the relevant files in examples/api/. If the task is exploratory, unfamiliar, or likely to need debugging, start in a .forge-notebook.json and iterate there first. Only read docs/permanent/API/runtime/ or docs/permanent/API/output/ if the task explicitly needs those areas. Then generate a runnable model using only documented ForgeCAD APIs and patterns from those files.
115
+ Before generating any ForgeCAD model code, read docs/permanent/API/model-building/README.md, then every file it lists, then the relevant files in examples/api/. Only read docs/permanent/API/runtime/ or docs/permanent/API/output/ if the task explicitly needs those areas. Then generate a runnable model using only documented ForgeCAD APIs and patterns from those files.
118
116
  ```
119
117
 
120
118
  Example AI workflows:
@@ -266,12 +264,8 @@ All CLI tools use the same runtime as the browser (`src/forge/headless.ts`), so
266
264
  | Task | Command |
267
265
  | --- | --- |
268
266
  | Validate a script | `forgecad run examples/cup.forge.js` |
269
- | Validate a notebook preview | `forgecad run examples/api/notebook-iteration.forge-notebook.json` |
270
- | Inspect notebook cells in the terminal | `forgecad notebook view examples/api/notebook-iteration.forge-notebook.json preview` |
271
267
  | Render PNG views | `forgecad render examples/cup.forge.js` |
272
- | Render a notebook preview | `forgecad render examples/api/notebook-iteration.forge-notebook.json` |
273
268
  | Render orbit GIF (solid + wireframe) | `forgecad capture gif examples/cup.forge.js` |
274
- | List notebook capture options | `forgecad capture gif examples/api/notebook-assembly-debug.forge-notebook.json --list` |
275
269
  | Export sketch SVG | `forgecad export svg examples/constraints/01-fully-constrained-rect.forge.js` |
276
270
  | Export exact STEP (supported subset only) | `forgecad export step examples/api/brep-exportable.forge.js` |
277
271
  | Export exact BREP (supported subset only) | `forgecad export brep examples/api/brep-exportable.forge.js` |
@@ -286,7 +280,6 @@ All CLI tools use the same runtime as the browser (`src/forge/headless.ts`), so
286
280
  ### CLI details
287
281
 
288
282
  - `render` outputs multi-angle PNGs (`front`, `side`, `top`, `iso`) by default.
289
- - For `forgecad run`, `forgecad render`, `forgecad capture gif`, and `forgecad capture mp4`, passing a `.forge-notebook.json` uses that notebook's preview cell.
290
283
  - `capture gif` outputs a single orbit animation with a full solid pass, then full wireframe pass.
291
284
  - `export svg` runs fully in Node (no browser/Puppeteer).
292
285
  - `export report` generates searchable-text PDF pages (overview, unique components, BOM, dimensions).
@@ -306,8 +299,6 @@ All CLI tools use the same runtime as the browser (`src/forge/headless.ts`), so
306
299
  - `examples/api/exploded-view.forge.js`: exploded layouts + cut-plane visualization
307
300
  - `examples/api/brep-exportable.forge.js`: exact-exportable STEP/BREP subset demo
308
301
  - `examples/api/geometry-info.forge.js`: inspect backend/provenance info for solids
309
- - `examples/api/notebook-iteration.forge-notebook.json`: stateful part exploration with pinned intermediate geometry
310
- - `examples/api/notebook-assembly-debug.forge-notebook.json`: assembly collision and sweep investigation in notebook cells
311
302
 
312
303
  BREP export support is intentionally tracked as a living parity table in [docs/permanent/API/output/brep-export.md](docs/permanent/API/output/brep-export.md).
313
304
 
@@ -1,5 +1,5 @@
1
- import { r as reactExports, j as jsxRuntimeExports, L as Link } from "./vendor-react-Dt7-aaJH.js";
2
- import { u as useAuthStore } from "./index-1CYp3zUp.js";
1
+ import { r as reactExports, j as jsxRuntimeExports, L as Link } from "./vendor-react-CG3i_wp0.js";
2
+ import { a as useAuthStore } from "./index-wTEK39at.js";
3
3
  function formatUptime(seconds) {
4
4
  const d = Math.floor(seconds / 86400);
5
5
  const h = Math.floor(seconds % 86400 / 3600);
@@ -12,7 +12,7 @@ function formatBytes(bytes) {
12
12
  if (bytes === 0) return "0 B";
13
13
  const units = ["B", "KB", "MB", "GB"];
14
14
  const i = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);
15
- const value = bytes / Math.pow(1024, i);
15
+ const value = bytes / 1024 ** i;
16
16
  return `${value.toFixed(i === 0 ? 0 : 1)} ${units[i]}`;
17
17
  }
18
18
  function timeAgo(dateStr) {
@@ -71,14 +71,20 @@ function HealthCard({ health }) {
71
71
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
72
72
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
73
73
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: dimTextStyle, children: "Status" }),
74
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: {
75
- fontSize: 13,
76
- fontWeight: 600,
77
- padding: "2px 10px",
78
- borderRadius: 6,
79
- background: isOk ? "rgba(74, 222, 128, 0.12)" : "rgba(248, 113, 113, 0.12)",
80
- color: isOk ? "#4ade80" : "#f87171"
81
- }, children: isOk ? "Healthy" : health.status })
74
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
75
+ "span",
76
+ {
77
+ style: {
78
+ fontSize: 13,
79
+ fontWeight: 600,
80
+ padding: "2px 10px",
81
+ borderRadius: 6,
82
+ background: isOk ? "rgba(74, 222, 128, 0.12)" : "rgba(248, 113, 113, 0.12)",
83
+ color: isOk ? "#4ade80" : "#f87171"
84
+ },
85
+ children: isOk ? "Healthy" : health.status
86
+ }
87
+ )
82
88
  ] }),
83
89
  /* @__PURE__ */ jsxRuntimeExports.jsx(Row, { label: "Uptime", value: formatUptime(health.uptime) }),
84
90
  /* @__PURE__ */ jsxRuntimeExports.jsx(Row, { label: "Version", value: health.version })
@@ -92,17 +98,23 @@ function Row({ label, value }) {
92
98
  ] });
93
99
  }
94
100
  function Badge({ text, color }) {
95
- return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: {
96
- padding: "2px 8px",
97
- borderRadius: 4,
98
- fontSize: 11,
99
- fontWeight: 600,
100
- background: `${color}18`,
101
- color,
102
- border: `1px solid ${color}40`,
103
- textTransform: "uppercase",
104
- letterSpacing: "0.03em"
105
- }, children: text });
101
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
102
+ "span",
103
+ {
104
+ style: {
105
+ padding: "2px 8px",
106
+ borderRadius: 4,
107
+ fontSize: 11,
108
+ fontWeight: 600,
109
+ background: `${color}18`,
110
+ color,
111
+ border: `1px solid ${color}40`,
112
+ textTransform: "uppercase",
113
+ letterSpacing: "0.03em"
114
+ },
115
+ children: text
116
+ }
117
+ );
106
118
  }
107
119
  function SignupSparkline({ data }) {
108
120
  if (!data.length) return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: dimTextStyle, children: "No data" });
@@ -154,45 +166,91 @@ function ProjectBreakdown({ stats }) {
154
166
  ] })
155
167
  ] });
156
168
  }
157
- function UserTable({ users, total }) {
169
+ function PlanToggle({ userId, currentPlan, onChanged }) {
170
+ const [loading, setLoading] = reactExports.useState(false);
171
+ const isPro = currentPlan === "pro";
172
+ const toggle = async () => {
173
+ const newPlan = isPro ? "free" : "pro";
174
+ const label = isPro ? "Downgrade to Free" : "Grant Pro";
175
+ if (!confirm(`${label} for this user?`)) return;
176
+ setLoading(true);
177
+ try {
178
+ const res = await fetch(`/api/admin/users/${userId}/plan`, {
179
+ method: "POST",
180
+ credentials: "include",
181
+ headers: { "Content-Type": "application/json" },
182
+ body: JSON.stringify({ plan: newPlan })
183
+ });
184
+ if (res.ok) {
185
+ onChanged(userId, newPlan);
186
+ }
187
+ } finally {
188
+ setLoading(false);
189
+ }
190
+ };
191
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
192
+ "button",
193
+ {
194
+ onClick: toggle,
195
+ disabled: loading,
196
+ title: isPro ? "Click to downgrade to Free" : "Click to grant Pro",
197
+ style: {
198
+ cursor: loading ? "wait" : "pointer",
199
+ background: "none",
200
+ border: "none",
201
+ padding: 0,
202
+ opacity: loading ? 0.5 : 1
203
+ },
204
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { text: currentPlan, color: isPro ? "#a78bfa" : "#64748b" })
205
+ }
206
+ );
207
+ }
208
+ function UserTable({ users, total, onPlanChanged }) {
158
209
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { ...cardStyle, padding: 0, overflow: "hidden" }, children: [
159
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { padding: "16px 20px", borderBottom: "1px solid var(--fc-border)", display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
160
- /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { style: { fontSize: 15, fontWeight: 600, color: "var(--fc-text)", margin: 0 }, children: "Users" }),
161
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { style: { fontSize: 12, color: "var(--fc-textDim)" }, children: [
162
- total,
163
- " total"
164
- ] })
165
- ] }),
210
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
211
+ "div",
212
+ {
213
+ style: {
214
+ padding: "16px 20px",
215
+ borderBottom: "1px solid var(--fc-border)",
216
+ display: "flex",
217
+ justifyContent: "space-between",
218
+ alignItems: "center"
219
+ },
220
+ children: [
221
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { style: { fontSize: 15, fontWeight: 600, color: "var(--fc-text)", margin: 0 }, children: "Users" }),
222
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { style: { fontSize: 12, color: "var(--fc-textDim)" }, children: [
223
+ total,
224
+ " total"
225
+ ] })
226
+ ]
227
+ }
228
+ ),
166
229
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { overflowX: "auto" }, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("table", { style: { width: "100%", borderCollapse: "collapse", minWidth: 700 }, children: [
167
- /* @__PURE__ */ jsxRuntimeExports.jsx("thead", { children: /* @__PURE__ */ jsxRuntimeExports.jsx("tr", { style: { borderBottom: "1px solid var(--fc-border)" }, children: ["Name", "Email", "Role", "Plan", "Projects", "Storage", "Joined"].map((h) => /* @__PURE__ */ jsxRuntimeExports.jsx("th", { style: {
168
- padding: "10px 16px",
169
- textAlign: "left",
170
- fontSize: 11,
171
- color: "var(--fc-textDim)",
172
- fontWeight: 600,
173
- textTransform: "uppercase",
174
- whiteSpace: "nowrap"
175
- }, children: h }, h)) }) }),
230
+ /* @__PURE__ */ jsxRuntimeExports.jsx("thead", { children: /* @__PURE__ */ jsxRuntimeExports.jsx("tr", { style: { borderBottom: "1px solid var(--fc-border)" }, children: ["Name", "Email", "Role", "Plan", "Projects", "Storage", "Joined"].map((h) => /* @__PURE__ */ jsxRuntimeExports.jsx(
231
+ "th",
232
+ {
233
+ style: {
234
+ padding: "10px 16px",
235
+ textAlign: "left",
236
+ fontSize: 11,
237
+ color: "var(--fc-textDim)",
238
+ fontWeight: 600,
239
+ textTransform: "uppercase",
240
+ whiteSpace: "nowrap"
241
+ },
242
+ children: h
243
+ },
244
+ h
245
+ )) }) }),
176
246
  /* @__PURE__ */ jsxRuntimeExports.jsx("tbody", { children: users.map((user) => /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { style: { borderBottom: "1px solid var(--fc-border)" }, children: [
177
247
  /* @__PURE__ */ jsxRuntimeExports.jsxs("td", { style: { padding: "10px 16px", fontSize: 13, color: "var(--fc-text)", fontWeight: 500 }, children: [
178
248
  user.name,
179
249
  !user.emailVerifiedAt && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { title: "Email not verified", style: { color: "#fb923c", marginLeft: 6, fontSize: 11 }, children: "unverified" })
180
250
  ] }),
181
251
  /* @__PURE__ */ jsxRuntimeExports.jsx("td", { style: { padding: "10px 16px", fontSize: 13, color: "var(--fc-textMuted)" }, children: user.email }),
182
- /* @__PURE__ */ jsxRuntimeExports.jsx("td", { style: { padding: "10px 16px" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
183
- Badge,
184
- {
185
- text: user.role,
186
- color: user.role === "admin" ? "#4ade80" : "#64748b"
187
- }
188
- ) }),
189
- /* @__PURE__ */ jsxRuntimeExports.jsx("td", { style: { padding: "10px 16px" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
190
- Badge,
191
- {
192
- text: user.plan,
193
- color: user.plan === "pro" ? "#a78bfa" : "#64748b"
194
- }
195
- ) }),
252
+ /* @__PURE__ */ jsxRuntimeExports.jsx("td", { style: { padding: "10px 16px" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { text: user.role, color: user.role === "admin" ? "#4ade80" : "#64748b" }) }),
253
+ /* @__PURE__ */ jsxRuntimeExports.jsx("td", { style: { padding: "10px 16px" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(PlanToggle, { userId: user.id, currentPlan: user.plan, onChanged: onPlanChanged }) }),
196
254
  /* @__PURE__ */ jsxRuntimeExports.jsx("td", { style: { padding: "10px 16px", fontSize: 13, color: "var(--fc-text)" }, children: user.projectCount }),
197
255
  /* @__PURE__ */ jsxRuntimeExports.jsx("td", { style: { padding: "10px 16px", fontSize: 13, color: "var(--fc-textMuted)" }, children: formatBytes(user.storageUsedBytes) }),
198
256
  /* @__PURE__ */ jsxRuntimeExports.jsx("td", { style: { padding: "10px 16px", fontSize: 12, color: "var(--fc-textDim)", whiteSpace: "nowrap" }, children: new Date(user.createdAt).toLocaleDateString() })
@@ -210,38 +268,69 @@ const ACTION_COLORS = {
210
268
  "share.publish": "#34d399",
211
269
  "share.delete": "#f87171",
212
270
  "member.add": "#818cf8",
213
- "member.remove": "#fb923c"
271
+ "member.remove": "#fb923c",
272
+ "license.generate": "#60a5fa",
273
+ "subscription.created": "#4ade80",
274
+ "subscription.canceled": "#fb923c",
275
+ "subscription.expired": "#f87171",
276
+ "subscription.admin_granted": "#a78bfa",
277
+ "subscription.admin_revoked": "#f87171"
214
278
  };
215
279
  function AuditLog({ entries, onLoadMore }) {
216
280
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { ...cardStyle, padding: 0, overflow: "hidden" }, children: [
217
281
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { padding: "16px 20px", borderBottom: "1px solid var(--fc-border)" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { style: { fontSize: 15, fontWeight: 600, color: "var(--fc-text)", margin: 0 }, children: "Recent Activity" }) }),
218
282
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { maxHeight: 400, overflowY: "auto" }, children: [
219
- entries.map((entry) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: {
220
- padding: "10px 20px",
221
- borderBottom: "1px solid var(--fc-border)",
222
- display: "flex",
223
- alignItems: "center",
224
- gap: 12
225
- }, children: [
226
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: {
227
- width: 6,
228
- height: 6,
229
- borderRadius: "50%",
230
- background: ACTION_COLORS[entry.action] ?? "var(--fc-textDim)",
231
- flexShrink: 0
232
- } }),
233
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [
234
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
235
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { fontSize: 12, fontWeight: 600, color: "var(--fc-text)", fontFamily: "monospace" }, children: entry.action }),
236
- entry.userName && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { style: { fontSize: 12, color: "var(--fc-textMuted)" }, children: [
237
- "by ",
238
- entry.userName
239
- ] })
240
- ] }),
241
- entry.details && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { fontSize: 11, color: "var(--fc-textDim)", marginTop: 2, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: Object.entries(entry.details).map(([k, v]) => `${k}: ${v}`).join(", ") })
242
- ] }),
243
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { fontSize: 11, color: "var(--fc-textDim)", whiteSpace: "nowrap", flexShrink: 0 }, children: timeAgo(entry.createdAt) })
244
- ] }, entry.id)),
283
+ entries.map((entry) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
284
+ "div",
285
+ {
286
+ style: {
287
+ padding: "10px 20px",
288
+ borderBottom: "1px solid var(--fc-border)",
289
+ display: "flex",
290
+ alignItems: "center",
291
+ gap: 12
292
+ },
293
+ children: [
294
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
295
+ "div",
296
+ {
297
+ style: {
298
+ width: 6,
299
+ height: 6,
300
+ borderRadius: "50%",
301
+ background: ACTION_COLORS[entry.action] ?? "var(--fc-textDim)",
302
+ flexShrink: 0
303
+ }
304
+ }
305
+ ),
306
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [
307
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
308
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { fontSize: 12, fontWeight: 600, color: "var(--fc-text)", fontFamily: "monospace" }, children: entry.action }),
309
+ entry.userName && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { style: { fontSize: 12, color: "var(--fc-textMuted)" }, children: [
310
+ "by ",
311
+ entry.userName
312
+ ] })
313
+ ] }),
314
+ entry.details && /* @__PURE__ */ jsxRuntimeExports.jsx(
315
+ "div",
316
+ {
317
+ style: {
318
+ fontSize: 11,
319
+ color: "var(--fc-textDim)",
320
+ marginTop: 2,
321
+ overflow: "hidden",
322
+ textOverflow: "ellipsis",
323
+ whiteSpace: "nowrap"
324
+ },
325
+ children: Object.entries(entry.details).map(([k, v]) => `${k}: ${v}`).join(", ")
326
+ }
327
+ )
328
+ ] }),
329
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { fontSize: 11, color: "var(--fc-textDim)", whiteSpace: "nowrap", flexShrink: 0 }, children: timeAgo(entry.createdAt) })
330
+ ]
331
+ },
332
+ entry.id
333
+ )),
245
334
  entries.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { padding: 24, textAlign: "center", color: "var(--fc-textDim)", fontSize: 14 }, children: "No activity logged yet" })
246
335
  ] }),
247
336
  entries.length > 0 && entries.length % 50 === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { padding: "12px 20px", borderTop: "1px solid var(--fc-border)", textAlign: "center" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -296,67 +385,89 @@ function AdminPage() {
296
385
  if (d) setAudit((prev) => [...prev, ...d.entries]);
297
386
  });
298
387
  }, [audit.length]);
388
+ const handlePlanChanged = reactExports.useCallback((userId, newPlan) => {
389
+ setUsers((prev) => prev.map((u) => u.id === userId ? { ...u, plan: newPlan } : u));
390
+ }, []);
299
391
  if (!user || user.role !== "admin") {
300
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: {
301
- display: "flex",
302
- alignItems: "center",
303
- justifyContent: "center",
304
- height: "100vh",
305
- flexDirection: "column",
306
- gap: 16
307
- }, children: [
308
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { fontSize: 48 }, children: "🚫" }),
309
- /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { style: { color: "var(--fc-text)", margin: 0 }, children: "Admin Access Required" }),
310
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { style: { color: "var(--fc-textMuted)", margin: 0 }, children: "You don't have permission to access this page." }),
311
- /* @__PURE__ */ jsxRuntimeExports.jsx(Link, { to: "/", style: { color: "var(--fc-accent)", textDecoration: "none" }, children: "Go home" })
312
- ] });
392
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
393
+ "div",
394
+ {
395
+ style: {
396
+ display: "flex",
397
+ alignItems: "center",
398
+ justifyContent: "center",
399
+ height: "100vh",
400
+ flexDirection: "column",
401
+ gap: 16
402
+ },
403
+ children: [
404
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { fontSize: 48 }, children: "🚫" }),
405
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { style: { color: "var(--fc-text)", margin: 0 }, children: "Admin Access Required" }),
406
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { style: { color: "var(--fc-textMuted)", margin: 0 }, children: "You don't have permission to access this page." }),
407
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Link, { to: "/", style: { color: "var(--fc-accent)", textDecoration: "none" }, children: "Go home" })
408
+ ]
409
+ }
410
+ );
313
411
  }
314
412
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { minHeight: "100vh", display: "flex", flexDirection: "column", background: "var(--fc-bg)" }, children: [
315
- /* @__PURE__ */ jsxRuntimeExports.jsxs("nav", { style: {
316
- height: 56,
317
- display: "flex",
318
- alignItems: "center",
319
- justifyContent: "space-between",
320
- padding: "0 24px",
321
- background: "var(--fc-bg)",
322
- borderBottom: "1px solid var(--fc-border)",
323
- flexShrink: 0
324
- }, children: [
325
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 12 }, children: [
326
- /* @__PURE__ */ jsxRuntimeExports.jsxs(Link, { to: "/", style: {
413
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
414
+ "nav",
415
+ {
416
+ style: {
417
+ height: 56,
327
418
  display: "flex",
328
419
  alignItems: "center",
329
- gap: 8,
330
- textDecoration: "none",
331
- color: "var(--fc-text)"
332
- }, children: [
333
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { fontSize: 20 }, children: "⚒" }),
334
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { fontSize: 16, fontWeight: 700, color: "var(--fc-accent)" }, children: "ForgeCAD" })
335
- ] }),
336
- /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { text: "Admin", color: "#fb923c" })
337
- ] }),
338
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 16 }, children: [
339
- /* @__PURE__ */ jsxRuntimeExports.jsx(
340
- "button",
341
- {
342
- onClick: loadData,
343
- title: `Last refresh: ${lastRefresh.toLocaleTimeString()}`,
344
- style: {
345
- background: "none",
346
- border: "1px solid var(--fc-border)",
347
- color: "var(--fc-textMuted)",
348
- padding: "4px 12px",
349
- borderRadius: 6,
350
- cursor: "pointer",
351
- fontSize: 12
352
- },
353
- children: "Refresh"
354
- }
355
- ),
356
- /* @__PURE__ */ jsxRuntimeExports.jsx(Link, { to: "/app", style: { color: "var(--fc-textMuted)", textDecoration: "none", fontSize: 14 }, children: "Back to App" }),
357
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { fontSize: 13, color: "var(--fc-textDim)" }, children: user.name })
358
- ] })
359
- ] }),
420
+ justifyContent: "space-between",
421
+ padding: "0 24px",
422
+ background: "var(--fc-bg)",
423
+ borderBottom: "1px solid var(--fc-border)",
424
+ flexShrink: 0
425
+ },
426
+ children: [
427
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 12 }, children: [
428
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
429
+ Link,
430
+ {
431
+ to: "/",
432
+ style: {
433
+ display: "flex",
434
+ alignItems: "center",
435
+ gap: 8,
436
+ textDecoration: "none",
437
+ color: "var(--fc-text)"
438
+ },
439
+ children: [
440
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { fontSize: 20 }, children: "⚒" }),
441
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { fontSize: 16, fontWeight: 700, color: "var(--fc-accent)" }, children: "ForgeCAD" })
442
+ ]
443
+ }
444
+ ),
445
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { text: "Admin", color: "#fb923c" })
446
+ ] }),
447
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 16 }, children: [
448
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
449
+ "button",
450
+ {
451
+ onClick: loadData,
452
+ title: `Last refresh: ${lastRefresh.toLocaleTimeString()}`,
453
+ style: {
454
+ background: "none",
455
+ border: "1px solid var(--fc-border)",
456
+ color: "var(--fc-textMuted)",
457
+ padding: "4px 12px",
458
+ borderRadius: 6,
459
+ cursor: "pointer",
460
+ fontSize: 12
461
+ },
462
+ children: "Refresh"
463
+ }
464
+ ),
465
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Link, { to: "/app", style: { color: "var(--fc-textMuted)", textDecoration: "none", fontSize: 14 }, children: "Back to App" }),
466
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { fontSize: 13, color: "var(--fc-textDim)" }, children: user.name })
467
+ ] })
468
+ ]
469
+ }
470
+ ),
360
471
  /* @__PURE__ */ jsxRuntimeExports.jsxs("main", { style: { flex: 1, padding: 24, maxWidth: 1200, margin: "0 auto", width: "100%" }, children: [
361
472
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 24 }, children: [
362
473
  /* @__PURE__ */ jsxRuntimeExports.jsx("h1", { style: { fontSize: 28, fontWeight: 700, color: "var(--fc-text)", margin: 0 }, children: "Dashboard" }),
@@ -372,13 +483,7 @@ function AdminPage() {
372
483
  sub: stats ? `+${stats.users.signupsToday} today` : void 0
373
484
  }
374
485
  ),
375
- /* @__PURE__ */ jsxRuntimeExports.jsx(
376
- StatNumber,
377
- {
378
- value: (stats == null ? void 0 : stats.projects.total) ?? "-",
379
- label: "Total Projects"
380
- }
381
- ),
486
+ /* @__PURE__ */ jsxRuntimeExports.jsx(StatNumber, { value: (stats == null ? void 0 : stats.projects.total) ?? "-", label: "Total Projects" }),
382
487
  /* @__PURE__ */ jsxRuntimeExports.jsx(
383
488
  StatNumber,
384
489
  {
@@ -387,20 +492,14 @@ function AdminPage() {
387
492
  sub: stats ? `${stats.users.activeWeek} this week` : void 0
388
493
  }
389
494
  ),
390
- /* @__PURE__ */ jsxRuntimeExports.jsx(
391
- StatNumber,
392
- {
393
- value: stats ? formatBytes(stats.storage.totalBytes) : "-",
394
- label: "Storage Used"
395
- }
396
- )
495
+ /* @__PURE__ */ jsxRuntimeExports.jsx(StatNumber, { value: stats ? formatBytes(stats.storage.totalBytes) : "-", label: "Storage Used" })
397
496
  ] }),
398
497
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(260px, 1fr))", gap: 16, marginBottom: 20 }, children: [
399
498
  /* @__PURE__ */ jsxRuntimeExports.jsx(SignupSparkline, { data: signups }),
400
499
  stats && /* @__PURE__ */ jsxRuntimeExports.jsx(UserBreakdown, { stats }),
401
500
  stats && /* @__PURE__ */ jsxRuntimeExports.jsx(ProjectBreakdown, { stats })
402
501
  ] }),
403
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { marginBottom: 20 }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(UserTable, { users, total: usersTotal }) }),
502
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { marginBottom: 20 }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(UserTable, { users, total: usersTotal, onPlanChanged: handlePlanChanged }) }),
404
503
  /* @__PURE__ */ jsxRuntimeExports.jsx(AuditLog, { entries: audit, onLoadMore: loadMoreAudit })
405
504
  ] })
406
505
  ] });