forgecad 0.9.13 → 0.9.15
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.
- package/LICENSE +6 -4
- package/README.md +8 -4
- package/dist/assets/{AdminPage-DramHHDf.js → AdminPage-CDyGUinA.js} +2 -2
- package/dist/assets/{BenchmarkPage-Bjgkh5m9.js → BenchmarkPage-DfPMY_-d.js} +4 -15
- package/dist/assets/{BlogPage-n_HGP3Qm.js → BlogPage-kF0fkdJT.js} +2 -2
- package/dist/assets/{DocsPage-WCIkPmzC.js → DocsPage-B954L3YN.js} +9 -3
- package/dist/assets/EditorApp-Beb-IZ0y.js +14014 -0
- package/dist/assets/{EditorApp-BAnckbsk.css → EditorApp-CuDLxKqL.css} +698 -0
- package/dist/assets/{EmbedViewer-DEZKqdfW.js → EmbedViewer-C77B-TrF.js} +3 -3
- package/dist/assets/{LandingPageProofDriven-CeRIctuj.js → LandingPageProofDriven-Cr6fXMDj.js} +35 -37
- package/dist/assets/LegalPage-BRlScr9A.css +91 -0
- package/dist/assets/LegalPage-Dzklqmmg.js +39 -0
- package/dist/assets/{PricingPage-BMedqFef.css → PricingPage-BPF6HKyO.css} +25 -0
- package/dist/assets/{PricingPage-rIRa8p4Y.js → PricingPage-zWXkvlwl.js} +19 -19
- package/dist/assets/{SettingsPage-BqCUvEXM.js → SettingsPage-Bz0of4KQ.js} +2 -2
- package/dist/assets/app-CE3sYcV7.css +3890 -0
- package/dist/assets/{app-BUZqJvSO.js → app-D3kDkggg.js} +2305 -960
- package/dist/assets/cli/{render-lhGxj50Y.js → render-DSY3mMQa.js} +423 -30
- package/dist/assets/{constructionHistoryWorker-ipD1jcIv.js → constructionHistoryWorker-gpDo-uH2.js} +927 -243
- package/dist/assets/{evalWorker-CHXSe_-u.js → evalWorker-CU0Ke6DP.js} +7799 -4163
- package/dist/assets/{forgecad_geometry-BVnIeXMG.js → forgecad_geometry-Dgceylq9.js} +43 -1
- package/dist/assets/{forgecad_geometry_bg-DufhhCBV.wasm → forgecad_geometry_bg-dD4RNQF1.wasm} +0 -0
- package/dist/assets/{inspectWorker-DeRnMVv1.js → inspectWorker-COyp8XXA.js} +927 -243
- package/dist/assets/{javascript-70-4uGcz.js → javascript-1kQXfVaz.js} +1 -1
- package/dist/assets/landing-proof-driven-DiGqdtWa.js +18 -0
- package/dist/assets/{landing-proof-driven-oFYW6mjz.css → landing-proof-driven-ORyigZ6p.css} +13 -7
- package/dist/assets/legalContent-ZfFGMmi4.js +251 -0
- package/dist/assets/{manifold-D1LZIHqn.js → manifold-BRI5prcH.js} +1 -1
- package/dist/assets/{manifold-C2fwoTgd.js → manifold-C-3h2M7p.js} +2 -2
- package/dist/assets/{manifold-BTkzxi9V.js → manifold-DNkrUWpA.js} +1 -1
- package/dist/assets/{reportWorker-Cq1qGmg0.js → reportWorker-CdBz5bNg.js} +7537 -10856
- package/dist/assets/{scalar-sampling-budget-D9Qv_UlJ.js → scalar-sampling-budget-wJF98aY9.js} +6943 -4345
- package/dist/assets/{scanProxyWorker-Bs2TDgLw.js → scanProxyWorker-B-9VbLIs.js} +32 -1
- package/dist/assets/{renderSceneState-Dr0xPq1A.js → targets-B9sGB5nB.js} +27 -1
- package/dist/assets/{vendor-react-Da3A2QmU.js → vendor-react-6j1Kke-Y.js} +6 -5
- package/dist/cli/render.html +1 -1
- package/dist/docs/index.html +2 -2
- package/dist/docs-raw/AI/ai-native-cad.md +50 -0
- package/dist/docs-raw/AI/usage.md +9 -17
- package/dist/docs-raw/CLI.md +71 -21
- package/dist/docs-raw/component-model.md +27 -11
- package/dist/docs-raw/generated/assembly.md +301 -212
- package/dist/docs-raw/generated/concepts.md +238 -240
- package/dist/docs-raw/generated/core.md +283 -6
- package/dist/docs-raw/generated/curves.md +274 -361
- package/dist/docs-raw/generated/lib.md +7 -1
- package/dist/docs-raw/generated/output.md +19 -4
- package/dist/docs-raw/generated/runtime-names.md +41 -0
- package/dist/docs-raw/generated/sdf.md +31 -0
- package/dist/docs-raw/generated/sheet-metal.md +9 -0
- package/dist/docs-raw/generated/sketch.md +44 -1
- package/dist/docs-raw/generated/viewport.md +14 -6
- package/dist/docs-raw/guides/coordinate-system.md +20 -16
- package/dist/docs-raw/guides/geometry-conventions.md +2 -2
- package/dist/docs-raw/guides/inspection-bundles.md +2 -1
- package/dist/docs-raw/guides/joint-design.md +24 -0
- package/dist/docs-raw/guides/positioning.md +13 -3
- package/dist/docs-raw/legal/privacy.md +63 -0
- package/dist/docs-raw/legal/software-license.md +55 -0
- package/dist/docs-raw/legal/terms.md +87 -0
- package/dist/docs-raw/skills/forgecad-3d-reconstruction.md +3 -3
- package/dist/docs-raw/skills/forgecad-blockout-model.md +1 -1
- package/dist/docs-raw/skills/forgecad-component-model.md +11 -2
- package/dist/docs-raw/skills/forgecad-high-level-spec.md +1 -1
- package/dist/docs-raw/skills/forgecad-image-replicator.md +8 -8
- package/dist/docs-raw/skills/forgecad-lld.md +1 -1
- package/dist/docs-raw/skills/forgecad-make-a-model.md +4 -4
- package/dist/docs-raw/skills/forgecad-model-grader.md +2 -2
- package/dist/docs-raw/skills/forgecad-prepare-prompt.md +2 -2
- package/dist/docs-raw/skills/forgecad-project.md +1 -1
- package/dist/docs-raw/skills/forgecad-reconstruction-benchmark.md +4 -4
- package/dist/docs-raw/skills/forgecad-render-inspect.md +4 -2
- package/dist/docs-raw/skills/forgecad-visual-spec.md +1 -1
- package/dist/docs-raw/skills/forgecad.md +4 -3
- package/dist/index.html +40 -12
- package/dist/llms.txt +8 -0
- package/dist/site.webmanifest +1 -1
- package/dist/sitemap.xml +49 -13
- package/dist-cli/{check-compiler-LOXCPEOI.js → check-compiler-SDX5QIXI.js} +1 -2
- package/dist-cli/{check-query-propagation-BAKNVWXR.js → check-query-propagation-EAYEFT77.js} +1 -2
- package/dist-cli/{chunk-RY43WF46.js → chunk-N4O47JLF.js} +13772 -9938
- package/dist-cli/forgecad.js +2387 -899
- package/dist-cli/{forgecad_geometry-GYVNKPIE.js → forgecad_geometry-QOQIIP53.js} +42 -1
- package/dist-cli/forgecad_geometry_bg.wasm +0 -0
- package/dist-cli/{solver-46FFSK2U.js → solver-OK4HECRH.js} +0 -1
- package/dist-skill/CONTEXT.md +1120 -724
- package/dist-skill/SKILL.md +3 -2
- package/dist-skill/docs/API/core/concepts.md +64 -1
- package/dist-skill/docs/CLI.md +71 -21
- package/dist-skill/docs/generated/assembly.md +277 -229
- package/dist-skill/docs/generated/core.md +283 -6
- package/dist-skill/docs/generated/curves.md +272 -362
- package/dist-skill/docs/generated/lib.md +7 -1
- package/dist-skill/docs/generated/output.md +19 -4
- package/dist-skill/docs/generated/runtime-names.md +41 -0
- package/dist-skill/docs/generated/sdf.md +31 -0
- package/dist-skill/docs/generated/sheet-metal.md +9 -0
- package/dist-skill/docs/generated/sketch.md +44 -2
- package/dist-skill/docs/generated/viewport.md +5 -90
- package/dist-skill/docs/guides/coordinate-system.md +20 -16
- package/dist-skill/docs/guides/geometry-conventions.md +2 -2
- package/dist-skill/docs/guides/inspection-bundles.md +2 -1
- package/dist-skill/docs/guides/joint-design.md +24 -0
- package/dist-skill/docs/guides/positioning.md +13 -3
- package/dist-skill/library/forgecad-3d-reconstruction/SKILL.md +2 -2
- package/dist-skill/library/forgecad-component-model/SKILL.md +10 -1
- package/dist-skill/library/forgecad-image-replicator/SKILL.md +6 -6
- package/dist-skill/library/forgecad-image-replicator/scripts/compare_images.py +166 -0
- package/dist-skill/library/forgecad-make-a-model/SKILL.md +3 -3
- package/dist-skill/library/forgecad-model-grader/SKILL.md +1 -1
- package/dist-skill/library/forgecad-prepare-prompt/SKILL.md +1 -1
- package/dist-skill/library/forgecad-reconstruction-benchmark/SKILL.md +3 -3
- package/dist-skill/library/forgecad-render-inspect/SKILL.md +3 -1
- package/examples/api/assembly-kinematics-foundation.forge.js +65 -0
- package/examples/api/assembly-kinematics-four-bar.forge.js +115 -0
- package/examples/api/assembly-kinematics-limb.forge.js +116 -0
- package/examples/api/connector-frame-rig-chain.forge.js +102 -0
- package/examples/api/exact-sheet-shell-assembly.forge.js +0 -2
- package/examples/api/exact-surface-studio.forge.js +6 -8
- package/examples/api/helix-basics.forge.js +6 -6
- package/examples/api/lean-foundations/README.md +12 -0
- package/examples/api/lean-foundations/curve-blend-exact.forge.js +22 -0
- package/examples/api/lean-foundations/curve-fit-interpolation.forge.js +18 -0
- package/examples/api/lean-foundations/curve-helix-canonicalization.forge.js +27 -0
- package/examples/api/lean-foundations/curve-route-canonicalization.forge.js +16 -0
- package/examples/api/lean-foundations/curve-trim-reverse.forge.js +24 -0
- package/examples/api/lean-foundations/exact-curve-arc.forge.js +36 -0
- package/examples/api/mixed-edge-finishes-proof.forge.js +8 -11
- package/examples/api/route3d-elbow.forge.js +68 -0
- package/examples/api/transition-curves.forge.js +44 -15
- package/examples/api/y-blend-corner-showcase.forge.js +0 -2
- package/examples/generative/coral-vase.forge.js +1 -1
- package/examples/nurbs-tube.forge.js +1 -1
- package/package.json +14 -18
- package/dist/assets/EditorApp-CP9Za6tm.js +0 -13630
- package/dist/assets/app-CsHnaBWt.css +0 -1789
- package/dist/docs-raw/API/README.md +0 -16
- package/dist/docs-raw/API/core/concepts.md +0 -118
- package/dist/docs-raw/INDEX.md +0 -138
- package/dist/docs-raw/RELEASING.md +0 -87
- package/dist/docs-raw/agent-native-api.md +0 -27
- package/dist/docs-raw/beta-deployment.md +0 -304
- package/dist/docs-raw/beta-operations.md +0 -325
- package/dist/docs-raw/blueprint-first.md +0 -145
- package/dist/docs-raw/cli-monetization.md +0 -112
- package/dist/docs-raw/coding-best-practices.md +0 -120
- package/dist/docs-raw/coding.md +0 -340
- package/dist/docs-raw/deployment.md +0 -374
- package/dist/docs-raw/guides/skill-maintenance.md +0 -161
- package/dist/docs-raw/guides/surface-members.md +0 -82
- package/dist/docs-raw/internals/backend-vocabulary.md +0 -35
- package/dist/docs-raw/internals/compiler.md +0 -307
- package/dist/docs-raw/internals/constraint-solver-quality.md +0 -161
- package/dist/docs-raw/internals/constraint-solver.md +0 -176
- package/dist/docs-raw/internals/shape-from-slices.md +0 -152
- package/dist/docs-raw/internals/sketch-2d-pipeline.md +0 -108
- package/dist/docs-raw/platform/admin.md +0 -45
- package/dist/docs-raw/platform/architecture.md +0 -82
- package/dist/docs-raw/platform/auth.md +0 -139
- package/dist/docs-raw/platform/email.md +0 -67
- package/dist/docs-raw/platform/google-oauth-setup.md +0 -88
- package/dist/docs-raw/platform/observability.md +0 -197
- package/dist/docs-raw/platform/projects.md +0 -111
- package/dist/docs-raw/platform/sharing.md +0 -90
- package/dist/docs-raw/product/README.md +0 -39
- package/dist/docs-raw/product/api-as-product-language.md +0 -13
- package/dist/docs-raw/product/business-model.md +0 -15
- package/dist/docs-raw/product/competitive-positioning.md +0 -17
- package/dist/docs-raw/product/creative-manufacturing.md +0 -15
- package/dist/docs-raw/product/founder-story.md +0 -11
- package/dist/docs-raw/product/manufacturing-workflows.md +0 -15
- package/dist/docs-raw/product/onboarding-first-experience.md +0 -256
- package/dist/docs-raw/product/product-loop.md +0 -17
- package/dist/docs-raw/product/strategic-decisions.md +0 -22
- package/dist/docs-raw/product/user-outreach-email-templates.md +0 -161
- package/dist/docs-raw/product/user-segments.md +0 -15
- package/dist/docs-raw/product/vision.md +0 -26
- package/dist/docs-raw/rl-environments.md +0 -508
- package/dist/docs-raw/runbook.md +0 -611
- package/dist-cli/check-compiler-LOXCPEOI.js.map +0 -1
- package/dist-cli/check-query-propagation-BAKNVWXR.js.map +0 -1
- package/dist-cli/chunk-RY43WF46.js.map +0 -1
- package/dist-cli/forgecad.js.map +0 -1
- package/dist-cli/forgecad_geometry-GYVNKPIE.js.map +0 -1
- package/dist-cli/solver-46FFSK2U.js.map +0 -1
- package/dist-skill/SKILL-dev.md +0 -145
- package/dist-skill/docs-dev/API/core/concepts.md +0 -118
- package/dist-skill/docs-dev/CLI.md +0 -647
- package/dist-skill/docs-dev/agent-native-api.md +0 -27
- package/dist-skill/docs-dev/blueprint-first.md +0 -145
- package/dist-skill/docs-dev/coding-best-practices.md +0 -120
- package/dist-skill/docs-dev/coding.md +0 -340
- package/dist-skill/docs-dev/component-model.md +0 -164
- package/dist-skill/docs-dev/generated/assembly.md +0 -794
- package/dist-skill/docs-dev/generated/core.md +0 -2117
- package/dist-skill/docs-dev/generated/curves.md +0 -2583
- package/dist-skill/docs-dev/generated/lib.md +0 -169
- package/dist-skill/docs-dev/generated/output.md +0 -247
- package/dist-skill/docs-dev/generated/sdf.md +0 -446
- package/dist-skill/docs-dev/generated/sheet-metal.md +0 -504
- package/dist-skill/docs-dev/generated/sketch.md +0 -1811
- package/dist-skill/docs-dev/generated/viewport.md +0 -585
- package/dist-skill/docs-dev/generated/wood.md +0 -108
- package/dist-skill/docs-dev/guides/coordinate-system.md +0 -46
- package/dist-skill/docs-dev/guides/geometry-conventions.md +0 -52
- package/dist-skill/docs-dev/guides/inspection-bundles.md +0 -485
- package/dist-skill/docs-dev/guides/joint-design.md +0 -78
- package/dist-skill/docs-dev/guides/modeling-recipes.md +0 -78
- package/dist-skill/docs-dev/guides/positioning.md +0 -161
- package/dist-skill/docs-dev/guides/skill-maintenance.md +0 -161
- package/dist-skill/docs-dev/internals/backend-vocabulary.md +0 -35
- package/dist-skill/docs-dev/internals/compiler.md +0 -307
- package/dist-skill/docs-dev/internals/constraint-solver-quality.md +0 -161
- package/dist-skill/docs-dev/internals/constraint-solver.md +0 -176
- package/dist-skill/docs-dev/internals/sketch-2d-pipeline.md +0 -108
- package/dist-skill/library/forgecad-image-replicator/scripts/compare_images.mjs +0 -289
|
@@ -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,108 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
skill-group: dev-compiler
|
|
3
|
-
skill-order: 2
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# 2D Sketch Pipeline: Runtime Mesh ↔ ProfileCompilePlan ↔ Export Backend
|
|
7
|
-
|
|
8
|
-
Captured from implementing sketch region selection, planar arrangement detection, and cross-sketch reference geometry (2026-03).
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## The Two-Track Export Model
|
|
13
|
-
|
|
14
|
-
Every 3D shape in ForgeCAD has two representations:
|
|
15
|
-
|
|
16
|
-
1. **Runtime mesh** — a watertight triangulated solid used for preview rendering and mesh-domain operations.
|
|
17
|
-
2. **`ProfileCompilePlan`** — a serializable Forge intent description (discriminated union with `kind: 'rect' | 'circle' | 'polygon' | 'boolean' | ...`) that the current export backend reads to produce STEP geometry.
|
|
18
|
-
|
|
19
|
-
When exporting STEP, the system walks the `ProfileCompilePlan` tree. If a node has no plan (plan is `null`), it falls back to a faceted mesh-to-STEP conversion — useful for interchange, but it loses parametric intent.
|
|
20
|
-
|
|
21
|
-
**Key invariant**: any sketch API that only calls `polygon()` and boolean ops (`add`/`subtract`) automatically stays on the maintained STEP path. No special backend-specific code is needed.
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
## What `Sketch` Is
|
|
26
|
-
|
|
27
|
-
`Sketch` wraps a Manifold `CrossSection` (2D polygon set). It also carries:
|
|
28
|
-
- An optional `ProfileCompilePlan` for the STEP path.
|
|
29
|
-
- A `Placement3D` for face-mounted sketches (`.onFace()`).
|
|
30
|
-
|
|
31
|
-
`ConstrainedSketchBuilder` → `.solve()` → `ConstraintSketch extends Sketch` holds the constraint definition alongside the solved `CrossSection`. The solved `CrossSection` is built from explicitly declared loops (`addLoop`); geometry outside loops is not included in the area — only in the constraint definition for arrangement detection.
|
|
32
|
-
|
|
33
|
-
---
|
|
34
|
-
|
|
35
|
-
## New 2D Sketch Surface-Selection APIs (2026-03)
|
|
36
|
-
|
|
37
|
-
### 1. `sketch.regions()` / `sketch.region(seed)`
|
|
38
|
-
|
|
39
|
-
**File**: `src/forge/sketch/regions.ts`
|
|
40
|
-
|
|
41
|
-
Decomposes a Manifold `CrossSection` into its distinct filled areas.
|
|
42
|
-
|
|
43
|
-
- `CrossSection.toPolygons()` returns a flat list of contours. Positive signed area → outer boundary; negative → hole.
|
|
44
|
-
- Holes are nested into their smallest containing outer boundary via `pointInPolygon`.
|
|
45
|
-
- Each outer boundary + its holes is reassembled as a `polygon(outerPts, holePts)` sketch.
|
|
46
|
-
- The `region(seed)` variant picks the one face whose outer boundary contains the seed and no hole does.
|
|
47
|
-
|
|
48
|
-
**STEP export**: uses `polygon()` and potentially `boolean(difference)` for rings — both are handled by the current export backend without extra sketch-specific code.
|
|
49
|
-
|
|
50
|
-
### 2. `constraintSketch.detectArrangement()` / `detectArrangementRegion(seed)`
|
|
51
|
-
|
|
52
|
-
**File**: `src/forge/sketch/arrangement.ts`
|
|
53
|
-
|
|
54
|
-
DCEL-based planar arrangement detection from the raw line segments in a `ConstraintDefinition`.
|
|
55
|
-
|
|
56
|
-
Algorithm:
|
|
57
|
-
1. Extract non-construction line segments from `def.lines`.
|
|
58
|
-
2. Split segments at all pairwise intersections — both X-crossings (`segSegT`) and **T-junctions** (`pointOnSegT`). T-junction support is critical: when a divider endpoint touches a boundary edge interior, only `pointOnSegT` detects it.
|
|
59
|
-
3. Snap nearby nodes and build a clean planar graph.
|
|
60
|
-
4. Build DCEL half-edges. At each node sort outgoing half-edges by polar angle.
|
|
61
|
-
5. `next(u→v)` = outgoing from v immediately preceding `twin(u→v)` in CCW order at v — i.e., `out[(pos−1+n) % n]`.
|
|
62
|
-
6. Traverse all face cycles; keep CCW faces (positive signed area). CW = unbounded outer face, excluded.
|
|
63
|
-
7. Return each face as a `polygon(pts)` sketch.
|
|
64
|
-
|
|
65
|
-
No explicit loops needed from the caller. Works on any set of line constraints.
|
|
66
|
-
|
|
67
|
-
**STEP export**: each face is a `polygon()` — handled by the maintained export path.
|
|
68
|
-
|
|
69
|
-
### 3. `builderB.referenceFrom(sketchA, entityId)` / `referenceAllFrom(sketchA)`
|
|
70
|
-
|
|
71
|
-
**File**: `src/forge/sketch/constraints.ts`
|
|
72
|
-
|
|
73
|
-
Import solved geometry from another `ConstraintSketch` as fixed construction references.
|
|
74
|
-
|
|
75
|
-
- Fixed points (`fixed: true`) and construction lines (`construction: true`) participate in constraint solving but contribute zero area to the resulting CrossSection profile.
|
|
76
|
-
- `referenceFrom(source, id)` looks up the entity by id in the source's `ConstraintDefinition` and creates a fixed copy in the current builder.
|
|
77
|
-
- Enables constraints like `parallel(bBot, refBase)` to lock relationships between separate sketches.
|
|
78
|
-
|
|
79
|
-
**STEP export**: construction elements are ignored by `buildSketchFromDefinition` when assembling loops → no impact on export path.
|
|
80
|
-
|
|
81
|
-
---
|
|
82
|
-
|
|
83
|
-
## Backend Agnosticism
|
|
84
|
-
|
|
85
|
-
These APIs are fully backend-agnostic because they operate on:
|
|
86
|
-
- **2D coordinates only** — no 3D kernel calls during region/arrangement detection.
|
|
87
|
-
- **`polygon()`** — the lowest-level sketch primitive, available on every backend.
|
|
88
|
-
- **Boolean ops** — available on every backend (Manifold, OCCT, …).
|
|
89
|
-
|
|
90
|
-
No backend-specific code was added. Adding a new backend (e.g., CGAL, OpenCASCADE directly) automatically inherits all three APIs as long as it handles `polygon` and `boolean` plan kinds.
|
|
91
|
-
|
|
92
|
-
The only Manifold-specific call is `CrossSection.toPolygons()` in `regions.ts`. If a future backend doesn't use `CrossSection`, `sketchRegions` would need an adapter — but the algorithm and public API shape would be unchanged.
|
|
93
|
-
|
|
94
|
-
---
|
|
95
|
-
|
|
96
|
-
## Gotchas
|
|
97
|
-
|
|
98
|
-
### Empty CrossSection for loop-less sketches
|
|
99
|
-
|
|
100
|
-
`constrainedSketch().solve()` used to throw "at least one closed loop" when called without `addLoop()`. Users calling `.detectArrangement()` never add loops. Fix: return an empty `CrossSection` via `CrossSection.difference([unit, unit])` (not `new CrossSection([])` — Manifold's `polygons2vec` crashes on empty array).
|
|
101
|
-
|
|
102
|
-
### T-junctions
|
|
103
|
-
|
|
104
|
-
Interior-only intersection detection (`segSegT`) misses T-junctions entirely. A 3×2 grid yields only 4 cells instead of 6 if T-junctions aren't split. Always run `pointOnSegT` for every endpoint of every other segment against every segment.
|
|
105
|
-
|
|
106
|
-
### DCEL `next` pointer direction
|
|
107
|
-
|
|
108
|
-
The formula traces faces to the **LEFT** of each directed half-edge. CW winding = unbounded outer face. Keep only faces with positive signed area (CCW winding = bounded interior face).
|
|
@@ -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 [../runbook.md](../runbook.md) for production database access.
|
|
@@ -1,82 +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 Kamal 2.
|
|
4
|
-
|
|
5
|
-
## Stack Overview
|
|
6
|
-
|
|
7
|
-
```
|
|
8
|
-
Internet
|
|
9
|
-
|
|
|
10
|
-
Cloudflare
|
|
11
|
-
|
|
|
12
|
-
kamal-proxy (:80/:443)
|
|
13
|
-
|
|
|
14
|
-
forgecad web container (Fastify / Node.js)
|
|
15
|
-
|--- serves SPA (Vite-built React frontend)
|
|
16
|
-
|--- API routes (/api/*)
|
|
17
|
-
|--- calls forgecad-backend over Docker network
|
|
18
|
-
|
|
|
19
|
-
PostgreSQL accessory
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
**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.
|
|
23
|
-
|
|
24
|
-
**Server:** Fastify with Drizzle ORM for database access. Serves both the static SPA bundle and the `/api/*` JSON endpoints from a single process.
|
|
25
|
-
|
|
26
|
-
**Database:** PostgreSQL for user accounts, project metadata, and application state.
|
|
27
|
-
|
|
28
|
-
**File storage:** User project files are stored on disk (Docker volume `project-data`), not in the database.
|
|
29
|
-
|
|
30
|
-
## Deployment Topology
|
|
31
|
-
|
|
32
|
-
Production uses Kamal deploy configs, not Docker Compose:
|
|
33
|
-
|
|
34
|
-
| File | Purpose |
|
|
35
|
-
|------|---------|
|
|
36
|
-
| `config/deploy.yml` | Kamal config for the ForgeCAD web app |
|
|
37
|
-
| `config/deploy.backend.yml` | Kamal config for the ForgeCAD backend compute service |
|
|
38
|
-
|
|
39
|
-
Docker Compose remains useful for local/self-hosted setups, but it is not the production control plane.
|
|
40
|
-
|
|
41
|
-
## Authentication
|
|
42
|
-
|
|
43
|
-
JWT-based auth with token rotation:
|
|
44
|
-
|
|
45
|
-
- **Access tokens:** 15-minute expiry
|
|
46
|
-
- **Refresh tokens:** 7-day expiry, rotated on use
|
|
47
|
-
|
|
48
|
-
See [auth.md](auth.md) for details on OAuth providers, token flow, and session management.
|
|
49
|
-
|
|
50
|
-
## URL Routes
|
|
51
|
-
|
|
52
|
-
| Route | Access | Purpose |
|
|
53
|
-
|-------|--------|---------|
|
|
54
|
-
| `/` | Public | Landing page |
|
|
55
|
-
| `/docs`, `/docs/:slug` | Public | API documentation |
|
|
56
|
-
| `/blog`, `/blog/:slug` | Public | Announcements and tutorials |
|
|
57
|
-
| `/app` | Auth-gated | The CAD editor |
|
|
58
|
-
| `/app?gist=<id>` | Public preview | Shared model from GitHub Gist (read-only until login) |
|
|
59
|
-
| `/app?url=<url>` | Public preview | Shared model from external URL |
|
|
60
|
-
| `/app#code/<file>/<compressed>` | Public preview | Shared model with inline code |
|
|
61
|
-
| `/app?embed=1` | Public | Embed-only viewport (no chrome, for iframes) |
|
|
62
|
-
| `/m/:shareId` | Public | Published model preview (code + 3D viewport) |
|
|
63
|
-
| `/m/:shareId?embed=1` | Public | Published model embed (viewport only) |
|
|
64
|
-
| `/admin` | Admin role | User management, system health, audit log |
|
|
65
|
-
| `/auth/callback/*` | Public | OAuth callback handlers |
|
|
66
|
-
|
|
67
|
-
## Security
|
|
68
|
-
|
|
69
|
-
- **Rate limiting:** 100 requests/min per IP; 10 auth attempts per 15 minutes
|
|
70
|
-
- **CORS:** Origin whitelist (no wildcard)
|
|
71
|
-
- **CSP:** Content Security Policy headers on all responses
|
|
72
|
-
- **Path traversal:** Server-side validation on all file access paths
|
|
73
|
-
|
|
74
|
-
## Related Documentation
|
|
75
|
-
|
|
76
|
-
- [auth.md](auth.md) -- Authentication and authorization
|
|
77
|
-
- [projects.md](projects.md) -- Project storage and management
|
|
78
|
-
- [sharing.md](sharing.md) -- Model sharing and embeds
|
|
79
|
-
- [admin.md](admin.md) -- Admin panel
|
|
80
|
-
- [email.md](email.md) -- Transactional email
|
|
81
|
-
- [../deployment.md](../deployment.md) -- Environment variables and deployment setup
|
|
82
|
-
- [../runbook.md](../runbook.md) -- Operational procedures and troubleshooting
|
|
@@ -1,139 +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
|
-
## CLI Auth for OAuth Accounts
|
|
54
|
-
|
|
55
|
-
The web app and CLI use different sign-in mechanics:
|
|
56
|
-
|
|
57
|
-
- Browser sign-in uses cookies. Email/password login and OAuth login both end with `httpOnly` cookies that the browser sends back to ForgeCAD.
|
|
58
|
-
- CLI sign-in uses bearer tokens. The CLI cannot reuse a browser-only OAuth cookie, and a Google OAuth redirect flow is awkward for a terminal.
|
|
59
|
-
|
|
60
|
-
That difference is why a Google-only user needs an API token in the CLI: there is no terminal password to submit for a browser-only OAuth account.
|
|
61
|
-
|
|
62
|
-
The normal CLI path is now one interactive command:
|
|
63
|
-
|
|
64
|
-
```bash
|
|
65
|
-
forgecad login
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
Choose **API token** when prompted, then paste a token created from the signed-in web app at **Settings > API Tokens**. The CLI validates the token with `GET /api/auth/session`, stores it in `~/.forgecad/auth.json` as `authType: "token"`, and sends it as `Authorization: Bearer ...` on project, publish, license, and token-management requests.
|
|
69
|
-
|
|
70
|
-
For CI/CD and one-off automation, skip local storage and set:
|
|
71
|
-
|
|
72
|
-
```bash
|
|
73
|
-
FORGECAD_TOKEN=fc_pat_... forgecad project push
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
Passing the token directly as `forgecad login --token fc_pat_...` is accepted for non-interactive setup, but the interactive prompt is preferred so tokens do not land in shell history.
|
|
77
|
-
|
|
78
|
-
For local development or self-hosted experiments, `forgecad login --server <url>` accepts a clean server origin only. Use HTTPS for remote servers; plain HTTP is accepted only for localhost. License activation remains tied to forgecad.io accounts.
|
|
79
|
-
|
|
80
|
-
---
|
|
81
|
-
|
|
82
|
-
## API Endpoints
|
|
83
|
-
|
|
84
|
-
| Method | Route | Purpose |
|
|
85
|
-
|--------|-------|---------|
|
|
86
|
-
| `POST` | `/api/auth/register` | Create account |
|
|
87
|
-
| `POST` | `/api/auth/login` | Login (sets cookies) |
|
|
88
|
-
| `POST` | `/api/auth/refresh` | Rotate refresh token, issue new access token |
|
|
89
|
-
| `POST` | `/api/auth/logout` | Clear session cookies, invalidate refresh token |
|
|
90
|
-
| `GET` | `/api/auth/session` | Return current user info (requires valid access token) |
|
|
91
|
-
| `GET` | `/api/auth/providers` | List available OAuth providers |
|
|
92
|
-
| `POST` | `/api/auth/callback/:provider` | OAuth callback |
|
|
93
|
-
| `POST` | `/api/auth/forgot-password` | Request password reset email |
|
|
94
|
-
| `POST` | `/api/auth/reset-password` | Apply password reset |
|
|
95
|
-
| `GET` | `/api/auth/verify-email` | Confirm email address |
|
|
96
|
-
| `POST` | `/api/auth/resend-verification` | Resend verification email |
|
|
97
|
-
|
|
98
|
-
---
|
|
99
|
-
|
|
100
|
-
## Rate Limiting
|
|
101
|
-
|
|
102
|
-
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.
|
|
103
|
-
|
|
104
|
-
---
|
|
105
|
-
|
|
106
|
-
## Environment Variables
|
|
107
|
-
|
|
108
|
-
| Variable | Required | Default | Purpose |
|
|
109
|
-
|----------|----------|---------|---------|
|
|
110
|
-
| `FORGE_JWT_SECRET` | Yes | -- | HMAC signing key for JWT access tokens |
|
|
111
|
-
| `FORGE_JWT_ACCESS_EXPIRES` | No | `15m` | Access token lifetime (e.g. `15m`, `1h`) |
|
|
112
|
-
| `FORGE_JWT_REFRESH_EXPIRES` | No | `7d` | Refresh token lifetime |
|
|
113
|
-
| `GITHUB_CLIENT_ID` | No | -- | GitHub OAuth application ID |
|
|
114
|
-
| `GITHUB_CLIENT_SECRET` | No | -- | GitHub OAuth secret |
|
|
115
|
-
| `GOOGLE_CLIENT_ID` | No | -- | Google OAuth client ID |
|
|
116
|
-
| `GOOGLE_CLIENT_SECRET` | No | -- | Google OAuth secret |
|
|
117
|
-
| `FORGE_ALLOWED_ORIGINS` | No | `localhost` | Comma-separated list of allowed CORS origins |
|
|
118
|
-
|
|
119
|
-
See [deployment.md](../deployment.md) for the full environment variable reference.
|
|
120
|
-
|
|
121
|
-
---
|
|
122
|
-
|
|
123
|
-
## Troubleshooting
|
|
124
|
-
|
|
125
|
-
**500 on login** -- `FORGE_JWT_SECRET` is not set. The server cannot sign tokens without it.
|
|
126
|
-
|
|
127
|
-
**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.
|
|
128
|
-
|
|
129
|
-
**CORS errors** -- `FORGE_ALLOWED_ORIGINS` does not include the frontend's origin. Add the correct domain (e.g. `https://forgecad.io`).
|
|
130
|
-
|
|
131
|
-
---
|
|
132
|
-
|
|
133
|
-
## Source Files
|
|
134
|
-
|
|
135
|
-
| File | Purpose |
|
|
136
|
-
|------|---------|
|
|
137
|
-
| `server/routes/auth.ts` | All auth route handlers |
|
|
138
|
-
| `server/middleware/auth.ts` | `requireAuth` and `requireAdmin` middleware |
|
|
139
|
-
| `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 [../deployment.md](../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 production secrets (`.kamal/secrets`) 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,88 +0,0 @@
|
|
|
1
|
-
# Google OAuth Setup Guide
|
|
2
|
-
|
|
3
|
-
Step-by-step guide to configure Google Sign-In for ForgeCAD.
|
|
4
|
-
|
|
5
|
-
## Prerequisites
|
|
6
|
-
|
|
7
|
-
- A Google account
|
|
8
|
-
- Access to [Google Cloud Console](https://console.cloud.google.com)
|
|
9
|
-
|
|
10
|
-
## 1. Create a Google Cloud Project
|
|
11
|
-
|
|
12
|
-
1. Go to [console.cloud.google.com](https://console.cloud.google.com)
|
|
13
|
-
2. Click the project dropdown at the top left (might say "Select a project")
|
|
14
|
-
3. Click **New Project**
|
|
15
|
-
4. Name it **ForgeCAD**, click **Create**
|
|
16
|
-
5. Make sure it's selected in the project dropdown
|
|
17
|
-
|
|
18
|
-
## 2. Configure the OAuth Consent Screen
|
|
19
|
-
|
|
20
|
-
This is what users see when they click "Sign in with Google."
|
|
21
|
-
|
|
22
|
-
1. In the left sidebar: **APIs & Services → OAuth consent screen**
|
|
23
|
-
2. Click **Get started** (or **Configure consent screen**)
|
|
24
|
-
3. Choose **External** (anyone with a Google account can sign in) → **Create**
|
|
25
|
-
4. Fill in:
|
|
26
|
-
- **App name**: `ForgeCAD`
|
|
27
|
-
- **User support email**: your email
|
|
28
|
-
- **Developer contact email**: your email
|
|
29
|
-
- Logo is optional (can add later)
|
|
30
|
-
5. Click **Save and Continue**
|
|
31
|
-
6. **Scopes** screen → click **Add or Remove Scopes**
|
|
32
|
-
- Add these three:
|
|
33
|
-
- `openid`
|
|
34
|
-
- `email` (or `.../auth/userinfo.email`)
|
|
35
|
-
- `profile` (or `.../auth/userinfo.profile`)
|
|
36
|
-
- Click **Update** → **Save and Continue**
|
|
37
|
-
7. **Test users** → skip for now (only matters while in "Testing" status) → **Save and Continue**
|
|
38
|
-
8. Review and click **Back to Dashboard**
|
|
39
|
-
|
|
40
|
-
## 3. Create OAuth Credentials
|
|
41
|
-
|
|
42
|
-
Use the Google Auth Platform / Credentials page for ForgeCAD web sign-in.
|
|
43
|
-
The `gcloud iam oauth-clients` command is for IAM/IAP OAuth application
|
|
44
|
-
resources and is not the right client type for this app's Google Sign-In flow.
|
|
45
|
-
|
|
46
|
-
1. In the left sidebar: **APIs & Services → Credentials**
|
|
47
|
-
2. Click **+ Create Credentials → OAuth client ID**
|
|
48
|
-
3. **Application type**: Web application
|
|
49
|
-
4. **Name**: `ForgeCAD Web` (just a label for you)
|
|
50
|
-
5. **Authorized JavaScript origins** — add your domain(s):
|
|
51
|
-
- `https://forgecad.io` (production)
|
|
52
|
-
- `http://localhost:5173` (local dev)
|
|
53
|
-
6. **Authorized redirect URIs** — must match exactly:
|
|
54
|
-
- `https://forgecad.io/auth/callback/google`
|
|
55
|
-
- `http://localhost:5173/auth/callback/google` (local dev)
|
|
56
|
-
7. Click **Create**
|
|
57
|
-
8. Copy the **Client ID** and **Client Secret** from the dialog
|
|
58
|
-
|
|
59
|
-
## 4. Add Credentials to `.env`
|
|
60
|
-
|
|
61
|
-
On your server (or local `.env`):
|
|
62
|
-
|
|
63
|
-
```bash
|
|
64
|
-
GOOGLE_CLIENT_ID=123456789-xxxxxxx.apps.googleusercontent.com
|
|
65
|
-
GOOGLE_CLIENT_SECRET=GOCSPX-xxxxxxxxxxxxxxxxx
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
## 5. Publish the App
|
|
69
|
-
|
|
70
|
-
While the consent screen is in **Testing** mode, only explicitly added test users can sign in.
|
|
71
|
-
|
|
72
|
-
1. Go to **OAuth consent screen**
|
|
73
|
-
2. Click **Publish App**
|
|
74
|
-
3. For apps requesting only `email`, `profile`, and `openid` scopes, Google does not require a full verification review — just confirm the prompt.
|
|
75
|
-
|
|
76
|
-
## 6. Restart and Test
|
|
77
|
-
|
|
78
|
-
Restart the server so it picks up the new env vars, then click the Google button on the login page.
|
|
79
|
-
|
|
80
|
-
## Troubleshooting
|
|
81
|
-
|
|
82
|
-
| Problem | Cause | Fix |
|
|
83
|
-
|---------|-------|-----|
|
|
84
|
-
| `redirect_uri_mismatch` error | Redirect URI in Google Console doesn't exactly match the one your app sends | Check scheme (`http` vs `https`), domain, port, path, and trailing slashes |
|
|
85
|
-
| "Access blocked: This app's request is invalid" | Consent screen not configured or missing scopes | Complete the OAuth consent screen setup (step 2) |
|
|
86
|
-
| "This app isn't verified" warning | App is published but not verified by Google | Normal for `openid`/`email`/`profile` scopes — users can click "Advanced → Go to ForgeCAD" to proceed |
|
|
87
|
-
| Google button doesn't appear on login page | `GOOGLE_CLIENT_ID` env var not set | Add it to `.env` and restart the server |
|
|
88
|
-
| Sign-in works but no email returned | Missing `email` scope | Re-check scopes in consent screen (step 2.6) |
|