Skip to content

Releases: meterup/pascal-editor

@meterup/pascal-viewer@1.0.0-beta.6

Pre-release

Choose a tag to compare

@jrolfs jrolfs released this 08 Oct 09:50
487d471

Minor Changes

  • d3aa350: Let a host bind the 3D camera's gestures.

    The bindings were fixed: left drag selects unless you're in preview mode or
    holding space, middle pans, right rotates, wheel zooms. A host whose scene is
    read-only, or whose users are on laptop trackpads with no middle button, had no
    say — panning meant the space bar.

    cameraInput on useViewer merges over the defaults rather than replacing
    them, so an unset entry keeps whatever the current mode asked for:

    useViewer.getState().setCameraInput({
      mouseButtons: { left: "pan" },
      touches: { one: "pan" },
    });

    Actions are named for what the user sees (pan, rotate, zoom, dolly,
    and zoomPan / dollyPan for the combined two-finger gestures) rather than
    mirroring the camera library's enum, so the binding API doesn't tie a host to
    that dependency. The wheel and the one-finger slots take narrower sets than the
    others, because a wheel has one axis and a pinch needs two fingers.

    Merging also covers the modifier path, which rebuilds the bindings on every key
    change, so holding space no longer drops a host's overrides.

  • 2f5c4e1: Rebase the fork onto upstream pascalorg/editor@4c34032c (2026-08-24) — 478
    commits, and a move off the fork's own 0.9.x line onto upstream's 1.0.0-beta
    versioning. The fork now carries nothing but its release machinery and two
    editor patches; everything else is upstream.

    Nothing we previously exported was removed. The public API grew substantially
    (core 172 → 388 exports, viewer 106 → 193, editor 156 → 470) without breaking
    underneath consumers.

    What matters most for consumers:

    • Near-collinear wall junctions no longer explode.
      calculateJunctionIntersections now rejects a miter on its outcome, capped
      at MITER_LIMIT (10) half-thicknesses, rather than only rejecting walls that
      are exactly parallel. A junction where two walls run straight through each
      other but miss collinearity by a fraction of a degree used to put a plan
      vertex halfThickness / sin(θ) away — kilometres out, collapsing the 2D
      fit-to-bounds into one flat rectangle and firing beams across the 3D scene. It
      now butts. Imported floor plans with off-axis wall runs render correctly.
    • Level assembly is dramatically faster. findJunctions buckets walls into
      a spatial grid by AABB instead of testing every wall against every junction
      point. On a 1081-wall imported floor that is 584 ms → 11 ms per call, and
      WallSystem repeats the call every frame while progressively rebuilding.
    • Vertical building model. Scenes now carry explicit building and level
      structure (buildingId, schema/nodes/building.ts), with load-time
      migrations for saves that predate it — vertical, site-child, wall-slot,
      elevator, retired-floorplan and construction-dimension. Anything synthesizing
      a SceneGraph rather than loading a saved one needs to match the new shape;
      this is the single change most likely to require work on the consumer side.
    • New packages: capture-protocol, capture-viewer, cli. New core
      systems alongside wall/ and stair/: fence/, slab/, elevator/.
    • healSceneNodes repairs already-saved scenes on load, dropping
      degenerate nodes such as zero-length walls.

    Three fork patches are dropped here because upstream made them redundant:

    • The floorplan-registry-layer geometry cache. Upstream landed its own
      geometryCacheRef/levelDataCacheRef memoisation plus the findJunctions
      prefilter above, which together address the same stall from further up.
    • The ThreeElements/lineBasicNodeMaterial narrowing in viewer that let
      tsgo check the package. Upstream adopted the TypeScript 7 native compiler
      and landed the identical narrowing.
    • crossOrigin on the 2D guide image. It forced the 2D <image> to fetch in
      the same CORS mode as the 3D texture load so the two shared one browser cache
      entry instead of downloading the image twice. Consumers that hand Pascal
      same-origin blob URLs never hit the split in the first place, and the flag is
      actively wrong for guide images served without CORS headers.
  • e1d3a5a: Add showSiteBoundary to hide the site's boundary geometry.

    The boundary was always drawn, with no way to turn it off. It is a useful
    reference while laying out a site and pure noise on a surface that only presents
    a finished building.

    showSiteBoundary joins useViewer next to showGrid / showZones and
    governs both views from one flag, because it is the same polygon in each:

    useViewer.getState().setShowSiteBoundary(false);

    In 2D that drops the dashed polygon, its edge labels and its handles. In 3D it
    drops the ground pad and the amber outline. Buildings, items and the horizon
    ground are unaffected, so the scene loses its lot outline rather than its floor
    — the horizon disc punches holes for slab footprints, not for the site polygon,
    so it still covers the ground the pad was covering.

    Two things stay deliberately outside the flag. The fitted viewport still sizes
    itself from the site polygon, so toggling this doesn't reframe the plan. And
    sculpted terrain keeps rendering, since that is modelled geometry rather than
    boundary chrome.

    Defaults to true and isn't persisted, so nothing changes until a host sets it.

