forgecad 0.6.3 → 0.8.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 (234) hide show
  1. package/README.md +3 -12
  2. package/dist/assets/{AdminPage-CeqCUUgu.js → AdminPage-D4bocK4E.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-D3A_g8V3.js} +329 -163
  5. package/dist/assets/{EditorApp-CnC2k4cW.css → EditorApp-BWYUSpUN.css} +590 -136
  6. package/dist/assets/EditorApp-Cihhqcsq.js +11692 -0
  7. package/dist/assets/{EmbedViewer-DBlzmQ5i.js → EmbedViewer-kWjKaC_t.js} +2 -4
  8. package/dist/assets/LandingPageProofDriven-Bg2IUc3l.css +856 -0
  9. package/dist/assets/LandingPageProofDriven-DXkKlyhI.js +601 -0
  10. package/dist/assets/PricingPage-BsU5vzEx.js +232 -0
  11. package/dist/assets/{SettingsPage-BqCh9JcC.js → SettingsPage-PqvpAKIs.js} +129 -5
  12. package/dist/assets/{evalWorker-Ql-aKwLA.js → evalWorker-C-hzNUMy.js} +8949 -3161
  13. package/dist/assets/{Viewport-CoB46f5R.js → index-Pz321YAt.js} +38382 -7501
  14. package/dist/assets/{index-2hfs_ub0.css → index-ay13WNfa.css} +726 -53
  15. package/dist/assets/{javascript-DCxGoE5Y.js → javascript-DAl8Gmyo.js} +1 -1
  16. package/dist/assets/{manifold-CqNMHHKO.js → manifold-BcbjWLIo.js} +4 -3
  17. package/dist/assets/{manifold-Cce9wRFz.js → manifold-DBckbFgx.js} +1 -1
  18. package/dist/assets/{manifold-D6BeHIOo.js → manifold-O2AAGXyj.js} +1 -1
  19. package/dist/assets/{reportWorker-sFEFonXf.js → reportWorker-Dxr-5A7w.js} +8760 -3559
  20. package/dist/assets/{vendor-react-Dt7-aaJH.js → vendor-react-CG3i_wp0.js} +65 -8
  21. package/dist/docs/index.html +2 -2
  22. package/dist/docs-raw/CLI.md +341 -718
  23. package/dist/docs-raw/generated/assembly.md +699 -112
  24. package/dist/docs-raw/generated/concepts.md +1834 -1346
  25. package/dist/docs-raw/generated/core.md +1012 -1059
  26. package/dist/docs-raw/generated/curves.md +759 -116
  27. package/dist/docs-raw/generated/lib.md +43 -748
  28. package/dist/docs-raw/generated/output.md +139 -245
  29. package/dist/docs-raw/generated/sdf.md +208 -0
  30. package/dist/docs-raw/generated/sheet-metal.md +473 -21
  31. package/dist/docs-raw/generated/sketch.md +1518 -362
  32. package/dist/docs-raw/generated/viewport.md +368 -299
  33. package/dist/docs-raw/generated/wood.md +104 -0
  34. package/dist/index.html +2 -2
  35. package/dist/landing/proof-ams-adapter.png +0 -0
  36. package/dist/landing/proof-bolt-and-nut.png +0 -0
  37. package/dist/landing/proof-fillet-enclosure.png +0 -0
  38. package/dist/landing/proof-glasses.png +0 -0
  39. package/dist/landing/proof-gyroid.png +0 -0
  40. package/dist/sitemap.xml +6 -6
  41. package/dist-cli/forgecad.js +12321 -5700
  42. package/dist-cli/forgecad.js.map +1 -0
  43. package/dist-cli/solver-46FFSK2U.js +363 -0
  44. package/dist-cli/solver-46FFSK2U.js.map +1 -0
  45. package/dist-skill/CONTEXT.md +4890 -6302
  46. package/dist-skill/SKILL-dev.md +22 -66
  47. package/dist-skill/SKILL.md +20 -59
  48. package/dist-skill/docs/API/core/concepts.md +37 -92
  49. package/dist-skill/docs/CLI.md +341 -718
  50. package/dist-skill/docs/generated/assembly.md +699 -112
  51. package/dist-skill/docs/generated/core.md +1012 -1059
  52. package/dist-skill/docs/generated/curves.md +759 -116
  53. package/dist-skill/docs/generated/lib.md +43 -748
  54. package/dist-skill/docs/generated/output.md +139 -245
  55. package/dist-skill/docs/generated/sdf.md +208 -0
  56. package/dist-skill/docs/generated/sheet-metal.md +473 -21
  57. package/dist-skill/docs/generated/sketch.md +1518 -362
  58. package/dist-skill/docs/generated/viewport.md +368 -299
  59. package/dist-skill/docs/generated/wood.md +104 -0
  60. package/dist-skill/docs/guides/coordinate-system.md +11 -17
  61. package/dist-skill/docs/guides/geometry-conventions.md +13 -70
  62. package/dist-skill/docs/guides/joint-design.md +78 -0
  63. package/dist-skill/docs/guides/modeling-recipes.md +22 -195
  64. package/dist-skill/docs/guides/positioning.md +88 -147
  65. package/dist-skill/docs-dev/API/core/concepts.md +78 -0
  66. package/dist-skill/docs-dev/CLI.md +488 -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 +2 -4
  70. package/dist-skill/docs-dev/component-model.md +164 -0
  71. package/dist-skill/docs-dev/generated/assembly.md +779 -0
  72. package/dist-skill/docs-dev/generated/core.md +1676 -0
  73. package/dist-skill/docs-dev/generated/curves.md +855 -0
  74. package/dist-skill/docs-dev/generated/lib.md +55 -0
  75. package/dist-skill/docs-dev/generated/output.md +234 -0
  76. package/dist-skill/docs-dev/generated/sdf.md +208 -0
  77. package/dist-skill/docs-dev/generated/sheet-metal.md +506 -0
  78. package/dist-skill/docs-dev/generated/sketch.md +1753 -0
  79. package/dist-skill/docs-dev/generated/viewport.md +513 -0
  80. package/dist-skill/docs-dev/generated/wood.md +104 -0
  81. package/dist-skill/docs-dev/guides/coordinate-system.md +46 -0
  82. package/dist-skill/docs-dev/guides/geometry-conventions.md +52 -0
  83. package/dist-skill/docs-dev/guides/joint-design.md +78 -0
  84. package/dist-skill/docs-dev/guides/modeling-recipes.md +77 -0
  85. package/dist-skill/docs-dev/guides/positioning.md +151 -0
  86. package/dist-skill/{docs → docs-dev}/guides/skill-maintenance.md +21 -10
  87. package/dist-skill/{docs → docs-dev}/internals/compiler.md +5 -6
  88. package/dist-skill/{docs → docs-dev}/internals/constraint-solver-quality.md +0 -1
  89. package/dist-skill/{docs → docs-dev}/internals/constraint-solver.md +0 -1
  90. package/dist-skill/{docs → docs-dev}/internals/sketch-2d-pipeline.md +2 -3
  91. package/examples/api/attachTo-basics.forge.js +8 -8
  92. package/examples/api/bill-of-materials.forge.js +9 -9
  93. package/examples/api/bolt-pattern.forge.js +5 -5
  94. package/examples/api/boolean-operations.forge.js +5 -5
  95. package/examples/api/bounding-box-visualizer.forge.js +3 -3
  96. package/examples/api/clone-duplicate.forge.js +2 -2
  97. package/examples/api/colors-union-vs-array.forge.js +6 -6
  98. package/examples/api/connector-assembly.forge.js +8 -6
  99. package/examples/api/connector-basics.forge.js +7 -7
  100. package/examples/api/constrained-sketch-mechanical.forge.js +4 -4
  101. package/examples/api/elbow-test.forge.js +3 -3
  102. package/examples/api/extrude-options.forge.js +8 -14
  103. package/examples/api/feature-created-faces.forge.js +6 -10
  104. package/examples/api/fillet-showcase.forge.js +2 -2
  105. package/examples/api/folded-service-panel-cover.forge.js +2 -2
  106. package/examples/api/gears-tier1.forge.js +5 -5
  107. package/examples/api/group-test.forge.js +3 -3
  108. package/examples/api/group-vs-union.forge.js +1 -1
  109. package/examples/api/highlight-debug.forge.js +4 -0
  110. package/examples/api/js-module-pillars.js +1 -1
  111. package/examples/api/js-module-scene.js +2 -2
  112. package/examples/api/mesh-import-slats.forge.js +4 -4
  113. package/examples/api/patterns.forge.js +3 -3
  114. package/examples/api/pointAlong-orientation.forge.js +3 -3
  115. package/examples/api/profile-2020-b-slot6.forge.js +4 -5
  116. package/examples/api/route-perimeter-flange.forge.js +1 -1
  117. package/examples/api/sdf-rover-demo.forge.js +10 -10
  118. package/examples/api/sketch-on-face-demo.forge.js +2 -2
  119. package/examples/api/sketch-regions.forge.js +4 -4
  120. package/examples/api/sketch-rounding-strategies.forge.js +1 -1
  121. package/examples/api/smooth-curve-connections.forge.js +1 -1
  122. package/examples/api/transition-curves.forge.js +4 -4
  123. package/examples/api/variable-sweep-pure-sdf-test.forge.js +162 -0
  124. package/examples/api/variable-sweep-test.forge.js +2 -2
  125. package/examples/api/wood-joinery.forge.js +60 -0
  126. package/examples/compiler-corpus/enclosure-shell-cuts.forge.js +3 -3
  127. package/examples/compiler-corpus/fastener-plate-variants.forge.js +2 -2
  128. package/examples/constraints/01-fully-constrained-rect.forge.js +2 -2
  129. package/examples/constraints/02-underconstrained-triangle.forge.js +1 -1
  130. package/examples/constraints/03-redundant-constraints.forge.js +2 -2
  131. package/examples/constraints/05-parallel-with-linedistance.forge.js +2 -2
  132. package/examples/constraints/06-complex-spectrogram.forge.js +1 -1
  133. package/examples/constraints/07-perpendicular-chain.forge.js +4 -4
  134. package/examples/constraints/08-symmetric-bracket.forge.js +4 -4
  135. package/examples/constraints/09-stress-spiral.forge.js +1 -1
  136. package/examples/constraints/10-stress-honeycomb.forge.js +1 -1
  137. package/examples/constraints/11-surface-grid.forge.js +2 -2
  138. package/examples/constraints/12-surface-nested.forge.js +4 -4
  139. package/examples/constraints/13-surface-complex.forge.js +1 -1
  140. package/examples/exact-arc-housing.forge.js +12 -0
  141. package/examples/experiments/drone-arm.forge.js +53 -0
  142. package/examples/furniture/adjustable-table.forge.js +15 -15
  143. package/examples/furniture/bathroom.forge.js +26 -26
  144. package/examples/furniture/chair.forge.js +13 -13
  145. package/examples/furniture/picture-frame.forge.js +6 -6
  146. package/examples/furniture/shoe-rack-doors.forge.js +8 -8
  147. package/examples/furniture/shoe-rack.forge.js +7 -7
  148. package/examples/furniture/table-lamp.forge.js +8 -8
  149. package/examples/gcode/lissajous-vase.forge.js +4 -4
  150. package/examples/gcode/math-surface.forge.js +3 -3
  151. package/examples/gcode/parametric-vase.forge.js +4 -4
  152. package/examples/gcode/spiral-tower.forge.js +4 -4
  153. package/examples/generative/crystal-growth.forge.js +9 -9
  154. package/examples/generative/frost-spires.forge.js +9 -9
  155. package/examples/generative/golden-spiral-tower.forge.js +11 -11
  156. package/examples/generative/molten-forge.forge.js +6 -6
  157. package/examples/generative/neon-coral.forge.js +7 -7
  158. package/examples/mechanical/3d-printer.forge.js +37 -37
  159. package/examples/mechanical/5-finger-robot-hand.forge.js +19 -19
  160. package/examples/mechanical/airplane-propeller.forge.js +9 -9
  161. package/examples/mechanical/bolt-and-nut.forge.js +10 -10
  162. package/examples/mechanical/door-with-hinges.forge.js +7 -7
  163. package/examples/mechanical/fillet-enclosure.forge.js +15 -11
  164. package/examples/mechanical/headphone-hanger-v2.forge.js +11 -11
  165. package/examples/mechanical/robot_hand.forge.js +24 -24
  166. package/examples/mechanical/robot_hand_2.forge.js +26 -26
  167. package/examples/nurbs-surface.forge.js +8 -0
  168. package/examples/nurbs-tube.forge.js +7 -0
  169. package/examples/products/bottle.forge.js +8 -8
  170. package/examples/products/chess-set.forge.js +25 -25
  171. package/examples/products/classical-piano.forge.js +20 -20
  172. package/examples/products/clock.forge.js +33 -33
  173. package/examples/products/cup.forge.js +5 -5
  174. package/examples/products/iphone.forge.js +20 -20
  175. package/examples/products/laptop.forge.js +24 -24
  176. package/examples/products/laser-cut-box.forge.js +6 -6
  177. package/examples/products/laser-cut-tray.forge.js +6 -6
  178. package/examples/products/liquid-soap-dispenser.forge.js +23 -23
  179. package/examples/products/origami-fish.forge.js +14 -12
  180. package/examples/products/spiderman-cake.forge.js +6 -6
  181. package/examples/shelf/container.forge.js +5 -5
  182. package/examples/shelf/shelf-unit.forge.js +6 -6
  183. package/examples/toolbox/bolted-joint.forge.js +7 -7
  184. package/package.json +9 -4
  185. package/dist/assets/EditorApp-B-vQvgam.js +0 -9888
  186. package/dist/assets/LandingPage-C5n9hDXI.js +0 -322
  187. package/dist/assets/PublishedModelPage-Dt7PCVBj.js +0 -146
  188. package/dist/assets/__vite-browser-external-CURh0WXD.js +0 -8
  189. package/dist/assets/deserializeRunResult-BLAFoiE0.js +0 -19365
  190. package/dist/assets/index-1CYp3zUp.js +0 -1455
  191. package/dist-skill/docs/API/API.md +0 -1666
  192. package/dist-skill/docs/API/README.md +0 -37
  193. package/dist-skill/docs/API/assembly/assembly.md +0 -617
  194. package/dist-skill/docs/API/core/edge-queries.md +0 -130
  195. package/dist-skill/docs/API/core/parameters.md +0 -122
  196. package/dist-skill/docs/API/core/reserved-terms.md +0 -137
  197. package/dist-skill/docs/API/core/sdf.md +0 -326
  198. package/dist-skill/docs/API/core/skill-cli.md +0 -194
  199. package/dist-skill/docs/API/core/skill-guide.md +0 -205
  200. package/dist-skill/docs/API/core/specs.md +0 -186
  201. package/dist-skill/docs/API/core/topology.md +0 -372
  202. package/dist-skill/docs/API/entities.md +0 -268
  203. package/dist-skill/docs/API/output/bom.md +0 -58
  204. package/dist-skill/docs/API/output/brep-export.md +0 -87
  205. package/dist-skill/docs/API/output/dimensions.md +0 -67
  206. package/dist-skill/docs/API/output/export.md +0 -110
  207. package/dist-skill/docs/API/output/gcode.md +0 -195
  208. package/dist-skill/docs/API/runtime/viewport.md +0 -420
  209. package/dist-skill/docs/API/sheet-metal/sheet-metal.md +0 -185
  210. package/dist-skill/docs/API/sketch/anchor.md +0 -37
  211. package/dist-skill/docs/API/sketch/booleans.md +0 -91
  212. package/dist-skill/docs/API/sketch/core.md +0 -73
  213. package/dist-skill/docs/API/sketch/extrude.md +0 -62
  214. package/dist-skill/docs/API/sketch/on-face.md +0 -104
  215. package/dist-skill/docs/API/sketch/operations.md +0 -78
  216. package/dist-skill/docs/API/sketch/path.md +0 -75
  217. package/dist-skill/docs/API/sketch/primitives.md +0 -146
  218. package/dist-skill/docs/API/sketch/regions.md +0 -80
  219. package/dist-skill/docs/API/sketch/text.md +0 -108
  220. package/dist-skill/docs/API/sketch/transforms.md +0 -65
  221. package/dist-skill/docs/API/toolbox/fasteners.md +0 -129
  222. package/dist-skill/docs/INDEX.md +0 -94
  223. package/dist-skill/docs/RELEASING.md +0 -55
  224. package/dist-skill/docs/cli-monetization.md +0 -111
  225. package/dist-skill/docs/deployment.md +0 -281
  226. package/dist-skill/docs/generated/concepts.md +0 -2112
  227. package/dist-skill/docs/internals/shape-from-slices.md +0 -152
  228. package/dist-skill/docs/platform/admin.md +0 -45
  229. package/dist-skill/docs/platform/architecture.md +0 -79
  230. package/dist-skill/docs/platform/auth.md +0 -110
  231. package/dist-skill/docs/platform/email.md +0 -67
  232. package/dist-skill/docs/platform/projects.md +0 -111
  233. package/dist-skill/docs/platform/sharing.md +0 -90
  234. package/dist-skill/docs/runbook.md +0 -345
