diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..fdacf1586 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,28 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + quality: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: oven-sh/setup-bun@v2 + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Lint & format check + run: bun run check + + - name: Type check + run: bun run check-types diff --git a/apps/editor/lib/bootstrap.ts b/apps/editor/lib/bootstrap.ts index 0186914e5..10788771a 100644 --- a/apps/editor/lib/bootstrap.ts +++ b/apps/editor/lib/bootstrap.ts @@ -45,7 +45,6 @@ function loadBuiltinsSync(): void { if (isDev()) { const kinds = Array.from(nodeRegistry.entries(), ([k]) => k) if (typeof console !== 'undefined') { - // biome-ignore lint/suspicious/noConsole: dev-only verification log console.info( `[pascal:registry] loaded ${builtinPlugin.id} v${builtinPlugin.apiVersion} (${kinds.length} kinds: ${kinds.join(', ') || '∅'})`, ) @@ -74,7 +73,6 @@ export async function loadExternalPlugins(): Promise { await loadPlugin(plugin) } if (isDev() && externals.length > 0 && typeof console !== 'undefined') { - // biome-ignore lint/suspicious/noConsole: dev-only verification log console.info(`[pascal:registry] + ${externals.length} discovered plugin(s)`) } } diff --git a/biome.jsonc b/biome.jsonc index 9b347988e..9edc891f0 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -110,7 +110,8 @@ "!**/build", "!**/public", "!**/components/ui", - "!**/next-env.d.ts" + "!**/next-env.d.ts", + "!packages/mcp" ] }, "overrides": [ diff --git a/packages/core/src/store/actions/node-actions.ts b/packages/core/src/store/actions/node-actions.ts index 2c5768a36..ead76ad5f 100644 --- a/packages/core/src/store/actions/node-actions.ts +++ b/packages/core/src/store/actions/node-actions.ts @@ -416,7 +416,9 @@ export const applyNodeChangesAction = ( return { nodes: nextNodes, rootNodeIds: resolvedRootIds, collections: nextCollections } }) - nodesToMarkDirty.forEach((id) => get().markDirty(id)) + nodesToMarkDirty.forEach((id) => { + get().markDirty(id) + }) parentsToMarkDirty.forEach((id) => { get().markDirty(id) const parent = get().nodes[id] diff --git a/packages/editor/src/components/editor/first-person/build-collider-world.ts b/packages/editor/src/components/editor/first-person/build-collider-world.ts index 4366ffbf2..363735da4 100644 --- a/packages/editor/src/components/editor/first-person/build-collider-world.ts +++ b/packages/editor/src/components/editor/first-person/build-collider-world.ts @@ -294,7 +294,9 @@ export function buildFirstPersonColliderWorldFromRegistry(): FirstPersonCollider } const mergedGeometry = mergeGeometries(geometries, false) - geometries.forEach((geometry) => geometry.dispose()) + geometries.forEach((geometry) => { + geometry.dispose() + }) if (!mergedGeometry || mergedGeometry.getAttribute('position') == null) { mergedGeometry?.dispose() diff --git a/packages/nodes/src/ceiling/tool.tsx b/packages/nodes/src/ceiling/tool.tsx index 57f693490..1a889dfc4 100644 --- a/packages/nodes/src/ceiling/tool.tsx +++ b/packages/nodes/src/ceiling/tool.tsx @@ -192,8 +192,12 @@ export const CeilingTool: React.FC = () => { if (points.length === 0) { mainLineRef.current.visible = false closingLineRef.current.visible = false - groundMainLineRef.current && (groundMainLineRef.current.visible = false) - groundClosingLineRef.current && (groundClosingLineRef.current.visible = false) + if (groundMainLineRef.current) { + groundMainLineRef.current.visible = false + } + if (groundClosingLineRef.current) { + groundClosingLineRef.current.visible = false + } return } const ceilingY = levelY + CEILING_HEIGHT diff --git a/packages/nodes/src/chimney/panel.tsx b/packages/nodes/src/chimney/panel.tsx index ab2ddc9fd..7eb189c81 100644 --- a/packages/nodes/src/chimney/panel.tsx +++ b/packages/nodes/src/chimney/panel.tsx @@ -133,6 +133,21 @@ export default function ChimneyPanel() { } }, [selectedId, node, deleteNode, setSelection]) + // Match the current store node against the preset table so the + // segmented control highlights "the preset you'd land on if you + // applied X again". Compare against the store node, not the live- + // override-merged `node`, so the highlight is stable across slider + // drags. Null means the user has tweaked away from any preset; the + // segmented control will then render with no segment selected. + const activePreset = useMemo(() => detectActiveChimneyPreset(storeNode), [storeNode]) + const applyPreset = useCallback( + (key: ChimneyPresetKey) => { + commitProp(chimneyPresets[key] as Partial) + triggerSFX('sfx:item-pick') + }, + [commitProp], + ) + if (!(node && node.type === 'chimney' && selectedId)) return null const scenestate = useScene.getState() @@ -321,21 +336,6 @@ export default function ChimneyPanel() { commitProp({ rotation: newWorldRot - ancestorWorldY }) } - // Match the current store node against the preset table so the - // segmented control highlights "the preset you'd land on if you - // applied X again". Compare against the store node, not the live- - // override-merged `node`, so the highlight is stable across slider - // drags. Null means the user has tweaked away from any preset; the - // segmented control will then render with no segment selected. - const activePreset = useMemo(() => detectActiveChimneyPreset(storeNode), [storeNode]) - const applyPreset = useCallback( - (key: ChimneyPresetKey) => { - commitProp(chimneyPresets[key] as Partial) - triggerSFX('sfx:item-pick') - }, - [commitProp], - ) - return ( { }) } } - // biome-ignore lint/correctness/useExhaustiveDependencies: capture-on-mount; meta is intentionally not re-read on changes. }, [node.id, isNew]) const { activeBuildingId, segmentXform, hitLocal, ghostRotation } = useDormerPlacement({ diff --git a/packages/nodes/src/dormer/panel-position-section.tsx b/packages/nodes/src/dormer/panel-position-section.tsx index c36e06291..2a4fefe32 100644 --- a/packages/nodes/src/dormer/panel-position-section.tsx +++ b/packages/nodes/src/dormer/panel-position-section.tsx @@ -79,7 +79,6 @@ export function DormerPositionSection({ if (Number.isFinite(lo_x)) bounds = { minX: lo_x, maxX: hi_x, minZ: lo_z, maxZ: hi_z } } return { worldX, worldZ, worldRotation, bounds } - // biome-ignore lint/correctness/useExhaustiveDependencies: roofChildrenKey is the stable signature of `roof.children`; intentionally omitting `roof` (object identity) in favor of the joined ids. }, [selectedId, px, py, pz, nodeRotation, segmentId, roofChildrenKey]) const worldX_now = worldXform.worldX diff --git a/packages/nodes/src/dormer/panel.tsx b/packages/nodes/src/dormer/panel.tsx index be6d1676f..7eff4733b 100644 --- a/packages/nodes/src/dormer/panel.tsx +++ b/packages/nodes/src/dormer/panel.tsx @@ -107,7 +107,7 @@ export default function DormerPanel() { }, [node, selectedId, setMovingNode, setSelection]) const handleDuplicate = useCallback(() => { - if (!(node && node.roofSegmentId)) return + if (!node?.roofSegmentId) return triggerSFX('sfx:item-pick') // Deep clone and strip the id so the move tool's onClick branch // (`isNew || !node.id`) takes the "create fresh" path. Setting diff --git a/packages/nodes/src/dormer/window-assembly.tsx b/packages/nodes/src/dormer/window-assembly.tsx index 12ca12a6e..0fa016474 100644 --- a/packages/nodes/src/dormer/window-assembly.tsx +++ b/packages/nodes/src/dormer/window-assembly.tsx @@ -144,7 +144,6 @@ const DormerWindowAssembly = ({ {winGeo.glassPanes.map((pane, i) => ( { const ref = useRef(null!) const n = node as RenderableNode - // biome-ignore lint/suspicious/noExplicitAny: useNodeEvents is keyed by - // literal kind; the registry path passes a runtime kind union. Routing - // through the type cast is safer than widening the hook signature. const handlers = useNodeEvents(node as any, node.type as any) const liveTransform = useLiveTransforms((s) => s.get(node.id as AnyNodeId)) // Registry arrow handles (rotation gizmo, position-affecting patches) diff --git a/packages/viewer/src/r3f.d.ts b/packages/viewer/src/r3f.d.ts index 8e0796cfd..172e7de50 100644 --- a/packages/viewer/src/r3f.d.ts +++ b/packages/viewer/src/r3f.d.ts @@ -78,21 +78,18 @@ interface ThreeJSXElements { } declare module 'react' { - // biome-ignore lint/style/noNamespace: Required for JSX module augmentation namespace JSX { interface IntrinsicElements extends ThreeJSXElements {} } } declare module 'react/jsx-runtime' { - // biome-ignore lint/style/noNamespace: Required for JSX module augmentation namespace JSX { interface IntrinsicElements extends ThreeJSXElements {} } } declare module 'react/jsx-dev-runtime' { - // biome-ignore lint/style/noNamespace: Required for JSX module augmentation namespace JSX { interface IntrinsicElements extends ThreeJSXElements {} } diff --git a/turbo.json b/turbo.json index 256dbb71a..dd97f6521 100644 --- a/turbo.json +++ b/turbo.json @@ -23,7 +23,7 @@ "dependsOn": ["^lint"] }, "check-types": { - "dependsOn": ["^check-types"] + "dependsOn": ["^build", "^check-types"] }, "dev": { "dependsOn": ["^build"],