Patch Changes

  • 54e1b19: Let a scene theme set the ground grid's line colours and idle opacity.

    appearance hardcoded both colours, and in dark mode <Grid> also ignored its
    own cellColor / sectionColor props, so neither a theme nor a host could
    adjust how the grid read. That matters because the grid is drawn over the lit
    ground fill: its contrast against that surface is a property of the pair, and
    a light/dark flag can't settle it. ground and backgroundSky were already
    carved out of appearance for the same reason.

    const theme = SCENE_THEMES.find((t) => t.id === "night");
    theme.grid = { cell: "#2b2d3a", section: "#343747", idleOpacity: 0.18 };

    All three keys are optional and fall back to the previous appearance-derived
    values, so existing themes render identically. idleOpacity applies to the
    always-on ground reference only; an active placement patch keeps its own
    brightened treatment.

@meterup/pascal-nodes@1.0.0-beta.6

Pre-release

Choose a tag to compare

@jrolfs jrolfs released this 08 Oct 09:50
487d471

Minor Changes

  • 2f5c4e1: Rebase the fork onto upstream pascalorg/editor@4c34032c (2026-08-24) — 478
    commits, and a move off the fork's own 0.9.x line onto upstream's 1.0.0-beta
    versioning. The fork now carries nothing but its release machinery and two
    editor patches; everything else is upstream.

    Nothing we previously exported was removed. The public API grew substantially
    (core 172 → 388 exports, viewer 106 → 193, editor 156 → 470) without breaking
    underneath consumers.

    What matters most for consumers:

    • Near-collinear wall junctions no longer explode.
      calculateJunctionIntersections now rejects a miter on its outcome, capped
      at MITER_LIMIT (10) half-thicknesses, rather than only rejecting walls that
      are exactly parallel. A junction where two walls run straight through each
      other but miss collinearity by a fraction of a degree used to put a plan
      vertex halfThickness / sin(θ) away — kilometres out, collapsing the 2D
      fit-to-bounds into one flat rectangle and firing beams across the 3D scene. It
      now butts. Imported floor plans with off-axis wall runs render correctly.
    • Level assembly is dramatically faster. findJunctions buckets walls into
      a spatial grid by AABB instead of testing every wall against every junction
      point. On a 1081-wall imported floor that is 584 ms → 11 ms per call, and
      WallSystem repeats the call every frame while progressively rebuilding.
    • Vertical building model. Scenes now carry explicit building and level
      structure (buildingId, schema/nodes/building.ts), with load-time
      migrations for saves that predate it — vertical, site-child, wall-slot,
      elevator, retired-floorplan and construction-dimension. Anything synthesizing
      a SceneGraph rather than loading a saved one needs to match the new shape;
      this is the single change most likely to require work on the consumer side.
    • New packages: capture-protocol, capture-viewer, cli. New core
      systems alongside wall/ and stair/: fence/, slab/, elevator/.
    • healSceneNodes repairs already-saved scenes on load, dropping
      degenerate nodes such as zero-length walls.

    Three fork patches are dropped here because upstream made them redundant:

    • The floorplan-registry-layer geometry cache. Upstream landed its own
      geometryCacheRef/levelDataCacheRef memoisation plus the findJunctions
      prefilter above, which together address the same stall from further up.
    • The ThreeElements/lineBasicNodeMaterial narrowing in viewer that let
      tsgo check the package. Upstream adopted the TypeScript 7 native compiler
      and landed the identical narrowing.
    • crossOrigin on the 2D guide image. It forced the 2D <image> to fetch in
      the same CORS mode as the 3D texture load so the two shared one browser cache
      entry instead of downloading the image twice. Consumers that hand Pascal
      same-origin blob URLs never hit the split in the first place, and the flag is
      actively wrong for guide images served without CORS headers.

Patch Changes

  • e1d3a5a: Add showSiteBoundary to hide the site's boundary geometry.

    The boundary was always drawn, with no way to turn it off. It is a useful
    reference while laying out a site and pure noise on a surface that only presents
    a finished building.

    showSiteBoundary joins useViewer next to showGrid / showZones and
    governs both views from one flag, because it is the same polygon in each:

    useViewer.getState().setShowSiteBoundary(false);

    In 2D that drops the dashed polygon, its edge labels and its handles. In 3D it
    drops the ground pad and the amber outline. Buildings, items and the horizon
    ground are unaffected, so the scene loses its lot outline rather than its floor
    — the horizon disc punches holes for slab footprints, not for the site polygon,
    so it still covers the ground the pad was covering.

    Two things stay deliberately outside the flag. The fitted viewport still sizes
    itself from the site polygon, so toggling this doesn't reframe the plan. And
    sculpted terrain keeps rendering, since that is modelled geometry rather than
    boundary chrome.

    Defaults to true and isn't persisted, so nothing changes until a host sets it.

