Reset core/viewer/editor/mcp packages and apps/editor from private-editor

Wholesale swap of packages/{core,viewer,editor,mcp} and apps/editor with the
versions from the private editor repo, which is the production source of truth.

Setup changes:
- packages/{core,viewer,editor} versions held at 0.7.0 baseline (matching
  the most recent published release) so a bump=minor publishes 0.8.0
- packages/mcp held at 0.1.1 (never published; first publish will go through
  the new release.yml flow)
- peerDependencies and devDependencies for inter-package @pascal-app/*
  references pinned to ^0.7.0 instead of '*' / 'workspace:*' so they are
  valid for npm consumers
- Root package.json: TypeScript bumped to 6.0.2, added overrides for
  @types/react, @types/react-dom, @types/three to prevent JSX namespace
  fragmentation across the workspace
- release.yml extended to also publish editor and mcp; 'both' option renamed
  to 'all'; added a sync step that updates inter-package peerDeps/devDeps to
  match the new versions on every bump (so viewer/editor/mcp tarballs always
  reference the version of core they were built against)
- Root scripts gained release:editor and release:mcp shortcuts

Verification:
- bun install --frozen-lockfile is consistent
- packages/{core,viewer,mcp} build cleanly, dist/index.d.ts emitted
- packages/editor check-types reports 21 pre-existing errors, identical to
  what private-editor currently reports

Open PRs against editor-v2 will need rebasing/conflict resolution.
This commit is contained in:
Wawa
2026-05-09 20:52:26 +00:00
parent 146f0a846f
commit e618175bba
196 changed files with 5899 additions and 3966 deletions
@@ -1,5 +1,3 @@
// @ts-expect-error — bun:test is provided by the Bun runtime; core does not
// depend on @types/bun so the import type is unresolved at compile time.
import { describe, expect, test } from 'bun:test'
import type { AnyNode } from '../../schema'
import { BuildingNode, LevelNode, SlabNode, StairNode, StairSegmentNode } from '../../schema'
@@ -1,14 +1,12 @@
import { resolveLevelId } from '../../hooks/spatial-grid/spatial-grid-sync'
import type {
AnyNode,
AnyNodeId,
CeilingNode,
LevelNode,
SlabNode,
StairNode,
StairSegmentNode,
} from '../../schema'
import { resolveLevelId } from '../../hooks/spatial-grid/spatial-grid-sync'
import { DEFAULT_WALL_HEIGHT } from '../wall/wall-footprint'
type Point2D = [number, number]
@@ -36,10 +34,9 @@ type AxisAlignedRect = {
maxZ: number
}
const CURVED_STAIR_SLAB_OPENING_RATIO = 0.9
const CURVED_STAIR_SLAB_OPENING_RATIO = 0.8
const STRAIGHT_STAIR_TARGET_THRESHOLD_MIN = 0.35
const STAIR_SLAB_OPENING_TIGHTENING = 0
const CURVED_STAIR_OPENING_STEP_PADDING = 3
function clamp(value: number, min: number, max: number) {
return Math.min(max, Math.max(min, value))
@@ -280,7 +277,7 @@ function polygonArea(points: Point2D[]) {
for (let index = 0; index < points.length; index += 1) {
const current = points[index]
const next = points[(index + 1) % points.length]
if (!current || !next) continue
if (!(current && next)) continue
area += current[0] * next[1] - next[0] * current[1]
}
return area / 2
@@ -426,39 +423,24 @@ function buildUnionPolygonsFromRects(rects: AxisAlignedRect[]): Point2D[][] {
return polygons
}
function getCurvedOpeningStepCount(
stair: StairNode,
innerRadius: number,
outerRadius: number,
totalSweep: number,
) {
const stepCount = Math.max(2, Math.round(stair.stepCount ?? 10))
const stepSweep = Math.abs(totalSweep) / stepCount
const midRadius = Math.max((innerRadius + outerRadius) * 0.5, 0.01)
const treadDepth = Math.max(stepSweep * midRadius, 0.2)
return Math.min(
stepCount,
function getCurvedOpeningPolygon(stair: StairNode): Point2D[] {
const width = Math.max(stair.width ?? 1, 0.4)
const innerRadius = Math.max(0.2, stair.innerRadius ?? 0.9)
const outerRadius = innerRadius + width
const totalSweep = stair.sweepAngle ?? Math.PI / 2
const openingSweep =
Math.sign(totalSweep || 1) *
Math.max(
1,
Math.ceil(1.8 / treadDepth),
Math.ceil(stepCount * CURVED_STAIR_SLAB_OPENING_RATIO),
),
)
}
function buildArcOpeningPolygon(
stair: StairNode,
innerRadius: number,
outerRadius: number,
startAngle: number,
endAngle: number,
): Point2D[] {
const sweep = endAngle - startAngle
Math.abs(totalSweep) * CURVED_STAIR_SLAB_OPENING_RATIO,
Math.abs(totalSweep) / Math.max(stair.stepCount ?? 1, 1),
)
const startAngle = totalSweep / 2 - openingSweep
const endAngle = totalSweep / 2
const segmentCount = Math.max(
10,
Math.min(
32,
Math.ceil(Math.abs(sweep) / (Math.PI / 24) + Math.max(stair.stepCount ?? 1, 1) * 0.5),
Math.ceil(Math.abs(openingSweep) / (Math.PI / 24) + Math.max(stair.stepCount ?? 1, 1) * 0.5),
),
)
const outerPoints: Point2D[] = []
@@ -466,7 +448,7 @@ function buildArcOpeningPolygon(
for (let index = 0; index <= segmentCount; index++) {
const t = index / segmentCount
const angle = startAngle + sweep * t
const angle = startAngle + (endAngle - startAngle) * t
outerPoints.push(
toWorldPlanPoint(stair, Math.cos(angle) * outerRadius, Math.sin(angle) * outerRadius),
)
@@ -474,8 +456,7 @@ function buildArcOpeningPolygon(
for (let index = segmentCount; index >= 0; index--) {
const t = index / segmentCount
const angle = startAngle + sweep * t
const angle = startAngle + (endAngle - startAngle) * t
innerPoints.push(
toWorldPlanPoint(stair, Math.cos(angle) * innerRadius, Math.sin(angle) * innerRadius),
)
@@ -484,39 +465,6 @@ function buildArcOpeningPolygon(
return [...outerPoints, ...innerPoints]
}
function getCurvedOpeningPolygon(stair: StairNode, targetElevation?: number): Point2D[] {
const width = Math.max(stair.width ?? 1, 0.4)
const innerRadius = Math.max(0.2, stair.innerRadius ?? 0.9)
const outerRadius = innerRadius + width
const totalSweep = stair.sweepAngle ?? Math.PI / 2
const stepCount = Math.max(2, Math.round(stair.stepCount ?? 10))
const stepHeight = Math.max(stair.totalRise ?? 2.5, 0.1) / stepCount
const stepSweep = totalSweep / stepCount
const targetThreshold = Math.max(stepHeight * 2, STRAIGHT_STAIR_TARGET_THRESHOLD_MIN)
const endAngle = totalSweep / 2
const fallbackStartStepIndex = Math.max(
0,
stepCount - getCurvedOpeningStepCount(stair, innerRadius, outerRadius, totalSweep),
)
let startStepIndex = fallbackStartStepIndex
if (typeof targetElevation === 'number') {
for (let index = 0; index < stepCount; index += 1) {
const stepTopElevation = stepHeight * (index + 1)
if (stepTopElevation >= targetElevation - targetThreshold) {
startStepIndex = Math.max(
0,
Math.min(fallbackStartStepIndex, index - CURVED_STAIR_OPENING_STEP_PADDING),
)
break
}
}
}
const startAngle = -totalSweep / 2 + stepSweep * startStepIndex
return buildArcOpeningPolygon(stair, innerRadius, outerRadius, startAngle, endAngle)
}
function getSpiralOpeningPolygon(stair: StairNode): Point2D[] {
const radius = Math.max(0.05, stair.innerRadius ?? 0.9) + Math.max(stair.width ?? 1, 0.4)
const segmentCount = 48
@@ -621,7 +569,7 @@ function getStairOpeningPolygons(
}
if (stair.stairType === 'curved') {
return [getCurvedOpeningPolygon(stair, targetElevation)]
return [getCurvedOpeningPolygon(stair)]
}
if (stair.stairType === 'spiral') {
@@ -775,8 +723,7 @@ export function syncAutoStairOpenings(nodes: Record<string, AnyNode>) {
const nextMetadata = [...manualMetadata, ...stairHoles.map((hole) => hole.metadata)]
if (
!polygonsEqual(existingHoles, nextHoles) ||
!metadataEqual(existingMetadata, nextMetadata)
!(polygonsEqual(existingHoles, nextHoles) && metadataEqual(existingMetadata, nextMetadata))
) {
updates.push({
id: slab.id,
@@ -826,8 +773,7 @@ export function syncAutoStairOpenings(nodes: Record<string, AnyNode>) {
const nextMetadata = [...manualMetadata, ...stairHoles.map((hole) => hole.metadata)]
if (
!polygonsEqual(existingHoles, nextHoles) ||
!metadataEqual(existingMetadata, nextMetadata)
!(polygonsEqual(existingHoles, nextHoles) && metadataEqual(existingMetadata, nextMetadata))
) {
updates.push({
id: ceiling.id,
+6 -3
View File
@@ -1,5 +1,5 @@
import type { Point2D } from './wall-mitering'
import type { FenceNode, WallNode } from '../../schema'
import type { Point2D } from './wall-mitering'
const CURVE_EPSILON = 1e-6
const DEFAULT_SAMPLE_SEGMENTS = 24
@@ -115,7 +115,7 @@ function getWallArcData(wall: WallCurveLike) {
}
const absSagitta = Math.abs(sagitta)
const radius = chord.length * chord.length / (8 * absSagitta) + absSagitta / 2
const radius = (chord.length * chord.length) / (8 * absSagitta) + absSagitta / 2
const centerOffset = radius - absSagitta
const direction = Math.sign(sagitta) || 1
const center = {
@@ -183,7 +183,10 @@ export function getWallMidpointHandlePoint(wall: WallCurveLike) {
export function sampleWallCenterline(wall: WallCurveLike, segments = DEFAULT_SAMPLE_SEGMENTS) {
const count = Math.max(1, segments)
return Array.from({ length: count + 1 }, (_, index) => getWallCurveFrameAt(wall, index / count).point)
return Array.from(
{ length: count + 1 },
(_, index) => getWallCurveFrameAt(wall, index / count).point,
)
}
export function getWallCurveLength(wall: WallCurveLike, segments = DEFAULT_SAMPLE_SEGMENTS) {
@@ -135,10 +135,7 @@ function findJunctions(walls: WallNode[]): Map<string, Junction> {
return actualJunctions
}
function getWallDirectionFromJunction(
wall: WallNode,
endType: 'start' | 'end' | 'passthrough',
) {
function getWallDirectionFromJunction(wall: WallNode, endType: 'start' | 'end' | 'passthrough') {
if (endType === 'passthrough') {
return {
x: wall.end[0] - wall.start[0],
@@ -148,9 +145,7 @@ function getWallDirectionFromJunction(
if (isCurvedWall(wall)) {
const frame = getWallCurveFrameAt(wall, endType === 'start' ? 0 : 1)
return endType === 'start'
? frame.tangent
: { x: -frame.tangent.x, y: -frame.tangent.y }
return endType === 'start' ? frame.tangent : { x: -frame.tangent.x, y: -frame.tangent.y }
}
return endType === 'start'
@@ -158,18 +153,12 @@ function getWallDirectionFromJunction(
: { x: wall.start[0] - wall.end[0], y: wall.start[1] - wall.end[1] }
}
function getWallBoundaryFrame(
wall: WallNode,
endType: 'start' | 'end',
) {
function getWallBoundaryFrame(wall: WallNode, endType: 'start' | 'end') {
if (isCurvedWall(wall)) {
const frame = getWallCurveFrameAt(wall, endType === 'start' ? 0 : 1)
return {
point: frame.point,
tangent:
endType === 'start'
? frame.tangent
: { x: -frame.tangent.x, y: -frame.tangent.y },
tangent: endType === 'start' ? frame.tangent : { x: -frame.tangent.x, y: -frame.tangent.y },
normal: frame.normal,
}
}