@@ -1,152 +0,0 @@
1
- # Shape.fromSlices — Design Document
2
-
3
- ## Problem
4
-
5
- Users want to define 3D shapes by specifying 2D cross-section profiles on
6
- different planes (like slicing an object at various positions), then
7
- interpolating between them to create the solid. Think: "here's what the egg
8
- looks like from the side, here's what it looks like from the top — build me
9
- the 3D shape."
10
-
11
- Today's `loft()` only handles parallel XY cross-sections at different Z
12
- heights. There is no way to combine constraints from multiple viewing
13
- directions (e.g., XY circle + XZ egg-ellipse = egg).
14
-
15
- ## API
16
-
17
- ```ts
18
- Shape.fromSlices(slices: Slice[], options?: FromSlicesOptions): Shape
19
- ```
20
-
21
- ### Types
22
-
23
- ```ts
24
- type SlicePlane = 'xy' | 'xz' | 'yz' | Vec3;
25
-
26
- interface Slice {
27
- on: SlicePlane; // plane normal (string shorthand or arbitrary unit vector)
28
- at: number; // signed offset along that normal from the origin
29
- profile: Sketch; // 2D cross-section on that plane
30
- }
31
-
32
- interface FromSlicesOptions {
33
- edgeLength?: number; // mesh resolution (Manifold only)
34
- boundsPadding?: number; // SDF bounding box padding (Manifold only)
35
- }
36
- ```
37
-
38
- ### Why `Shape.fromSlices()`?
39
-
40
- 1. **No new global** — `Shape` already exists in the runtime. Adding a static
41
- method adds zero namespace pollution.
42
- 2. **Discoverable** — "I want a Shape, what can I build one from?" leads
43
- naturally to `Shape.fromSlices`, `Shape.fromPatch`, etc.
44
- 3. **Precedent** — `Points.distance()`, `sdf.sphere()`, `route.fillet()` all
45
- use the namespace-dot-method pattern. This extends it to the core `Shape`
46
- class.
47
- 4. **Consistent with broader direction** — new specialized constructors go on
48
- `Shape` as static methods instead of polluting the flat global namespace.
49
-
50
- ## Algorithm
51
-
52
- ### Core idea: group → interpolate → intersect
53
-
54
- 1. **Group** slices by normal direction. Two slices with the same (or
55
- antiparallel) normal belong to the same group.
56
-
57
- 2. **Within each group** — build a solid by lofting (ThruSections / level-set)
58
- between the profiles, placed on their respective parallel planes.
59
- - Single-slice group: extrude the profile along the normal direction,
60
- bounded generously (later trimmed by intersection).
61
- - Multi-slice group: loft between profiles via ThruSections (OCCT) or
62
- level-set (Manifold).
63
-
64
- 3. **Across groups** — boolean-intersect all group solids. Each group's solid
65
- represents a "viewing constraint": the final shape must fit within all of
66
- them simultaneously.
67
-
68
- ### Why intersection?
69
-
70
- Each group defines "what the object looks like from one direction." Extruding
71
- a side-view silhouette gives a prism that contains every possible shape
72
- matching that silhouette. Intersecting all such prisms carves out the shape
73
- that satisfies all views simultaneously. This is the *visual hull*
74
- reconstruction principle.
75
-
76
- ### Examples
77
-
78
- **Egg (two silhouettes):**
79
- ```
80
- Group 1 (normal=Y, 1 slice): XZ egg-ellipse → extrude along Y → egg prism
81
- Group 2 (normal=X, 1 slice): YZ egg-ellipse → extrude along X → egg prism
82
- Intersect → egg solid
83
- ```
84
-
85
- **Vase (loft + envelope):**
86
- ```
87
- Group 1 (normal=Z, 3 slices): circle@0, squircle@40, circle@80 → loft
88
- Group 2 (normal=Y, 1 slice): vase silhouette → extrude along Y
89
- Intersect → vase with cross-section transitions
90
- ```
91
-
92
- **Pure loft (single group):**
93
- ```
94
- Group 1 (normal=Z, 3 slices): profiles at different Z heights → loft
95
- No intersection needed (single group) → equivalent to loft()
96
- ```
97
-
98
- ## Compile plan
99
-
100
- A new `kind: 'fromSlices'` is added to `ShapeCompilePlan`:
101
-
102
- ```ts
103
- {
104
- kind: 'fromSlices';
105
- groups: FromSlicesGroupPlan[];
106
- edgeLength: number;
107
- boundsPadding: number;
108
- }
109
-
110
- interface FromSlicesGroupPlan {
111
- normal: [number, number, number]; // unit normal for this group
112
- slices: {
113
- offset: number; // signed distance along normal
114
- profile: ProfileCompilePlan;
115
- }[];
116
- }
117
- ```
118
-
119
- ### Lowering strategy
120
-
121
- **OCCT backend:**
122
- - Single-slice group → extrude profile, rotate to align with normal, translate
123
- - Multi-slice group → ThruSections with wires placed on their planes
124
- (existing lowerLoftPlan pattern, generalized from Z-only to arbitrary normal)
125
- - Intersection → `BRepAlgoAPI_Common`
126
- - Result: exact B-rep, exportable to STEP
127
-
128
- **Manifold backend:**
129
- - Single-slice group → extrude cross-section, transform
130
- - Multi-slice group → loft via level-set (existing buildLoftLevelSetInput)
131
- - Intersection → Manifold boolean intersection
132
- - Result: mesh
133
-
134
- ### Single-group optimization
135
-
136
- When all slices share the same normal (pure loft case), skip the intersection
137
- entirely and lower as a plain loft. This avoids unnecessary boolean overhead
138
- and produces identical output to `loft()`.
139
-
140
- ## Broader API implications
141
-
142
- This is the first `Shape` static method. It establishes a pattern for future
143
- specialized constructors:
144
-
145
- | Method | Replaces global | Status |
146
- |--------|----------------|--------|
147
- | `Shape.fromSlices()` | (new) | This PR |
148
- | `Shape.fromPatch()` | `surfacePatch()` | Future |
149
-
150
- Existing globals (`loft`, `sweep`, `box`, etc.) remain unchanged — no
151
- migration, no breakage. New specialized construction goes through
152
- `Shape.method()`.
@@ -1,45 +0,0 @@
1
- # Admin Dashboard & Audit System
2
-
3
- The admin dashboard lives at `/admin` and is restricted to users with `role = 'admin'`. The frontend gate is in `src/pages/AdminPage.tsx`; the backend enforces the same role check on `/api/admin/*` routes.
4
-
5
- ## Dashboard features
6
-
7
- - **System health** -- server status, uptime, version (fetched from `/api/health`).
8
- - **User statistics** -- total users, total projects, active today.
9
- - **User list** -- all users with name, email, role, and join date.
10
- - **Audit log** -- recent activity feed (via `/api/admin/audit`).
11
-
12
- ## Audit log
13
-
14
- Every significant user action is recorded in the `audit_log` table. The schema (`server/db/schema.ts`):
15
-
16
- | Column | Type | Description |
17
- |--------|------|-------------|
18
- | `id` | uuid | Primary key |
19
- | `user_id` | uuid | FK to `users` |
20
- | `project_id` | uuid | FK to `projects` (nullable) |
21
- | `action` | text | Event type (see below) |
22
- | `details` | jsonb | Arbitrary metadata |
23
- | `ip_address` | inet | Client IP |
24
- | `created_at` | timestamptz | When it happened |
25
-
26
- ### Tracked actions
27
-
28
- | Category | Actions |
29
- |----------|---------|
30
- | Auth | `user.register`, `user.login`, `user.password_reset`, `user.token_replay_detected` |
31
- | Projects | `project.create`, `project.update`, `project.delete` |
32
- | Files | `file.save`, `file.delete` |
33
- | Shares | `share.publish`, `share.unpublish` |
34
-
35
- Audit inserts are inline in each route handler (`server/routes/auth.ts`, `server/routes/projects.ts`, `server/routes/files.ts`, `server/routes/shares.ts`).
36
-
37
- ## Granting admin access
38
-
39
- There is no self-service role escalation. Set the role directly in the database:
40
-
41
- ```sql
42
- UPDATE users SET role = 'admin' WHERE email = 'user@example.com';
43
- ```
44
-
45
- See [auth.md](auth.md) for the role system and [../project/runbook.md](../project/runbook.md) for production database access.
@@ -1,79 +0,0 @@
1
- # System Architecture
2
-
3
- ForgeCAD is a browser-based parametric CAD application. The production stack runs as Docker containers on Hetzner, managed by Coolify.
4
-
5
- ## Stack Overview
6
-
7
- ```
8
- Internet
9
- |
10
- Coolify Proxy (Traefik, TLS termination)
11
- |
12
- forgecad container (Fastify / Node.js)
13
- |--- serves SPA (Vite-built React frontend)
14
- |--- API routes (/api/*)
15
- |
16
- PostgreSQL
17
- ```
18
-
19
- **Frontend:** React SPA built with Vite. Three.js (via React Three Fiber) powers the 3D viewport. Monaco Editor provides the code editor. The SPA is served by the same Fastify process that handles API routes.
20
-
21
- **Server:** Fastify with Drizzle ORM for database access. Serves both the static SPA bundle and the `/api/*` JSON endpoints from a single process.
22
-
23
- **Database:** PostgreSQL for user accounts, project metadata, and application state.
24
-
25
- **File storage:** User project files are stored on disk (Docker volume `project-data`), not in the database.
26
-
27
- ## Docker Compose Configurations
28
-
29
- Two compose files support different deployment targets:
30
-
31
- | File | Services | Use case |
32
- |------|----------|----------|
33
- | `docker-compose.yml` | Caddy + ForgeCAD + PostgreSQL + Redis | Full self-hosted deployment with built-in TLS |
34
- | `docker-compose.coolify.yml` | ForgeCAD + PostgreSQL | Coolify-managed deployment (Traefik handles TLS) |
35
-
36
- Production uses `docker-compose.coolify.yml`. Coolify auto-deploys on push to the `mainline` branch.
37
-
38
- ## Authentication
39
-
40
- JWT-based auth with token rotation:
41
-
42
- - **Access tokens:** 15-minute expiry
43
- - **Refresh tokens:** 7-day expiry, rotated on use
44
-
45
- See [auth.md](auth.md) for details on OAuth providers, token flow, and session management.
46
-
47
- ## URL Routes
48
-
49
- | Route | Access | Purpose |
50
- |-------|--------|---------|
51
- | `/` | Public | Landing page |
52
- | `/docs`, `/docs/:slug` | Public | API documentation |
53
- | `/blog`, `/blog/:slug` | Public | Announcements and tutorials |
54
- | `/app` | Auth-gated | The CAD editor |
55
- | `/app?gist=<id>` | Public preview | Shared model from GitHub Gist (read-only until login) |
56
- | `/app?url=<url>` | Public preview | Shared model from external URL |
57
- | `/app#code/<file>/<compressed>` | Public preview | Shared model with inline code |
58
- | `/app?embed=1` | Public | Embed-only viewport (no chrome, for iframes) |
59
- | `/m/:shareId` | Public | Published model preview (code + 3D viewport) |
60
- | `/m/:shareId?embed=1` | Public | Published model embed (viewport only) |
61
- | `/admin` | Admin role | User management, system health, audit log |
62
- | `/auth/callback/*` | Public | OAuth callback handlers |
63
-
64
- ## Security
65
-
66
- - **Rate limiting:** 100 requests/min per IP; 10 auth attempts per 15 minutes
67
- - **CORS:** Origin whitelist (no wildcard)
68
- - **CSP:** Content Security Policy headers on all responses
69
- - **Path traversal:** Server-side validation on all file access paths
70
-
71
- ## Related Documentation
72
-
73
- - [auth.md](auth.md) -- Authentication and authorization
74
- - [projects.md](projects.md) -- Project storage and management
75
- - [sharing.md](sharing.md) -- Model sharing and embeds
76
- - [admin.md](admin.md) -- Admin panel
77
- - [email.md](email.md) -- Transactional email
78
- - [../project/deployment.md](../project/deployment.md) -- Environment variables and deployment setup
79
- - [../project/runbook.md](../project/runbook.md) -- Operational procedures and troubleshooting
@@ -1,110 +0,0 @@
1
- # Authentication
2
-
3
- ForgeCAD uses JWT-based authentication with refresh token rotation. Tokens are delivered via `httpOnly` cookies -- never exposed to client-side JavaScript.
4
-
5
- ---
6
-
7
- ## Token Architecture
8
-
9
- - **Access token**: JWT signed with HMAC (HS256). Default lifetime: 15 minutes.
10
- - **Refresh token**: Opaque token stored in the database. Default lifetime: 7 days.
11
- - **Rotation**: Each refresh request issues a new refresh token and invalidates the old one. If a previously-used refresh token is replayed, all sessions for that user are revoked (replay detection).
12
-
13
- Both tokens are set as `httpOnly`, `Secure`, `SameSite=Strict` cookies.
14
-
15
- ---
16
-
17
- ## User Roles
18
-
19
- | Role | Access |
20
- |-------|--------|
21
- | `user` | Standard account, can use `/app` and all editor features |
22
- | `admin` | Full access including `/admin` dashboard |
23
-
24
- Middleware:
25
-
26
- - `requireAuth` -- extracts the user from the JWT access token. Returns 401 if the token is missing or expired.
27
- - `requireAdmin` -- calls `requireAuth`, then checks `role === 'admin'`. Returns 403 otherwise.
28
-
29
- ---
30
-
31
- ## Registration and Login
32
-
33
- 1. **Register** (`POST /api/auth/register`) -- accepts email and password. Creates the account and sends a verification email. See [email delivery](email.md) for delivery configuration.
34
- 2. **Verify email** (`GET /api/auth/verify-email`) -- confirms the address using the token from the email link.
35
- 3. **Login** (`POST /api/auth/login`) -- validates credentials, sets access and refresh token cookies.
36
-
37
- Password reset:
38
-
39
- 1. `POST /api/auth/forgot-password` -- sends a reset email with a single-use token (1-hour expiry).
40
- 2. `POST /api/auth/reset-password` -- applies the new password using the reset token.
41
-
42
- ---
43
-
44
- ## OAuth
45
-
46
- GitHub and Google are supported as optional OAuth providers. They are enabled by setting the corresponding environment variables (see below). When not configured, the provider is omitted from the login UI.
47
-
48
- - `GET /api/auth/providers` -- returns the list of configured OAuth providers and their authorization URLs.
49
- - `POST /api/auth/callback/:provider` -- handles the OAuth redirect, creates or links the account, and sets token cookies.
50
-
51
- ---
52
-
53
- ## API Endpoints
54
-
55
- | Method | Route | Purpose |
56
- |--------|-------|---------|
57
- | `POST` | `/api/auth/register` | Create account |
58
- | `POST` | `/api/auth/login` | Login (sets cookies) |
59
- | `POST` | `/api/auth/refresh` | Rotate refresh token, issue new access token |
60
- | `POST` | `/api/auth/logout` | Clear session cookies, invalidate refresh token |
61
- | `GET` | `/api/auth/session` | Return current user info (requires valid access token) |
62
- | `GET` | `/api/auth/providers` | List available OAuth providers |
63
- | `POST` | `/api/auth/callback/:provider` | OAuth callback |
64
- | `POST` | `/api/auth/forgot-password` | Request password reset email |
65
- | `POST` | `/api/auth/reset-password` | Apply password reset |
66
- | `GET` | `/api/auth/verify-email` | Confirm email address |
67
- | `POST` | `/api/auth/resend-verification` | Resend verification email |
68
-
69
- ---
70
-
71
- ## Rate Limiting
72
-
73
- Auth endpoints are rate-limited to **10 requests per 15 minutes per IP**. This applies to login, registration, password reset, and token refresh. Exceeding the limit returns HTTP 429.
74
-
75
- ---
76
-
77
- ## Environment Variables
78
-
79
- | Variable | Required | Default | Purpose |
80
- |----------|----------|---------|---------|
81
- | `FORGE_JWT_SECRET` | Yes | -- | HMAC signing key for JWT access tokens |
82
- | `FORGE_JWT_ACCESS_EXPIRES` | No | `15m` | Access token lifetime (e.g. `15m`, `1h`) |
83
- | `FORGE_JWT_REFRESH_EXPIRES` | No | `7d` | Refresh token lifetime |
84
- | `GITHUB_CLIENT_ID` | No | -- | GitHub OAuth application ID |
85
- | `GITHUB_CLIENT_SECRET` | No | -- | GitHub OAuth secret |
86
- | `GOOGLE_CLIENT_ID` | No | -- | Google OAuth client ID |
87
- | `GOOGLE_CLIENT_SECRET` | No | -- | Google OAuth secret |
88
- | `FORGE_ALLOWED_ORIGINS` | No | `localhost` | Comma-separated list of allowed CORS origins |
89
-
90
- See [deployment.md](../project/deployment.md) for the full environment variable reference.
91
-
92
- ---
93
-
94
- ## Troubleshooting
95
-
96
- **500 on login** -- `FORGE_JWT_SECRET` is not set. The server cannot sign tokens without it.
97
-
98
- **401 on authenticated endpoints** -- the access token has expired. The client should call `POST /api/auth/refresh` to obtain a new one. If refresh also returns 401, the session has expired entirely and the user must log in again.
99
-
100
- **CORS errors** -- `FORGE_ALLOWED_ORIGINS` does not include the frontend's origin. Add the correct domain (e.g. `https://forgecad.io`).
101
-
102
- ---
103
-
104
- ## Source Files
105
-
106
- | File | Purpose |
107
- |------|---------|
108
- | `server/routes/auth.ts` | All auth route handlers |
109
- | `server/middleware/auth.ts` | `requireAuth` and `requireAdmin` middleware |
110
- | `server/db/schema.ts` | User and refresh token table definitions |
@@ -1,67 +0,0 @@
1
- # Email Delivery
2
-
3
- ForgeCAD uses [Resend](https://resend.com) for transactional email. The free tier covers 3,000 emails/month and 100/day — more than enough for early production.
4
-
5
- Emails are sent for:
6
- - Email address verification (on register + resend)
7
- - Password reset links
8
-
9
- See [auth.md](auth.md) for the authentication flows that trigger these emails.
10
-
11
- ## Environment Variables
12
-
13
- | Variable | Required | Default | Notes |
14
- |---|---|---|---|
15
- | `RESEND_API_KEY` | Yes (in prod) | — | From Resend dashboard |
16
- | `FORGE_EMAIL_FROM` | No | `ForgeCAD <noreply@forgecad.io>` | Must match a verified domain |
17
- | `FORGE_APP_URL` | No | `http://localhost:5173` | Base URL used in email links |
18
-
19
- Without `RESEND_API_KEY`, emails fall back to `console.log` — fine for local dev.
20
-
21
- See [../project/deployment.md](../project/deployment.md) for the full environment variable reference.
22
-
23
- ## First-Time Setup
24
-
25
- ### 1. Create a Resend account
26
-
27
- Sign up at [resend.com](https://resend.com). No credit card required for the free tier.
28
-
29
- ### 2. Verify your sending domain
30
-
31
- In the Resend dashboard: **Domains** > **Add Domain** > enter `forgecad.io`.
32
-
33
- Resend will show you DNS records to add (typically a few TXT/MX entries). If your DNS is on **Cloudflare**, Resend can auto-configure the records — just connect your Cloudflare account in the Resend dashboard. It takes a few minutes to propagate.
34
-
35
- ### 3. Create an API key
36
-
37
- In the Resend dashboard: **API Keys** > **Create API Key**.
38
-
39
- Use **Send access only** — no need for full access on the server.
40
-
41
- ### 4. Set env vars
42
-
43
- In Coolify (production) or your local `.env`:
44
-
45
- ```
46
- RESEND_API_KEY=re_...
47
- FORGE_EMAIL_FROM=ForgeCAD <noreply@forgecad.io>
48
- FORGE_APP_URL=https://forgecad.io
49
- ```
50
-
51
- ## Local Development
52
-
53
- Leave `RESEND_API_KEY` unset. The `send()` function detects the missing key and logs the email content and token to the console instead of sending. Token links are printed so you can copy them directly into the browser.
54
-
55
- ## Code Layout
56
-
57
- | File | Purpose |
58
- |---|---|
59
- | `server/email/send.ts` | Resend client + `sendVerificationEmail` / `sendPasswordResetEmail` helpers |
60
- | `server/env.ts` | `resendApiKey`, `emailFrom`, `appUrl` env vars |
61
- | `server/routes/auth.ts` | Calls into `send.ts` from register, forgot-password, resend-verification routes |
62
-
63
- ## Adding a New Email Type
64
-
65
- 1. Add a new `send*Email(to, ...)` export in `server/email/send.ts`
66
- 2. Call it from the relevant route in `server/routes/auth.ts` (or wherever)
67
- 3. No new infrastructure needed — same Resend client and `send()` helper
@@ -1,111 +0,0 @@
1
- # Projects and File Management
2
-
3
- Projects are the top-level container for user work in ForgeCAD. Each project groups related scripts, notebooks, and output files under a single name with access controls.
4
-
5
- ## Project Model
6
-
7
- Each project has:
8
-
9
- | Field | Type | Description |
10
- |-------|------|-------------|
11
- | `id` | UUID | Primary key |
12
- | `slug` | string | URL-safe identifier, unique per owner |
13
- | `name` | string | Display name |
14
- | `visibility` | `private` \| `public` | Controls unauthenticated access |
15
- | `ownerId` | UUID | Foreign key to the user who created the project |
16
-
17
- Source: `server/db/schema.ts`
18
-
19
- ## Member Roles
20
-
21
- Projects support collaborative access through membership:
22
-
23
- | Role | Capabilities |
24
- |------|-------------|
25
- | **owner** | Full control: rename, delete, manage members, read/write files |
26
- | **editor** | Read and write files (save, delete, mkdir) |
27
- | **viewer** | Read files, watch for changes |
28
-
29
- The owner is always the user who created the project. Ownership cannot be transferred through the membership API.
30
-
31
- ## File Storage
32
-
33
- Project files are stored on disk, not in the database. The storage root is configured by the `FORGE_STORAGE_ROOT` environment variable (default: `/data/projects` in Docker). Each project gets an isolated directory under this root.
34
-
35
- See [deployment.md](../project/deployment.md) for Docker volume and storage configuration.
36
-
37
- ### Supported File Types
38
-
39
- | Extension | Purpose |
40
- |-----------|---------|
41
- | `.forge.js` | ForgeCAD model scripts |
42
- | `.forge-notebook.json` | Interactive notebooks |
43
- | `.js` | Utility modules (imported by scripts) |
44
- | `.svg` | Vector assets |
45
- | `.stl`, `.3mf` | Exported mesh files |
46
-
47
- ### Path Traversal Protection
48
-
49
- All file operations validate resolved paths against the project's storage directory. Any path that escapes the project root (via `..` segments or symlinks) is rejected. This check runs in `server/storage.ts` before any read or write reaches the filesystem.
50
-
51
- ### Storage Quotas
52
-
53
- Each user has a storage quota tracked via `storage_used_bytes` on the user record.
54
-
55
- | Limit | Value |
56
- |-------|-------|
57
- | Storage per user | 50 MB |
58
- | Projects per user | 20 |
59
-
60
- The quota is checked before every file write. Storage counters are adjusted atomically and clamped to zero to prevent negative drift from race conditions or deleted files.
61
-
62
- Source: `server/quotas.ts`
63
-
64
- ## Real-Time File Watching
65
-
66
- Clients can subscribe to file changes via Server-Sent Events:
67
-
68
- ```
69
- GET /api/projects/:id/watch
70
- ```
71
-
72
- This endpoint streams events whenever files in the project are created, modified, or deleted. The editor uses this to stay in sync when multiple clients have the same project open.
73
-
74
- ## API Endpoints
75
-
76
- ### Projects
77
-
78
- | Method | Route | Min Role | Purpose |
79
- |--------|-------|----------|---------|
80
- | GET | `/api/projects` | -- | List the authenticated user's projects |
81
- | POST | `/api/projects` | -- | Create a new project |
82
- | GET | `/api/projects/:id` | viewer | Get project details |
83
- | PATCH | `/api/projects/:id` | owner | Update project name, slug, or visibility |
84
- | DELETE | `/api/projects/:id` | owner | Delete project and all its files |
85
-
86
- ### Members
87
-
88
- | Method | Route | Min Role | Purpose |
89
- |--------|-------|----------|---------|
90
- | GET | `/api/projects/:id/members` | viewer | List project members |
91
- | POST | `/api/projects/:id/members` | owner | Add a member by user ID |
92
- | PATCH | `/api/projects/:id/members/:userId` | owner | Change a member's role |
93
- | DELETE | `/api/projects/:id/members/:userId` | owner | Remove a member |
94
-
95
- ### Files
96
-
97
- | Method | Route | Min Role | Purpose |
98
- |--------|-------|----------|---------|
99
- | GET | `/api/projects/:id/watch` | viewer | SSE stream of file changes |
100
- | POST | `/api/projects/:id/save` | editor | Write file contents to disk |
101
- | POST | `/api/projects/:id/delete` | editor | Delete a file |
102
- | POST | `/api/projects/:id/mkdir` | editor | Create a directory |
103
- | GET | `/api/projects/:id/read-binary` | viewer | Download a binary file (meshes, assets) |
104
-
105
- Source: `server/routes/projects.ts`, `server/routes/files.ts`
106
-
107
- ## Related
108
-
109
- - [Architecture](architecture.md) -- system overview and deployment topology
110
- - [Sharing](sharing.md) -- model publishing and public URLs
111
- - [Deployment](../project/deployment.md) -- storage volume configuration, Docker setup
@@ -1,90 +0,0 @@
1
- # Model Sharing and Publishing
2
-
3
- ForgeCAD supports multiple ways to share models. Two categories exist: **server-backed publishing** (persistent URLs, requires auth to publish) and **client-side sharing** (no server needed, data encoded in the URL).
4
-
5
- See [architecture.md](architecture.md) for the full URL route table.
6
-
7
- ## Server-backed publishing
8
-
9
- Authenticated users can publish models to get a stable, public URL. Published models are stored in the `shared_files` database table.
10
-
11
- ### Database schema (`shared_files`)
12
-
13
- | Column | Type | Notes |
14
- |--------|------|-------|
15
- | `id` | uuid | Primary key |
16
- | `share_id` | text | Unique, 10-char nanoid -- the URL identifier |
17
- | `owner_id` | uuid | FK to `users.id`, cascade delete |
18
- | `filename` | text | Original filename |
19
- | `code` | text | Full source code |
20
- | `title` | text | Optional display title |
21
- | `created_at` | timestamptz | Auto-set |
22
- | `updated_at` | timestamptz | Auto-set, updated on re-publish |
23
-
24
- ### Public URLs
25
-
26
- | URL | Behavior |
27
- |-----|----------|
28
- | `/m/:shareId` | Full model preview: source code panel + 3D viewport |
29
- | `/m/:shareId?embed=1` | Viewport only, no UI chrome -- designed for iframe embeds |
30
-
31
- No authentication is required to view a published model.
32
-
33
- ### API endpoints
34
-
35
- | Method | Route | Auth | Purpose |
36
- |--------|-------|------|---------|
37
- | GET | `/api/shares/:shareId` | None | Fetch a published model (code, title, author name, timestamps) |
38
- | GET | `/api/shares` | Required | List the authenticated user's published models |
39
- | POST | `/api/shares` | Required | Publish a new model or update an existing one |
40
- | DELETE | `/api/shares/:shareId` | Required (owner or admin) | Unpublish a model |
41
-
42
- ### Publish behavior
43
-
44
- - If the user already has a published model with the same filename, `POST /api/shares` updates the existing share rather than creating a new one. The `share_id` is preserved so existing links remain valid.
45
- - Each user may publish up to **100** models. Exceeding this limit returns `403` with `{ error: "Share limit reached" }`.
46
- - All publish, update, and delete operations are recorded in the `audit_log` table.
47
-
48
- ### Audit trail
49
-
50
- Every share mutation writes to `audit_log` with one of these actions:
51
- - `share.publish` -- new model published
52
- - `share.update` -- existing model code/title updated
53
- - `share.delete` -- model unpublished
54
-
55
- ## Client-side sharing (no server)
56
-
57
- These methods encode model data directly into the URL. They work without authentication and without the hosted backend.
58
-
59
- ### Inline code (hash fragment)
60
-
61
- Format: `/app#code/<filename>/<lz-compressed-code>`
62
-
63
- The code is compressed with `lz-string` (`compressToEncodedURIComponent`) and placed in the URL hash. Since hash fragments are not sent to the server, this works entirely client-side. Encoding and decoding logic lives in `src/share.ts`.
64
-
65
- ### Multi-file bundles (hash fragment)
66
-
67
- Format: `/app#bundle/<lz-compressed-packed>`
68
-
69
- Packs multiple files into a single compressed payload using null-byte separators: `entry\0filename1\0code1\0filename2\0code2...`. Used when a model has import dependencies.
70
-
71
- ### Gist sharing
72
-
73
- Format: `/app?gist=<github-gist-id>`
74
-
75
- Fetches code from a public GitHub Gist at load time.
76
-
77
- ### URL sharing
78
-
79
- Format: `/app?url=<external-url>`
80
-
81
- Fetches code from any publicly accessible URL at load time.
82
-
83
- ## Source files
84
-
85
- | File | Role |
86
- |------|------|
87
- | `server/routes/shares.ts` | API route handlers for publish/unpublish/list |
88
- | `server/db/schema.ts` | `sharedFiles` table definition and types |
89
- | `src/pages/SharedModelPage.tsx` | Frontend page rendering `/m/:shareId` |
90
- | `src/share.ts` | Client-side encode/decode for hash-based sharing |