@meterup/pascal-mcp@1.0.0-beta.7

Pre-release

Choose a tag to compare

@jrolfs jrolfs released this 08 Oct 09:50
487d471

Minor Changes

  • 2f5c4e1: Rebase the fork onto upstream pascalorg/editor@4c34032c (2026-08-24) — 478
    commits, and a move off the fork's own 0.9.x line onto upstream's 1.0.0-beta
    versioning. The fork now carries nothing but its release machinery and two
    editor patches; everything else is upstream.

    Nothing we previously exported was removed. The public API grew substantially
    (core 172 → 388 exports, viewer 106 → 193, editor 156 → 470) without breaking
    underneath consumers.

    What matters most for consumers:

    • Near-collinear wall junctions no longer explode.
      calculateJunctionIntersections now rejects a miter on its outcome, capped
      at MITER_LIMIT (10) half-thicknesses, rather than only rejecting walls that
      are exactly parallel. A junction where two walls run straight through each
      other but miss collinearity by a fraction of a degree used to put a plan
      vertex halfThickness / sin(θ) away — kilometres out, collapsing the 2D
      fit-to-bounds into one flat rectangle and firing beams across the 3D scene. It
      now butts. Imported floor plans with off-axis wall runs render correctly.
    • Level assembly is dramatically faster. findJunctions buckets walls into
      a spatial grid by AABB instead of testing every wall against every junction
      point. On a 1081-wall imported floor that is 584 ms → 11 ms per call, and
      WallSystem repeats the call every frame while progressively rebuilding.
    • Vertical building model. Scenes now carry explicit building and level
      structure (buildingId, schema/nodes/building.ts), with load-time
      migrations for saves that predate it — vertical, site-child, wall-slot,
      elevator, retired-floorplan and construction-dimension. Anything synthesizing
      a SceneGraph rather than loading a saved one needs to match the new shape;
      this is the single change most likely to require work on the consumer side.
    • New packages: capture-protocol, capture-viewer, cli. New core
      systems alongside wall/ and stair/: fence/, slab/, elevator/.
    • healSceneNodes repairs already-saved scenes on load, dropping
      degenerate nodes such as zero-length walls.

    Three fork patches are dropped here because upstream made them redundant:

    • The floorplan-registry-layer geometry cache. Upstream landed its own
      geometryCacheRef/levelDataCacheRef memoisation plus the findJunctions
      prefilter above, which together address the same stall from further up.
    • The ThreeElements/lineBasicNodeMaterial narrowing in viewer that let
      tsgo check the package. Upstream adopted the TypeScript 7 native compiler
      and landed the identical narrowing.
    • crossOrigin on the 2D guide image. It forced the 2D <image> to fetch in
      the same CORS mode as the 3D texture load so the two shared one browser cache
      entry instead of downloading the image twice. Consumers that hand Pascal
      same-origin blob URLs never hit the split in the first place, and the flag is
      actively wrong for guide images served without CORS headers.

All notable changes to @meterup/pascal-mcp will be documented in this file.

The format is based on Keep a Changelog,
and this project adheres to Semantic Versioning.

@meterup/pascal-ifc-converter@1.0.0-beta.6

Choose a tag to compare

@jrolfs jrolfs released this 08 Oct 09:50
487d471

Minor Changes

  • 2f5c4e1: Rebase the fork onto upstream pascalorg/editor@4c34032c (2026-08-24) — 478
    commits, and a move off the fork's own 0.9.x line onto upstream's 1.0.0-beta
    versioning. The fork now carries nothing but its release machinery and two
    editor patches; everything else is upstream.

    Nothing we previously exported was removed. The public API grew substantially
    (core 172 → 388 exports, viewer 106 → 193, editor 156 → 470) without breaking
    underneath consumers.

    What matters most for consumers:

    • Near-collinear wall junctions no longer explode.
      calculateJunctionIntersections now rejects a miter on its outcome, capped
      at MITER_LIMIT (10) half-thicknesses, rather than only rejecting walls that
      are exactly parallel. A junction where two walls run straight through each
      other but miss collinearity by a fraction of a degree used to put a plan
      vertex halfThickness / sin(θ) away — kilometres out, collapsing the 2D
      fit-to-bounds into one flat rectangle and firing beams across the 3D scene. It
      now butts. Imported floor plans with off-axis wall runs render correctly.
    • Level assembly is dramatically faster. findJunctions buckets walls into
      a spatial grid by AABB instead of testing every wall against every junction
      point. On a 1081-wall imported floor that is 584 ms → 11 ms per call, and
      WallSystem repeats the call every frame while progressively rebuilding.
    • Vertical building model. Scenes now carry explicit building and level
      structure (buildingId, schema/nodes/building.ts), with load-time
      migrations for saves that predate it — vertical, site-child, wall-slot,
      elevator, retired-floorplan and construction-dimension. Anything synthesizing
      a SceneGraph rather than loading a saved one needs to match the new shape;
      this is the single change most likely to require work on the consumer side.
    • New packages: capture-protocol, capture-viewer, cli. New core
      systems alongside wall/ and stair/: fence/, slab/, elevator/.
    • healSceneNodes repairs already-saved scenes on load, dropping
      degenerate nodes such as zero-length walls.

    Three fork patches are dropped here because upstream made them redundant:

    • The floorplan-registry-layer geometry cache. Upstream landed its own
      geometryCacheRef/levelDataCacheRef memoisation plus the findJunctions
      prefilter above, which together address the same stall from further up.
    • The ThreeElements/lineBasicNodeMaterial narrowing in viewer that let
      tsgo check the package. Upstream adopted the TypeScript 7 native compiler
      and landed the identical narrowing.
    • crossOrigin on the 2D guide image. It forced the 2D <image> to fetch in
      the same CORS mode as the 3D texture load so the two shared one browser cache
      entry instead of downloading the image twice. Consumers that hand Pascal
      same-origin blob URLs never hit the split in the first place, and the flag is
      actively wrong for guide images served without CORS headers.

