From 6a4de8cff56e0bb3268a77512eb9eee83576a54d Mon Sep 17 00:00:00 2001 From: Wassim SAMAD Date: Fri, 15 May 2026 11:59:09 -0400 Subject: [PATCH] Drop wall + fence feature flags: register unconditionally; remove verification logs Parity comparison against deployed prod is now cleaner than juggling env-var flag toggles locally. Both kinds enter builtinPlugin.nodes unconditionally; the Phase 0 dispatch shims ( wrappers + NodeRenderer's registry-first branch) handle the cutover. Files deleted: - packages/nodes/src/wall/feature-flag.ts - packages/nodes/src/fence/feature-flag.ts Files changed: - packages/nodes/src/index.ts: drops isWallRegistryEnabled / isFenceRegistryEnabled gates; wallDefinition + fenceDefinition land directly in builtinPlugin.nodes. - packages/nodes/src/{wall,fence}/index.ts: drop the flag re-export. - packages/nodes/src/{wall,fence}/renderer.tsx: drop the one-shot verification console.info. Same for the system.tsx wrappers. - packages/viewer/src/components/renderers/{wall,fence}/{wall,fence}- renderer.tsx: drop the paired [X:legacy] verification logs (no longer comparing flag-toggled paths). Net DX: no env var to remember when starting `bun dev:community`. To A/B test, compare against editor.pascal.app deployed prod. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/nodes/src/fence/feature-flag.ts | 13 ------- packages/nodes/src/fence/index.ts | 1 - packages/nodes/src/fence/renderer.tsx | 12 +------ packages/nodes/src/fence/system.tsx | 11 +----- packages/nodes/src/index.ts | 34 ++++++------------- packages/nodes/src/wall/feature-flag.ts | 23 ------------- packages/nodes/src/wall/index.ts | 1 - packages/nodes/src/wall/renderer.tsx | 17 +--------- packages/nodes/src/wall/system.tsx | 25 ++++---------- .../renderers/fence/fence-renderer.tsx | 16 +-------- .../renderers/wall/wall-renderer.tsx | 14 -------- 11 files changed, 20 insertions(+), 147 deletions(-) delete mode 100644 packages/nodes/src/fence/feature-flag.ts delete mode 100644 packages/nodes/src/wall/feature-flag.ts diff --git a/packages/nodes/src/fence/feature-flag.ts b/packages/nodes/src/fence/feature-flag.ts deleted file mode 100644 index 74123a65..00000000 --- a/packages/nodes/src/fence/feature-flag.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Feature flag for the registry-driven fence. - * - * Same pattern as wall (Phase 3) and spawn (Phase 2): with the flag on, - * `fenceDefinition` is appended to `builtinPlugin.nodes` and the Phase 0 - * dispatch shims hand fence over to the registry. With the flag off, - * the legacy fence paths run unchanged. - * - * Drops the moment Phase 5 fence parity is signed off. - */ -export const isFenceRegistryEnabled = (): boolean => { - return process.env.NEXT_PUBLIC_USE_REGISTRY_FOR_FENCE === 'true' -} diff --git a/packages/nodes/src/fence/index.ts b/packages/nodes/src/fence/index.ts index d79345fe..4a7262a7 100644 --- a/packages/nodes/src/fence/index.ts +++ b/packages/nodes/src/fence/index.ts @@ -1,3 +1,2 @@ export { fenceDefinition } from './definition' -export { isFenceRegistryEnabled } from './feature-flag' export { FenceNode } from './schema' diff --git a/packages/nodes/src/fence/renderer.tsx b/packages/nodes/src/fence/renderer.tsx index 7503030b..64116314 100644 --- a/packages/nodes/src/fence/renderer.tsx +++ b/packages/nodes/src/fence/renderer.tsx @@ -2,7 +2,7 @@ import { type FenceNode, useRegistry, useScene } from '@pascal-app/core' import { DEFAULT_STAIR_MATERIAL, useNodeEvents } from '@pascal-app/viewer' -import { useEffect, useLayoutEffect, useMemo, useRef } from 'react' +import { useLayoutEffect, useMemo, useRef } from 'react' import type { Mesh } from 'three' /** @@ -17,8 +17,6 @@ import type { Mesh } from 'three' * Material is `DEFAULT_STAIR_MATERIAL` (legacy reuse; fence and stairs * share the wood-tone preset). */ -let didLogFirstRegistryFenceMount = false - const FenceRenderer = ({ node }: { node: FenceNode }) => { const ref = useRef(null!) const handlers = useNodeEvents(node, 'fence') @@ -29,14 +27,6 @@ const FenceRenderer = ({ node }: { node: FenceNode }) => { useScene.getState().markDirty(node.id) }, [node.id]) - useEffect(() => { - if (didLogFirstRegistryFenceMount) return - didLogFirstRegistryFenceMount = true - console.info( - '[fence:registry] first registry-driven FenceRenderer mounted — legacy FenceRenderer is NOT in use', - ) - }, []) - return ( ` wrapper around `` in * `viewer/components/viewer/index.tsx` short-circuits whenever - * `nodeRegistry.has('fence')` is true — same pattern wall used in - * milestone B. + * `nodeRegistry.has('fence')` is true — same pattern wall uses. * * Phase 6 deletes the legacy mount point; until then this bundle is the * single mount surface for fence's per-frame work when registry-driven. @@ -22,13 +20,6 @@ import { useEffect } from 'react' * the rebuild loop and this bundle can be deleted. */ const FenceSystems = () => { - useEffect(() => { - console.info('[fence:registry] system bundle mounted — registry path active') - return () => { - console.info('[fence:registry] system bundle unmounted') - } - }, []) - return } diff --git a/packages/nodes/src/index.ts b/packages/nodes/src/index.ts index cfd50e62..9bf989d2 100644 --- a/packages/nodes/src/index.ts +++ b/packages/nodes/src/index.ts @@ -1,8 +1,8 @@ import type { AnyNodeDefinition, Plugin } from '@pascal-app/core' -import { fenceDefinition, isFenceRegistryEnabled } from './fence' +import { fenceDefinition } from './fence' import { shelfDefinition } from './shelf' import { spawnDefinition } from './spawn' -import { isWallRegistryEnabled, wallDefinition } from './wall' +import { wallDefinition } from './wall' /** * Built-in plugin bundling every node kind shipped with the Pascal editor. @@ -15,35 +15,21 @@ import { isWallRegistryEnabled, wallDefinition } from './wall' * `loadPlugin` call path. This is intentional: the API is stress-tested * by built-ins before any third-party plugin lands. * - * Status by kind: - * - **shelf**: brand-new kind, registry-driven, no legacy. Registered - * unconditionally. - * - **spawn**: migrated to the registry path during Phase 2. Legacy - * SpawnRenderer / SpawnTool files still present in viewer/editor but - * short-circuited by the Phase 0 shims. Registered unconditionally. - * - **wall**: registry-driven behind `NEXT_PUBLIC_USE_REGISTRY_FOR_WALL`. - * Phase 3 stress test; flag drops when fixture parity signs off. - * - **fence**: registry-driven behind `NEXT_PUBLIC_USE_REGISTRY_FOR_FENCE`. - * First Phase 5 batch-migration kind. Same shape as wall (thin - * renderer + system re-export); pure geometry / floor-plan / tool - * affordance ports as later milestones. + * All four current kinds are registered unconditionally. Parity is + * verified by comparing against deployed production rather than an + * in-app env-var flag toggle. Legacy paths still exist in `viewer/` and + * `editor/` for kinds undergoing migration; they short-circuit via the + * Phase 0 `` wrapper + `NodeRenderer`'s registry-first + * dispatch. Phase 6 deletes the legacy paths. */ -const wallEntries: AnyNodeDefinition[] = isWallRegistryEnabled() - ? [wallDefinition as unknown as AnyNodeDefinition] - : [] - -const fenceEntries: AnyNodeDefinition[] = isFenceRegistryEnabled() - ? [fenceDefinition as unknown as AnyNodeDefinition] - : [] - export const builtinPlugin: Plugin = { id: 'pascal:core', apiVersion: 1, nodes: [ shelfDefinition as unknown as AnyNodeDefinition, spawnDefinition as unknown as AnyNodeDefinition, - ...wallEntries, - ...fenceEntries, + wallDefinition as unknown as AnyNodeDefinition, + fenceDefinition as unknown as AnyNodeDefinition, ], } diff --git a/packages/nodes/src/wall/feature-flag.ts b/packages/nodes/src/wall/feature-flag.ts deleted file mode 100644 index cab551f6..00000000 --- a/packages/nodes/src/wall/feature-flag.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Feature flag for the registry-driven wall. - * - * Wall is the Phase 3 stress test — its scope is large enough (5 affordances, - * miter cascade, host re-anchor, slab/zone dirty propagation, undo - * correctness) that flipping it on uncontrolled would risk silently - * regressing every wall scene in production. - * - * Pattern mirrors `NEXT_PUBLIC_USE_REGISTRY_FOR_SPAWN` used during the - * Phase 2 spawn migration: when ON, `wallDefinition` is appended to - * `builtinPlugin.nodes` and the Phase 0 dispatch shims take over wall - * rendering / tooling. When OFF, the legacy wall paths run unchanged. - * - * Literal `process.env.NEXT_PUBLIC_USE_REGISTRY_FOR_WALL` access is required - * — Next.js only inline-substitutes literal env reads, so `process.env[name]` - * with a variable would always read undefined in client bundles. - * - * Drop this file the moment Phase 3 parity is signed off and wall is - * registered unconditionally. - */ -export const isWallRegistryEnabled = (): boolean => { - return process.env.NEXT_PUBLIC_USE_REGISTRY_FOR_WALL === 'true' -} diff --git a/packages/nodes/src/wall/index.ts b/packages/nodes/src/wall/index.ts index 877fb3a8..b30febab 100644 --- a/packages/nodes/src/wall/index.ts +++ b/packages/nodes/src/wall/index.ts @@ -1,3 +1,2 @@ export { wallDefinition } from './definition' -export { isWallRegistryEnabled } from './feature-flag' export { WallNode } from './schema' diff --git a/packages/nodes/src/wall/renderer.tsx b/packages/nodes/src/wall/renderer.tsx index 5a17e41a..23afed34 100644 --- a/packages/nodes/src/wall/renderer.tsx +++ b/packages/nodes/src/wall/renderer.tsx @@ -14,8 +14,7 @@ import { BufferGeometry, Float32BufferAttribute, type Mesh } from 'three' * items) inside the wall's local frame. * * Behaviorally identical to the legacy `WallRenderer` in - * `@pascal-app/viewer/components/renderers/wall/wall-renderer.tsx` — same - * placeholder geometry, same material lookup, same dirty-mark on mount. + * `@pascal-app/viewer/components/renderers/wall/wall-renderer.tsx`. * Phase 6 deletes the legacy file; until then both coexist and the Phase 0 * shims pick which one renders based on `nodeRegistry.has('wall')`. * @@ -25,12 +24,6 @@ import { BufferGeometry, Float32BufferAttribute, type Mesh } from 'three' * That decision lands in a later milestone; for now the system retains * ownership of the rebuild loop. */ -// Phase 3 verification — set once when the first registry-driven wall -// renderer mounts. Stops the log firing per wall (a scene with 200 walls -// would spam the console). Drop alongside the feature flag at Phase 3 -// sign-off. -let didLogFirstRegistryWallMount = false - function createEmptyWallGeometry(): BufferGeometry { const geometry = new BufferGeometry() geometry.setAttribute('position', new Float32BufferAttribute([], 3)) @@ -55,14 +48,6 @@ const WallRenderer = ({ node }: { node: WallNode }) => { useScene.getState().markDirty(node.id) }, [node.id]) - useEffect(() => { - if (didLogFirstRegistryWallMount) return - didLogFirstRegistryWallMount = true - console.info( - '[wall:registry] first registry-driven WallRenderer mounted — legacy WallRenderer is NOT in use', - ) - }, []) - useEffect(() => { return () => { placeholderGeometry.dispose() diff --git a/packages/nodes/src/wall/system.tsx b/packages/nodes/src/wall/system.tsx index c2351693..0d7476fd 100644 --- a/packages/nodes/src/wall/system.tsx +++ b/packages/nodes/src/wall/system.tsx @@ -1,12 +1,11 @@ 'use client' import { WallCutout, WallSystem } from '@pascal-app/viewer' -import { useEffect } from 'react' /** * Registry-driven wall system bundle. * - * Wall has two per-frame concerns that need to mount when the kind is + * Wall has two per-frame concerns that mount when the kind is * registry-driven: * * - **`WallSystem`** — reads `dirtyNodes`, batches by level, runs @@ -17,28 +16,16 @@ import { useEffect } from 'react' * - **`WallCutout`** — cutaway-mode hide/show logic based on camera * direction and `frontSide` / `backSide` interior/exterior tags. * - * Both already live in `@pascal-app/viewer` and are wrapped in - * `` at the legacy mount point. When the - * `wall` kind appears in `nodeRegistry`, those wrappers short-circuit and - * this bundle takes over the mount via `RegisteredSystems`. The - * components themselves are unchanged — no logic duplication during - * Phase 3. + * Both live in `@pascal-app/viewer` and are wrapped in + * `` at the legacy mount point. With wall + * registered, those wrappers short-circuit and this bundle takes over + * the mount via `RegisteredSystems`. The components themselves are + * unchanged — no logic duplication. * * Phase 6 deletes the legacy `` wrappers; until * then this file is the single mount surface for wall's per-frame work. */ const WallSystems = () => { - // Phase 3 verification log — confirms the registry took over the wall - // dispatch. Fires once when RegisteredSystems lazy-loads this bundle (so - // exactly once per viewer mount when NEXT_PUBLIC_USE_REGISTRY_FOR_WALL=true). - // Drop alongside the feature flag at Phase 3 sign-off. - useEffect(() => { - console.info('[wall:registry] system bundle mounted — registry path active') - return () => { - console.info('[wall:registry] system bundle unmounted') - } - }, []) - return ( <> diff --git a/packages/viewer/src/components/renderers/fence/fence-renderer.tsx b/packages/viewer/src/components/renderers/fence/fence-renderer.tsx index 192f877e..e87186c5 100644 --- a/packages/viewer/src/components/renderers/fence/fence-renderer.tsx +++ b/packages/viewer/src/components/renderers/fence/fence-renderer.tsx @@ -1,15 +1,9 @@ import { type FenceNode, useRegistry, useScene } from '@pascal-app/core' -import { useEffect, useLayoutEffect, useMemo, useRef } from 'react' +import { useLayoutEffect, useMemo, useRef } from 'react' import type { Mesh } from 'three' import { useNodeEvents } from '../../../hooks/use-node-events' import { DEFAULT_STAIR_MATERIAL } from '../../../lib/materials' -// Phase 5 verification log — see matching `[fence:registry]` log in -// nodes/src/fence/renderer.tsx. Fires once if the legacy path is active -// (flag off or kind not registered). Drop alongside the legacy file at -// Phase 6 cleanup. -let didLogFirstLegacyFenceMount = false - export const FenceRenderer = ({ node }: { node: FenceNode }) => { const ref = useRef(null!) const handlers = useNodeEvents(node, 'fence') @@ -20,14 +14,6 @@ export const FenceRenderer = ({ node }: { node: FenceNode }) => { useScene.getState().markDirty(node.id) }, [node.id]) - useEffect(() => { - if (didLogFirstLegacyFenceMount) return - didLogFirstLegacyFenceMount = true - console.info( - '[fence:legacy] first legacy FenceRenderer mounted — registry-driven FenceRenderer is NOT in use', - ) - }, []) - return ( { useScene.getState().markDirty(node.id) }, [node.id]) - useEffect(() => { - if (didLogFirstLegacyWallMount) return - didLogFirstLegacyWallMount = true - console.info( - '[wall:legacy] first legacy WallRenderer mounted — registry-driven WallRenderer is NOT in use', - ) - }, []) - useEffect(() => { return () => { placeholderGeometry.dispose()