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/packages/core/src/store/actions/node-actions.ts b/packages/core/src/store/actions/node-actions.ts index 2c5768a36..e3342cf3c 100644 --- a/packages/core/src/store/actions/node-actions.ts +++ b/packages/core/src/store/actions/node-actions.ts @@ -416,8 +416,10 @@ export const applyNodeChangesAction = ( return { nodes: nextNodes, rootNodeIds: resolvedRootIds, collections: nextCollections } }) - nodesToMarkDirty.forEach((id) => get().markDirty(id)) - parentsToMarkDirty.forEach((id) => { + for (const id of nodesToMarkDirty) { + get().markDirty(id) + } + for (const id of parentsToMarkDirty) { get().markDirty(id) const parent = get().nodes[id] if (parent && 'children' in parent && Array.isArray(parent.children)) { @@ -425,7 +427,7 @@ export const applyNodeChangesAction = ( get().markDirty(childId as AnyNodeId) } } - }) + } } export const updateNodesAction = ( 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..ff039020d 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()) + for (const geometry of geometries) { + 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..b15343f42 100644 --- a/packages/nodes/src/ceiling/tool.tsx +++ b/packages/nodes/src/ceiling/tool.tsx @@ -192,8 +192,8 @@ 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..2f8e7a3f5 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() @@ -322,20 +337,6 @@ export default function ChimneyPanel() { } // 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 ( { 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"],