Patch Changes

  • Updated dependencies [2251db5]
  • Updated dependencies [2f5c4e1]
  • Updated dependencies [5ea6e96]
    • @meterup/pascal-core@1.0.0-beta.6

@meterup/pascal-editor@1.0.0-beta.6

Pre-release

Choose a tag to compare

@jrolfs jrolfs released this 08 Oct 09:50
487d471

Minor Changes

  • d3aa350: Let a host bind the 3D camera's gestures.

    The bindings were fixed: left drag selects unless you're in preview mode or
    holding space, middle pans, right rotates, wheel zooms. A host whose scene is
    read-only, or whose users are on laptop trackpads with no middle button, had no
    say — panning meant the space bar.

    cameraInput on useViewer merges over the defaults rather than replacing
    them, so an unset entry keeps whatever the current mode asked for:

    useViewer.getState().setCameraInput({
      mouseButtons: { left: "pan" },
      touches: { one: "pan" },
    });

    Actions are named for what the user sees (pan, rotate, zoom, dolly,
    and zoomPan / dollyPan for the combined two-finger gestures) rather than
    mirroring the camera library's enum, so the binding API doesn't tie a host to
    that dependency. The wheel and the one-finger slots take narrower sets than the
    others, because a wheel has one axis and a pinch needs two fingers.

    Merging also covers the modifier path, which rebuilds the bindings on every key
    change, so holding space no longer drops a host's overrides.

  • 068468e: Add onHeadingChange to the floor-plan compass slot.

    needleRef is the only way a host gets the per-frame heading, and it's typed
    RefObject<SVGSVGElement> for the element that visually rotates. That works for
    a host whose rotating element is an <svg> it owns, and not at all for one
    composing an existing icon component, which has nowhere to put the ref and
    falls back to re-rendering per commit.

    onHeadingChange carries the same values as a subscription:

    floorplanCompassSlot={({ northRotationDeg, alignToNorth, onHeadingChange }) => {
      const heading = useMotionValue(northRotationDeg)
      useEffect(() => onHeadingChange((deg) => heading.set(deg)), [onHeadingChange])
      return <motion.div style={{ rotate: heading }} onClick={alignToNorth}>{glyph}</motion.div>
    }}

    It fires immediately with the current heading, because each source only writes
    on its own updates and a still view sends nothing, so a subscriber that waited
    would show north until the first movement.

    Deliberately a callback rather than an animation library's value type, so the
    slot contract stays library-agnostic and a host can drive a plain style write
    instead if it prefers. needleRef is unchanged.

  • 9a4878b: Add floorplanDividerSlot for host-owned split-view dividers.

    The divider's pill was a fixed neutral capsule, so a host whose own resize
    handles look nothing like it had no way to reconcile the two, and nowhere to
    hang a control that belongs on the boundary between the panes.

    The slot replaces the pill and receives the handler that starts the pane drag,
    so a host can put it on whatever the user actually grabs:

    <Editor
      floorplanDividerSlot={({ startResize }) => (
        <MyControlGroup>
          <MyToggle onClick={(event) => event.stopPropagation()} />
          <MyGrabHandle onPointerDown={startResize} />
        </MyControlGroup>
      )}
    />

    The divider itself still starts a drag on pointer down, so controls that aren't
    the grab handle need to stop propagation or clicking them resizes the panes.

    Only rendered in split view. Omitting the slot keeps the built-in pill, which
    also now carries data-pascal-floorplan-divider-thumb for hosts that only want
    to restyle it.

  • 2f5c4e1: Rebase the fork onto upstream pascalorg/editor@4c34032c (2026-08-24) — 478
    commits, and a move off the fork's own 0.9.x line onto upstream's 1.0.0-beta
    versioning. The fork now carries nothing but its release machinery and two
    editor patches; everything else is upstream.

    Nothing we previously exported was removed. The public API grew substantially
    (core 172 → 388 exports, viewer 106 → 193, editor 156 → 470) without breaking
    underneath consumers.

    What matters most for consumers:

    • Near-collinear wall junctions no longer explode.
      calculateJunctionIntersections now rejects a miter on its outcome, capped
      at MITER_LIMIT (10) half-thicknesses, rather than only rejecting walls that
      are exactly parallel. A junction where two walls run straight through each
      other but miss collinearity by a fraction of a degree used to put a plan
      vertex halfThickness / sin(θ) away — kilometres out, collapsing the 2D
      fit-to-bounds into one flat rectangle and firing beams across the 3D scene. It
      now butts. Imported floor plans with off-axis wall runs render correctly.
    • Level assembly is dramatically faster. findJunctions buckets walls into
      a spatial grid by AABB instead of testing every wall against every junction
      point. On a 1081-wall imported floor that is 584 ms → 11 ms per call, and
      WallSystem repeats the call every frame while progressively rebuilding.
    • Vertical building model. Scenes now carry explicit building and level
      structure (buildingId, schema/nodes/building.ts), with load-time
      migrations for saves that predate it — vertical, site-child, wall-slot,
      elevator, retired-floorplan and construction-dimension. Anything synthesizing
      a SceneGraph rather than loading a saved one needs to match the new shape;
      this is the single change most likely to require work on the consumer side.
    • New packages: capture-protocol, capture-viewer, cli. New core
      systems alongside wall/ and stair/: fence/, slab/, elevator/.
    • healSceneNodes repairs already-saved scenes on load, dropping
      degenerate nodes such as zero-length walls.

    Three fork patches are dropped here because upstream made them redundant:

    • The floorplan-registry-layer geometry cache. Upstream landed its own
      geometryCacheRef/levelDataCacheRef memoisation plus the findJunctions
      prefilter above, which together address the same stall from further up.
    • The ThreeElements/lineBasicNodeMaterial narrowing in viewer that let
      tsgo check the package. Upstream adopted the TypeScript 7 native compiler
      and landed the identical narrowing.
    • crossOrigin on the 2D guide image. It forced the 2D <image> to fetch in
      the same CORS mode as the 3D texture load so the two shared one browser cache
      entry instead of downloading the image twice. Consumers that hand Pascal
      same-origin blob URLs never hit the split in the first place, and the flag is
      actively wrong for guide images served without CORS headers.
  • 068468e: Add useFloorplanNavigationControls for host-driven 2D navigation.

    A host could already move the plan with publishNavigationSyncPose, but only by
    knowing rather a lot: that a pose is { target, azimuth, viewWidth }, that
    'host' is the one source that always reaches the plan, that azimuth is
    radians, that screen deltas have to be rotated into the plan by its own azimuth
    before they can move the target, and that animating means publishing a stream
    rather than a single pose. The demo in apps/editor did all of that by hand.

    The hook does it instead:

    const plan = useFloorplanNavigationControls()
    
    useGesture({
      onDrag: ({ delta: [dx, dy] }) => plan.panByPixels(-dx, -dy),
      onPinch: ({ offset: [scale] }) => plan.zoomTo(scale),
    })
    
    <button onClick={() => plan.rotateByDegrees(90, { durationMs: 260 })}>Rotate</button>

    Rotation tweens take the short way round, so turning from 350° to 10° travels
    20° forwards rather than 340° back, and an in-flight tween is cancelled by the
    next command or by unmount.

    Whether a command moves the 3D camera too is the link's business, not the
    hook's: these publish as 'host', which always drives the plan, and the camera
    follows only while floorplanNavigationLink is on.

    The hook reads the shared pose at call time through getPose() and
    getMetersPerPixel() rather than subscribing to it. navigationSyncPose
    changes every frame while anything is moving, so a subscription would re-render
    the caller at that rate, which for a component that renders the editor is the
    whole cost the imperative viewport exists to avoid. The returned object has a
    stable identity, so it can go straight into an effect's dependencies.

    Supporting this, the floor-plan panel now publishes its measured size in CSS
    pixels. A pose carries only its width in metres, so until now nobody but the
    panel could convert one back to screen space, which is why the playground had
    to stand in window.innerWidth and why it was wrong in split view. The panel
    also republishes navigationSyncPose on every committed viewport change rather
    than seeding it once: a single seed goes stale as soon as the plan moves any
    other way, most obviously the initial fit, and a host reading that stale pose
    and publishing it back dragged the plan to wherever the seed was taken.

  • 5fbeb64: Add hideBuiltInOverlays, and let hosts style the split-view divider.

    Hosts that bring their own toolbar had one way to get rid of the editor's
    floating overlays: hold capture mode on. That works, but capture mode is a
    snapshot mode. It also unmounts SelectionManager, which owns the highlight
    material and the outliner as well as click handling, so the canvas ends up
    unselectable and nothing highlights even when something is selected from the
    2D plan. Permanent capture mode is the wrong tool and this was the cost.

    hideBuiltInOverlays hides exactly the chrome and nothing else:

    <Editor hideBuiltInOverlays layoutVersion="v2" />

    That drops the level selector, the action menu, the panel manager, the helper
    manager, the built-in Plugins rail entry and the camera-controls hint, in both
    layout versions. The Plugins entry matters because useHostPanels appends it
    in the edit workspace, so a host passing sidebarTabs={[]} still got a
    non-empty tab bar and the v2 layout rendered a left column for a panel it never
    as...

Read more

@meterup/pascal-core@1.0.0-beta.6

Pre-release

Choose a tag to compare

@jrolfs jrolfs released this 08 Oct 09:50
487d471

Minor Changes

  • 2f5c4e1: Rebase the fork onto upstream pascalorg/editor@4c34032c (2026-08-24) — 478
    commits, and a move off the fork's own 0.9.x line onto upstream's 1.0.0-beta
    versioning. The fork now carries nothing but its release machinery and two
    editor patches; everything else is upstream.

    Nothing we previously exported was removed. The public API grew substantially
    (core 172 → 388 exports, viewer 106 → 193, editor 156 → 470) without breaking
    underneath consumers.

    What matters most for consumers:

    • Near-collinear wall junctions no longer explode.
      calculateJunctionIntersections now rejects a miter on its outcome, capped
      at MITER_LIMIT (10) half-thicknesses, rather than only rejecting walls that
      are exactly parallel. A junction where two walls run straight through each
      other but miss collinearity by a fraction of a degree used to put a plan
      vertex halfThickness / sin(θ) away — kilometres out, collapsing the 2D
      fit-to-bounds into one flat rectangle and firing beams across the 3D scene. It
      now butts. Imported floor plans with off-axis wall runs render correctly.
    • Level assembly is dramatically faster. findJunctions buckets walls into
      a spatial grid by AABB instead of testing every wall against every junction
      point. On a 1081-wall imported floor that is 584 ms → 11 ms per call, and
      WallSystem repeats the call every frame while progressively rebuilding.
    • Vertical building model. Scenes now carry explicit building and level
      structure (buildingId, schema/nodes/building.ts), with load-time
      migrations for saves that predate it — vertical, site-child, wall-slot,
      elevator, retired-floorplan and construction-dimension. Anything synthesizing
      a SceneGraph rather than loading a saved one needs to match the new shape;
      this is the single change most likely to require work on the consumer side.
    • New packages: capture-protocol, capture-viewer, cli. New core
      systems alongside wall/ and stair/: fence/, slab/, elevator/.
    • healSceneNodes repairs already-saved scenes on load, dropping
      degenerate nodes such as zero-length walls.

    Three fork patches are dropped here because upstream made them redundant:

    • The floorplan-registry-layer geometry cache. Upstream landed its own
      geometryCacheRef/levelDataCacheRef memoisation plus the findJunctions
      prefilter above, which together address the same stall from further up.
    • The ThreeElements/lineBasicNodeMaterial narrowing in viewer that let
      tsgo check the package. Upstream adopted the TypeScript 7 native compiler
      and landed the identical narrowing.
    • crossOrigin on the 2D guide image. It forced the 2D <image> to fetch in
      the same CORS mode as the 3D texture load so the two shared one browser cache
      entry instead of downloading the image twice. Consumers that hand Pascal
      same-origin blob URLs never hit the split in the first place, and the flag is
      actively wrong for guide images served without CORS headers.

Patch Changes

  • 2251db5: Add an alignmentGuideStroke slot to FloorplanPalette and draw the 2D snap
    guides from it.

    FloorplanAlignmentGuideLayer hardcoded #ef4444, so the one piece of 2D
    chrome most visible during a drag was the one piece a theme couldn't reach.
    It already consumes the render context, so it now reads the slot and keeps the
    red as its no-provider fallback.

  • 5ea6e96: Let plugins type their node kinds into AnyNode by declaration merging.

    Plugin kinds were absent from AnyNode, AnyNodeType and AnyNodeId, so
    plugin code and anything consuming it had to widen to string or cast. Ids
    made it worse: objectId produces `${kind}_${string}`, so a plugin node's
    id was never assignable to AnyNodeId and generic scene code reached for
    as AnyNodeId.

    Declare kinds in PluginNodes and they join all three unions, which restores
    exhaustive switches and drops the casts:

    declare module "@meterup/pascal-core/plugin-nodes" {
      interface PluginNodes {
        tree: z.infer<typeof TreeNode>;
      }
    }

    Purely type-level and additive. The interface ships empty, PluginNodeType
    resolves to never, and AnyNode stays exactly equal to the new BuiltinNode
    alias, so a host with no plugins sees no change at all. There is no runtime
    component and no API signature churn; nodeRegistry.get still takes string,
    which it has to, since setPluginDiscovery allows kinds that were never
    compiled against.

    Two design points worth knowing:

    The interface lives in its own module and is reachable only at
    @meterup/pascal-core/plugin-nodes. declare module merges only with the module
    that declares the interface, and the package entry is a pure barrel, so
    augmenting @meterup/pascal-core would quietly declare an unrelated second
    interface and widen nothing. Nothing else is exported from that module, so an
    augmentation has nothing to shadow.

    PluginNode adds the common BaseNode fields and takes type from the key
    rather than trusting the declared shape. AnyNode is a union, and a union only
    exposes properties present on every member, so a declaration that
    under-described itself would silently strip parentId and friends from
    AnyNode across the entire host, with the errors landing in host code far from
    the plugin responsible.

  • Updated dependencies [2f5c4e1]

    • @meterup/pascal-capture-protocol@1.0.0-beta.5

@meterup/pascal-cli@1.0.0-beta.2

Pre-release

Choose a tag to compare

@jrolfs jrolfs released this 08 Oct 09:50
487d471

Minor Changes

  • 2f5c4e1: Rebase the fork onto upstream pascalorg/editor@4c34032c (2026-08-24) — 478
    commits, and a move off the fork's own 0.9.x line onto upstream's 1.0.0-beta
    versioning. The fork now carries nothing but its release machinery and two
    editor patches; everything else is upstream.

    Nothing we previously exported was removed. The public API grew substantially
    (core 172 → 388 exports, viewer 106 → 193, editor 156 → 470) without breaking
    underneath consumers.

    What matters most for consumers:

    • Near-collinear wall junctions no longer explode.
      calculateJunctionIntersections now rejects a miter on its outcome, capped
      at MITER_LIMIT (10) half-thicknesses, rather than only rejecting walls that
      are exactly parallel. A junction where two walls run straight through each
      other but miss collinearity by a fraction of a degree used to put a plan
      vertex halfThickness / sin(θ) away — kilometres out, collapsing the 2D
      fit-to-bounds into one flat rectangle and firing beams across the 3D scene. It
      now butts. Imported floor plans with off-axis wall runs render correctly.
    • Level assembly is dramatically faster. findJunctions buckets walls into
      a spatial grid by AABB instead of testing every wall against every junction
      point. On a 1081-wall imported floor that is 584 ms → 11 ms per call, and
      WallSystem repeats the call every frame while progressively rebuilding.
    • Vertical building model. Scenes now carry explicit building and level
      structure (buildingId, schema/nodes/building.ts), with load-time
      migrations for saves that predate it — vertical, site-child, wall-slot,
      elevator, retired-floorplan and construction-dimension. Anything synthesizing
      a SceneGraph rather than loading a saved one needs to match the new shape;
      this is the single change most likely to require work on the consumer side.
    • New packages: capture-protocol, capture-viewer, cli. New core
      systems alongside wall/ and stair/: fence/, slab/, elevator/.
    • healSceneNodes repairs already-saved scenes on load, dropping
      degenerate nodes such as zero-length walls.

    Three fork patches are dropped here because upstream made them redundant:

    • The floorplan-registry-layer geometry cache. Upstream landed its own
      geometryCacheRef/levelDataCacheRef memoisation plus the findJunctions
      prefilter above, which together address the same stall from further up.
    • The ThreeElements/lineBasicNodeMaterial narrowing in viewer that let
      tsgo check the package. Upstream adopted the TypeScript 7 native compiler
      and landed the identical narrowing.
    • crossOrigin on the 2D guide image. It forced the 2D <image> to fetch in
      the same CORS mode as the 3D texture load so the two shared one browser cache
      entry instead of downloading the image twice. Consumers that hand Pascal
      same-origin blob URLs never hit the split in the first place, and the flag is
      actively wrong for guide images served without CORS headers.

@meterup/pascal-capture-viewer@1.0.0-beta.5

Choose a tag to compare

@jrolfs jrolfs released this 08 Oct 09:50
487d471

Minor Changes

  • 2f5c4e1: Rebase the fork onto upstream pascalorg/editor@4c34032c (2026-08-24) — 478
    commits, and a move off the fork's own 0.9.x line onto upstream's 1.0.0-beta
    versioning. The fork now carries nothing but its release machinery and two
    editor patches; everything else is upstream.

    Nothing we previously exported was removed. The public API grew substantially
    (core 172 → 388 exports, viewer 106 → 193, editor 156 → 470) without breaking
    underneath consumers.

    What matters most for consumers:

    • Near-collinear wall junctions no longer explode.
      calculateJunctionIntersections now rejects a miter on its outcome, capped
      at MITER_LIMIT (10) half-thicknesses, rather than only rejecting walls that
      are exactly parallel. A junction where two walls run straight through each
      other but miss collinearity by a fraction of a degree used to put a plan
      vertex halfThickness / sin(θ) away — kilometres out, collapsing the 2D
      fit-to-bounds into one flat rectangle and firing beams across the 3D scene. It
      now butts. Imported floor plans with off-axis wall runs render correctly.
    • Level assembly is dramatically faster. findJunctions buckets walls into
      a spatial grid by AABB instead of testing every wall against every junction
      point. On a 1081-wall imported floor that is 584 ms → 11 ms per call, and
      WallSystem repeats the call every frame while progressively rebuilding.
    • Vertical building model. Scenes now carry explicit building and level
      structure (buildingId, schema/nodes/building.ts), with load-time
      migrations for saves that predate it — vertical, site-child, wall-slot,
      elevator, retired-floorplan and construction-dimension. Anything synthesizing
      a SceneGraph rather than loading a saved one needs to match the new shape;
      this is the single change most likely to require work on the consumer side.
    • New packages: capture-protocol, capture-viewer, cli. New core
      systems alongside wall/ and stair/: fence/, slab/, elevator/.
    • healSceneNodes repairs already-saved scenes on load, dropping
      degenerate nodes such as zero-length walls.

    Three fork patches are dropped here because upstream made them redundant:

    • The floorplan-registry-layer geometry cache. Upstream landed its own
      geometryCacheRef/levelDataCacheRef memoisation plus the findJunctions
      prefilter above, which together address the same stall from further up.
    • The ThreeElements/lineBasicNodeMaterial narrowing in viewer that let
      tsgo check the package. Upstream adopted the TypeScript 7 native compiler
      and landed the identical narrowing.
    • crossOrigin on the 2D guide image. It forced the 2D <image> to fetch in
      the same CORS mode as the 3D texture load so the two shared one browser cache
      entry instead of downloading the image twice. Consumers that hand Pascal
      same-origin blob URLs never hit the split in the first place, and the flag is
      actively wrong for guide images served without CORS headers.

@meterup/pascal-capture-protocol@1.0.0-beta.5

Choose a tag to compare

@jrolfs jrolfs released this 08 Oct 09:50
487d471

Minor Changes

  • 2f5c4e1: Rebase the fork onto upstream pascalorg/editor@4c34032c (2026-08-24) — 478
    commits, and a move off the fork's own 0.9.x line onto upstream's 1.0.0-beta
    versioning. The fork now carries nothing but its release machinery and two
    editor patches; everything else is upstream.

    Nothing we previously exported was removed. The public API grew substantially
    (core 172 → 388 exports, viewer 106 → 193, editor 156 → 470) without breaking
    underneath consumers.

    What matters most for consumers:

    • Near-collinear wall junctions no longer explode.
      calculateJunctionIntersections now rejects a miter on its outcome, capped
      at MITER_LIMIT (10) half-thicknesses, rather than only rejecting walls that
      are exactly parallel. A junction where two walls run straight through each
      other but miss collinearity by a fraction of a degree used to put a plan
      vertex halfThickness / sin(θ) away — kilometres out, collapsing the 2D
      fit-to-bounds into one flat rectangle and firing beams across the 3D scene. It
      now butts. Imported floor plans with off-axis wall runs render correctly.
    • Level assembly is dramatically faster. findJunctions buckets walls into
      a spatial grid by AABB instead of testing every wall against every junction
      point. On a 1081-wall imported floor that is 584 ms → 11 ms per call, and
      WallSystem repeats the call every frame while progressively rebuilding.
    • Vertical building model. Scenes now carry explicit building and level
      structure (buildingId, schema/nodes/building.ts), with load-time
      migrations for saves that predate it — vertical, site-child, wall-slot,
      elevator, retired-floorplan and construction-dimension. Anything synthesizing
      a SceneGraph rather than loading a saved one needs to match the new shape;
      this is the single change most likely to require work on the consumer side.
    • New packages: capture-protocol, capture-viewer, cli. New core
      systems alongside wall/ and stair/: fence/, slab/, elevator/.
    • healSceneNodes repairs already-saved scenes on load, dropping
      degenerate nodes such as zero-length walls.

    Three fork patches are dropped here because upstream made them redundant:

    • The floorplan-registry-layer geometry cache. Upstream landed its own
      geometryCacheRef/levelDataCacheRef memoisation plus the findJunctions
      prefilter above, which together address the same stall from further up.
    • The ThreeElements/lineBasicNodeMaterial narrowing in viewer that let
      tsgo check the package. Upstream adopted the TypeScript 7 native compiler
      and landed the identical narrowing.
    • crossOrigin on the 2D guide image. It forced the 2D <image> to fetch in
      the same CORS mode as the 3D texture load so the two shared one browser cache
      entry instead of downloading the image twice. Consumers that hand Pascal
      same-origin blob URLs never hit the split in the first place, and the flag is
      actively wrong for guide images served without CORS headers.

@meterup/pascal-viewer@0.9.1

Choose a tag to compare

@github-actions github-actions released this 29 Jun 20:58
cea4976

Published to GitHub Packages.