Skip to content

Upstream 7f629b8c'yi al (55 commit) + dikey açıklık ve sürükleme kapısı - #41

Merged
ovurrsl merged 61 commits into
integrationfrom
claude/github-push-turbopack-fix-v0z56s
Aug 25, 2026
Merged

Upstream 7f629b8c'yi al (55 commit) + dikey açıklık ve sürükleme kapısı#41
ovurrsl merged 61 commits into
integrationfrom
claude/github-push-turbopack-fix-v0z56s

Conversation

@ovurrsl

@ovurrsl ovurrsl commented Aug 24, 2026

Copy link
Copy Markdown
Owner

Üç commit: upstream birleştirmesi, ve üstüne eklenti tarafının beklediği iki çekirdek yeteneği. Bir de Relock'un ürettiği kilit dosyası.


1. Upstream 7f629b8 — 55 commit

240 dosya, +20.477 / −910. mirror-upstream'in açık tuttuğu PR #5'in içeriği.

Ne geldi

Lean-to çatı uzantıları ve otomatik yağmur suyu tahliyesi (pascalorg#651, pascalorg#690), Blender tarzı özel mesh düzenleme (pascalorg#638), senkron 2B görüntüleyici modları (pascalorg#672), homojen çoklu seçimde ortak parametre düzenleme (pascalorg#680), eklenti müfettiş kartı uzantıları (pascalorg#667), boş-graf kayıt koruması, duvar hover/tıklama doğruluğunda bir dizi düzeltme (pascalorg#683, pascalorg#686, pascalorg#687, pascalorg#689, pascalorg#697), ve yükleme penceresinde sahneleri silen otomatik kaydı düzelten yama (pascalorg#682).

Otuz beş çakışmanın beşi gerçek çakışma değildi

integration, upstream'in pascalorg#607, pascalorg#608 ve pascalorg#638'ini cherry-pick olarak taşıyor. Git bunu aynı yolun iki bağımsız eklenmesi olarak görüp add/add bildirdi. Beşinin dördü, alındıkları upstream commit'iyle bayt bayt aynıydı; beşincisi tek bir savunmacı ?. ile ayrılıyordu.

git diff origin/integration:<dosya> <upstream-sha>:<dosya>

bunu bir saniyede söylüyor. Dördü için upstream'in yeni hâli doğrudan alındı — upstream o dosyaları o zamandan beri düzeltmiş (pascalorg#686, pascalorg#687), yani "onlarınkini al" burada kesin bir yükseltme.

Korunan fork konumları

UPSTREAM.md'de zaten yazılı olanlar: duvar sistemlerindeki resolveSelectionHighlight thunk'ı ve freezeObjectTransform, üst alan sınırı olmayan depo ölçeğindeki oda testi, eklenti farkındalıklı graph-schema.ts, sahne API'sindeki sahiplik ve düzenleme kirası kontrolleri, output: 'standalone', warehouse pin'i, vendored articraft ve trees workspace'leri.

Yazılı olmayan dört konum vardı; artık yazılı:

Konum Karar
Oda zarfı yükseklik tavanları Upstream 6 m → 20 m yaptı; fork tavanı tamamen kaldırmıştı. Tavan yine tavandır: depo net yüksekliği 10–12 m, high-bay 15'i geçer — ve SliderControl sürüklemeyi değil yazılan değeri de kırpıyor, yani sürüklenerek çizilmiş 120 m'lik duvar Length alanına dokunulunca sessizce 20 m oluyordu (fork PR #17, on üç tavan). Upstream'in yapısal değişiklikleri alındı (kolon yüksekliğindeki !managedByLeanTo sarmalayıcısı), max alınmadı
@pascal-app/plugin-trees pin'i workspace:* kaldı. Github pin'ine geçmek packages/plugin-trees (ve yanındaki articraft) ile ne olacağına karar vermeyi gerektiriyor — kendi build'iyle kanıtlanması gereken ayrı bir iş, upstream merge'inin içinde yapılacak şey değil. Bedeli yazıldı: trees upstream güncellemesi almıyor
scene-loader.tsx yüzen düğmeler Upstream'in Light preview / All scenes bloğu yine düşürüldü. İkisi de çizimin üstünde duran navigasyondu; Scenes rayı ikincisini editörden çıkmadan cevaplıyor, ?disable=postFx hâlâ okunuyor
Site ağacındaki def.tree kapısı Upstream'in getTreeNodeComponent'i alındı (kendi testi onu import ediyor), kapı onun içine taşındı. Upstream koşulsuz geri düşüyor ve bir katın satırı her çocuğunu TreeNode'a veriyor — yani koşulsuz geri düşme roof-segment, stair-segment, kanal/boru bağlantıları ve guide için satır çizerdi

Upstream'in testi geldiği anda kırmızıydı — ve sebebi geçen merge'in kaydında yazıyordu

Boş-graf koruması testi fixture'ını uydurma bir qa:box kind'ıyla kuruyor; gerekçe "yabancı türlü düğüm yalnız BaseNode zarfına tutulur". Upstream'de doğru, burada yanlış: lib/graph-schema.ts bir eklenti kind'ını o eklentinin kendi def.schema'sına, geri kalanı AnyNode'a yönlendiriyor. Uydurma kind reddediliyor, üç kayıt da test edilen korumaya hiç ulaşmadan 400 invalid_request dönüyordu.

Fixture artık WallNode.parse(...) çağırıyor — 2026-08-10 kaydının graph-schema.test.ts'ten çıkardığı kuralın ta kendisi, ikinci kez.

bun.lock ve ilk kırmızı koşu

Upstream plugin-bones'u 85238a8e'ye çıkardı. Kilit dosyası her GitHub tarball'ının sha512'sini tutuyor ve onu yalnız gerçek api.github.com'a ulaşabilen bir makine hesaplayabilir; bu oturumun kapsamı dışındaki depolar için tarball istekleri 403 dönüyor.

Kilit olduğu gibi commit'lendi, CI beklendiği gibi kırmızı oldu — tam olarak bir satırda:

error: lockfile had changes, but lockfile is frozen

Relock bu dal üzerinde dispatch edildi (f3ab6a39), kilidi gerçek bir runner'da yeniden üretti ve dala geri push etti. Sonraki commit onun üstüne bindi.

Bir de denetim listesinden bir madde

@pascal-app/plugin-articraft artık transpilePackages'ta. Ham TypeScript yayınlıyor ("main": "./src/index.ts"), lib/bootstrap.ts onu import ediyor, ve listede hiç yoktu. Bugüne kadar yalnızca bun'ın symlink düzeni gerçek yolunu node_modules dışına düşürdüğü ve Next onu yine de derlediği için çalışıyordu — linker davranışı değişse uyarısız kırılırdı.


2. Dikey açıklık yüzeye göre cevap versin

Bir katın döşemesi ile tavanı katın iki ucunda durur. 1–3 katları arasında çalışan bir kuyu, döşemelerden 2 ve 3'ü, tavanlardan 1 ve 2'yi keser — aynı küme değil.

VerticalOpeningConfig.servesLevel yalnız kat kimliğini görüyordu, yani bir kind iki yüzeye tek cevap veriyor ve bir uçta zorunlu olarak yanılıyordu: döşeme mantığıyla en alt katın tavanı kuyunun önünde kapalı kalır, tavan mantığıyla en üst durağın üstündeki tavanda olmaması gereken bir delik açılır.

Host asansörünün kendi kodunda bu yüzden hep iki ayrı yüklem vardı (shouldApplyElevatorToSlab / shouldApplyElevatorToCeiling); eklentilere açtığımız yetenek tek yüklem veriyordu. Yüzey türü de geçiriliyor — senkronizasyon zaten yüzey düğümünü elinde tuttuğu için maliyeti tek argüman (surface.type).

Yeni test yüzeye göre ayrım yapan bir kind kaydediyor, aynı katta hem döşeme hem tavan bulunan bir sahne kuruyor, döşemenin kesildiğini ve tavanın kesilmediğini doğruluyor. Argümanı geçirmeden koşunca gerçekten düşüyor:

=== argüman geçirilmeden ===
(fail) verticalOpening capability > lets the kind answer per surface
 7 pass, 1 fail
=== geçirilerek ===
 8 pass, 0 fail

3. Bir kind kendi sürüklenmesini de denetleyebilsin

floorPlaced.collides uzamsal ızgaranın plan dikdörtgenidir: yalnız XZ, hiç yükseklik yok. Zemindeki mobilya için doğru, kullanılabilir hacmi çoğunlukla hava olan her şey için yanlış — ızgaraya göre tünelli bir gözün altındaki yürüme yolundan geçen bir konveyör ile dikmelerinin içinden sürülen bir konveyör aynı şeydir. Böyle kind'lar collides'ı kapalı tutmak zorunda.

Kapalı tutmak sürükleme denetimini tümüyle kapatıyordu. recomputeValidity'yi süren şey boxDimensions ve o yalnız collides'a bağlıydı; yani böyle bir kind üç boyutta denetleyen bir kapıdan geçip yerleşiyor, sonra hiçbir şey sorulmadan doğrudan çeliğin içine sürüklenebiliyordu. Yerleştirmenin reddettiğini sürükleme kabul ediyordu.

movable.canMoveTo kind'ın kendi cevabı: sürüklemenin her fare hareketinde koşuyor ve ızgaranınkinin yerine geçmiyor, onunla birleşiyor. Bildirmek yeşil/kırmızı kutuyu da açıyor — görünür bir sebep vermeden bir bırakmayı reddetmek, nesnenin öylece kımıldamayı reddetmesi gibi okunur; Alt zorluyor, tam olarak çarpışan bir kind'da olduğu gibi. Hiçbirini bildirmeyen kind eskiden beri sahip olduğu düz ok imlecini koruyor — çıkarılan showsValidityBox tam olarak bunun üstünden test ediliyor.

Eklenti tarafı ikisini de kullanıyor: ovurrsl/plugin-warehouse#34 — kat delikleri, sürükleme kapısı ve asma katın kendi çarpışması. O PR yeşil.


Kapılar

bunx biome check .    temiz
bun run check-types   burada bağımlılıklarını çözebilen her workspace'te temiz
                      (yalnız @mint/pascal-plugin, plugin-bones, plugin-streetscape
                       "modül bulunamadı" — proxy 403'ü, kod değil)
bun run test          16/16 görev yeşil

Merge sonrası, yayınlamadan önce

UPSTREAM.md'nin "Before you publish: keep a way back" bölümü geçerli: canlı deploy'un build edildiği sha'yı (son başarılı Deploy bundle koşusunun head_sha'sı — integration'ın ucu değil) bir rollback dalı olarak itin.

Aymericr and others added 30 commits August 10, 2026 13:14
* Add roof surface placement support for items

Items (e.g. solar panels) can now be placed on sloped roof surfaces.
The placement system computes euler rotation from the roof surface
normal so items sit flush on the slope instead of going inside.

- Add roofStrategy to placement-strategies with enter/move/click/leave
- Wire roof:enter/move/click/leave events in the placement coordinator
- Add calculateRoofRotation in placement-math using surface normals
- Support full 3D cursor rotation for sloped surfaces
- Items on roofs are parented to the level with world-space rotation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fixed conflict

* fix: reconcile room surfaces with wall topology

* fix: index room topology reconciliation

* fix: address topology reconciliation review

* test: broaden topology equivalence coverage

* test: cover customized surface split in editor

* style: format wall topology regression test

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(editor): add Streetscape plugin to Pascal

Register @pascal-app/plugin-streetscape (pinned by commit) alongside the
trees and mint plugins: plugin discovery + host panel at bootstrap, Next
transpilation, and Tailwind @source so its panel styles compile.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(editor): credit the Streetscape plugin's actual author

Override creator/pluginUrl at registration — the upstream manifest points
at a pascalorg repo that doesn't exist.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore(editor): bump Streetscape pin to webp thumbnails

Upstream converted the 37 PNG catalog thumbnails (~17 MB) to webp/svg
(~630 KB), bringing the packed CLI back under its release budget, and
fixed pluginUrl/repository metadata — keep only the creator-name
override.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…calorg#630)

* fix(editor): keep non-terrain walls plane-bound on creation

The terrain drafting flow stamped the ghost's explicit height and a
supportOffset onto every wall whose frozen construction plane sat above
y=0 — i.e. any wall started on a slab or deck, not just ground-hosted
terrain chains. Such walls stopped following the level height and no
longer re-elected their base from slab support.

Gate the stamping on a ground-drafted plane (the terrain exception), and
re-resolve the aimed support surface per commit for non-ground chains so
a later segment still elects the slab it visibly crosses instead of
being capped at the first click's elevation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(nodes): rework wall panel around plane-bound top

Top: "Follows level" (no explicit height, shows the resolved height) vs
"Custom height" (seeded from the effective height so geometry doesn't
jump on detach). Bottom: the terrain infill toggle no longer materializes
an explicit height, so toggling it can't silently detach the wall top
from the storey plane.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(editor): keep flat-ground wall drafts plane-bound

Pointing at bare ground freezes a GROUND construction plane, and the
commit path treated every ground-preferred draft as a terrain chain:
stamped ghost height, persisted ground host, election capped at the
draft plane. On flat ground (no sculpted terrain) all three are wrong —
the wall showed "Custom height 2.50" instead of following the level,
and a slab drawn later could never lift it, leaving the wall buried in
the slab (z-fighting band at the base).

Gate the terrain exception on terrainSupportLift(): ground-preferred
drafts with no terrain support drop their draft options entirely and
commit plane-bound. The terrain-freeze test now seeds a real terrain
field; a new regression test pins the flat-ground shape.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(nodes): restore full plane-bound defaults from the follows-level toggle

Switching Top back to "Follows level" only cleared the stored height,
so regression-era walls (stamped ground host + draft offset) kept their
base pinned at the level floor, still buried in any slab. The toggle now
also drops the draft offset, and drops a ground host when no sculpted
terrain supports it — giving existing broken walls a one-click repair.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(nodes): carry the base repair through the bottom Auto toggle

The follows-level toggle repairs regression-era walls, but walls whose
Bottom already displays "Auto" while secretly ground-pinned had no
clickable path that kept their custom height — the pin isn't reflected
in the control, and the base stayed buried in the slab (z-fighting its
side faces). "Auto" now performs the same re-election repair (drop the
draft offset, drop a terrain-less ground host), and the segmented
control fires on already-selected clicks, so clicking "Auto" itself
heals the wall.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(core): self-heal buried ground pins in the vertical canonicalization

Regression-era walls carry supportSlabId 'ground', which short-circuits
slab election: the base stays at the level floor, buried in the room
slab and z-fighting its side faces, while the panel truthfully-but-
uselessly displays the base as automatic. Users shouldn't have to click
anything: the load-time canonicalization now strips the pin (and draft
offset) when the un-pinned election would land the wall on a slab its
base is currently embedded in.

Deliberately narrow so the one legitimate flat-scene ground pin
survives: a wall kept on the ground under a hovering deck is not buried
(the deck's occupied interval sits above the base) and keeps its pin,
and scenes with sculpted terrain are skipped wholesale — the pin is
load-bearing there and the live terrain field isn't visible to this
pure, authority-shared pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
pascalorg#639)

Viewport and area crop modes captured at the raw canvas backing-store
resolution, so retina/5K displays produced multi-MB WebP snapshots that
blow past upload transport limits. Clamp the output long edge to 2048
(matching the 1920-class standard presets) in the snapshot pipeline, the
non-pipeline fallback, and the capture HUD resolution chip.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ries (pascalorg#641)

A noPanel tab highlights in the rail and activates without opening the
sidebar panel column, for host surfaces that swap the stage instead
(e.g. the hosted item builder). Mobile layout skips noPanel tabs.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…rg#642)

Level height, wall custom height, ceiling custom height, and column
height were all UI-capped at 6m, which rules out tall single-space
builds (climbing gyms, warehouses, halls). 20m covers the tallest
realistic single-storey uses — lead-climbing walls top out around
15-18m — while keeping the sliders usable in the common 2-4m band.
The caps were never enforced in core schemas; only the controls change.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…org#643)

* fix(editor): derive the wall placement box from the wall-local point

The placement wireframe and the item it previews were computed from two
different quantities: the node position snapped the hit in WALL-LOCAL
coords, while the cursor snapped the raw WORLD hit independently per axis.
Those are different lattices — wall-local X runs from `wall.start`, and
wall-local Y is measured from the supporting slab's elevation, not world
zero — so the box drifted off the item by the slab elevation plus up to a
grid step, and the commit (which follows the node) landed where the box
was not.

`calculateCursorRotation` also returns the wall face's yaw + π, which is
invisible for the z-symmetric door/window boxes it was written for but put
the asymmetric `wall-side` item box on the far side of the wall. The
coordinator already compensated for that π when publishing the 2D
floorplan preview; that workaround goes away with the cause.

Both now come from `resolveWallPlacementPose`, which maps one wall-local
point through the hit object's frame — the exact inverse of how
`localPosition` was measured. `z` follows the hosting convention rather
than the hit depth, so `wall-side` items store the face they mount on
(matching ItemSystem's per-frame push, no first-frame pop) and `wall`
items stay centred in the thickness instead of being snapped out of a
thick wall.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(editor): stop floor-stacking hosted nodes during a drag

`FloorElevationSystem` documents that it "respects `floorPlaced.applies` so
items with `asset.attachTo` (wall / ceiling mounted) are left alone", but it
never called the predicate — only `getFloorPlacedElevation` did.

For an opted-out node that resolver returns 0, so the system's write
degenerated to `mesh.position.y = position[1]`. Harmless while `position` is
the node's own host-local store value, but tools publish live transforms in
WORLD space: the moment a wall item started moving, the system copied its
world Y into the mesh's wall-local Y slot every frame and the ghost floated
off the wall by the host frame's elevation. Hence the reported sequence —
correct on `wall:enter` (no live transform yet), wrong throughout the move,
correct again after commit (the coordinator clears the live transform first).

Honour `applies` before the write. Covers the other three opt-outs too:
ceiling items, cabinet modules inside a run, and non-floor duct terminals.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…one editor (pascalorg#649)

* feat: wire Bones plugin (structural framing) into the standalone editor

Third-party plugin by @Snoopy147 — github:Snoopy147/plugin-bones.
Registered like streetscape/mint: static import in bootstrap,
transpilePackages entry, pinned-commit dependency.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: Bones → pascalorg/plugin-bones — the full engineering X-ray

All systems online: wall framing, CMU, floor, roof, foundation,
electrical, plumbing, HVAC, jurisdiction profiles (51 states),
takeoff. 191 plugin tests green; verified live in this editor.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: Bones verified build — hip rafter orientation, switch guard, skip-override plumbing seam

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: bump Bones pin

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: bump plugin-bones to e1ee7c0 (all 8 systems at LOD 400 build)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: bump plugin-bones to ba4d5af (X-ray depth fix + flush foundation corners)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: bump plugin-bones to 1edd562 (depth-clear sentinel X-ray)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: bump plugin-bones to cda167b (WebGPU-safe depth-wipe X-ray)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: Bones ships opt-in — defaultInstalled false, enable per scene from the Plugins panel

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: bump plugin-bones to c61669e (round-3 build: all round-2 blockers)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: lockfile refresh for plugin-bones c61669e

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: bump plugin-bones to 1bb10bb (round-3 fixes)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: bump plugin-bones to f79efd4 (round-4 fixes)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: bump plugin-bones to 7c2801d (round-5 fixes)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: bump plugin-bones to c99a3c5 (round-6 fixes)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: bump plugin-bones to fd78253 (round-7 fixes)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: bump plugin-bones to c5a3606 (round-8: 90-degree girder fix)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: bump plugin-bones to a480fd8 (round-9 fabrication exactness)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: sort the plugin-bones import

Biome's organizeImports assist wants it after @pascal-app/nodes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Julien Brissonneau <julien.brissonneau@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…pascalorg#650)

* fix(editor): drop the stale 6 m caps left over from the 20 m storey raise

`pascalorg#642` raised the room-envelope caps from 6 m to 20 m but missed two
sliders, so a tall storey is still unusable in practice:

- ceiling panel's non-level-parent fallback stayed at 6 m while
  `definition.ts` uses `POSITIVE_INFINITY` for the same case — the panel
  and the 3D drag handle disagreed on the same bound.
- slab elevation (Surface/Floor and Base/Rim) stayed at 6 m, so a slab
  could not be raised past 6 m inside a 20 m level. `clampSlabElevation`
  in the write path is the real bound; the slider was just a UI ceiling.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(editor): make the ceiling's level constraint visible instead of silent

A ceiling's cap is the storey plane (`getCeilingClampBound`), so on a
stock 2.5 m level it is 2.49 m — which means the "Standard (2.5m)" and
"High (3.0m)" presets clamped silently and read as dead buttons, with
nothing pointing at the level height as the real gate.

Presets taller than the level are now disabled with the available height
in their tooltip, and the Height section names the bound. The clamp
itself is unchanged: a ceiling still never pokes through its own storey.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
… engine fixes) (pascalorg#653)

Rendering: the X-ray now renders as a solid scene-layer pass plus a
ghost overlay pass — it survives orbit/zoom (the depth-wipe approach
silently never worked in the TSL pipeline) and no longer paints over
foreground objects like nature-plugin trees, which now occlude and
shade the skeleton like real geometry.

Engines: repo-wide interpenetration gate + the geometry it caught
(ridge-face rafter bearing, fascia outside tail cuts, dropped gable
ends, sistered ties, wall corner/tee run insets, oblique foundation
corners, per-bay floor blocking, live girder end-bearing validation,
d/4 birdsmouth cap, purchasable 1x8 fascia, CMU grout field, HVAC
square-trunk takeoff, LA-1/GA-1 circuit pins). 408 tests + tsc green.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
… copy) (pascalorg#655)

Adds the 'Save full plans' button at the bottom of the Bones sidebar —
a print-ready LOD 400 plan set (per-system plan sheets + schedules,
Print -> Save as PDF). Trims the catalog description to stay
high-level.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ay, mitered blueprints, exterior detection) (pascalorg#656)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ough openings (pascalorg#657)

Prod-reported bug: wires bored through low windows, boxes on doors.
Routing now detours over headers/under sills for ANY opening crossing the
drill plane; anchors/junctions/switch/panel snap out of ROs. Gated by
electrical.openings.test.ts + visually verified (QA E1 PASS). Also:
blueprint round-1 follow-ups (shared sheet transform, circuit-ID labels,
GEN palette spread, callouts, even pagination, link-wall footings).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…org#658)

Two-storey prod bug: the roof (and any cross-level system) rendered at
ground level. Members are level-local; cross-level roofs now shift by the
host's exact storey stacking (per-building baseY incl. baseElevation,
default height 2.5). One X-ray per building frames ALL roof-bearing
levels; per-building ground detection and floor-to-floor datum. Verified
by adversarial code review (two rounds, all findings fixed) + in-browser
alignment with the host-drawn shell to the pixel. 457 tests green.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ode mounting) (pascalorg#659)

* fix(viewer): frame-rate-independent level elevation smoothing + bones 4cd28a0

The storey lerp factor delta*12 exceeds 1 past ~166 ms frame times — the
recurrence then AMPLIFIES the error each frame and levels oscillate
kilometers off-screen (exploded mode 'not separating', fit-camera chasing
a runaway level = blank viewport). Proven by per-frame instrumentation:
y series 0 → -179.8 → -587.9 → peak ±1309 m under load, while a light
scene damps 11.6 → 7.500. exp(-12*delta) keeps the factor in (0,1) for
any delta; converged values snap exact.

Bones 4cd28a0: cross-level roof framing mounts into the roof LEVEL's
Object3D (tagged level-local members, no baked storey offset) so stacked/
exploded/solo apply natively — trusses ride the roof level's exploded
slot and hide when soloing other floors. Plus electrical RO edge
clearance for the panel and Alpha branding.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* clamp instead of exp: preserves the exact-snap contract at delta>=1/12

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ctural set + characteristics (pascalorg#660)

Placed-fixture plumbing (meter, water heater, hot/cold homeruns, DFU-sized
sloped DWV to a sewer exit) hardened through six adversarial verify rounds
(14 defect classes fixed + gated, incl. cross-trade separation and
rough-opening avoidance). Blueprint set grows cover/elevations/section
with round-2 examiner fixes. Building-characteristics drawer + CSV +
sheet block. Under-slab DWV ghosts through the floor in X-ray.
513 plugin tests green.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…H/water/sewer/power) (pascalorg#661)

One-click 'Place service points': five labeled, selectable, movable
equipment nodes (panel with bolt sign, WH, water valve, sewer stub,
power weatherhead). Wires and pipes re-derive around wherever they move;
overrides are authoritative with NEC 110.26/RO warnings; two adversarial
verify rounds (8 defects fixed + gated). 559 plugin tests green.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ded connectors (pascalorg#662)

Sections with true cut poché (filled plane-slices, legible on oblique
geometry), width-aware circuit-label de-collision, grid-based roof
coverage flag, full flag-list wrap, butt-cap linework; braided supply
connectors for off-wall fixtures (RO-flagged, parts-counted). Examiner
round-3 fix-check + connector skeptic PASS; 585 tests green.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ter after mount (pascalorg#665)

* fix: re-derive selectable-kind subscriptions when plugins register late

Plugin kinds register through async dynamic-import discovery, AFTER the
selection managers mount and snapshot getSelectableKinds() into their
emitter subscription lists. In prod (community bootstrap fetches plugin
chunks over the network) a plugin node kind therefore never gets hover /
click / double-click subscriptions unless an unrelated effect dep
happens to change — user-visible as "windows outline on hover, plugin
service boxes don't".

Fix: the registry bumps a monotonic version + notifies listeners on
every registerNode/_reset; a new useRegistryVersion() hook
(useSyncExternalStore) feeds that version into the dependency arrays of
every effect that snapshots the kind list (5 in the editor
SelectionManager, 1 in the viewer SelectionManager), so a late plugin
load rebuilds the subscriptions.

Gates: registry version bump + listener notify + unsubscribe, and
loadPlugin notifying per kind (packages/core registry.test.ts).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* read registryVersion in effect bodies (biome exhaustive-deps)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* biome format

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…le exterior, attic HVAC, service UX (pascalorg#666)

Exploded view reads floor/trusses/shell as strata; gable walls on roof
levels wear the full exterior stack; ducts route in the attic with
ceiling grilles (never through plate bands — new M1 gate); thermostat,
heat pump and electric meter join the movable service points (street →
meter → panel cable on the wall graph, RO-aware); door-style drag +
per-wall engineering card; five verify rounds, every confirmed finding
fixed + gated. 668 tests green.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…nspector) (pascalorg#667)

* feat(core): plugin inspector-card extensions in the registry

Plugins can now contribute sections to the floating node inspector card:
`Plugin.inspectorExtensions` declares { id, pluginId, kinds, icon, title,
component } entries; `loadPlugin` fans them out into a per-kind map read
via `getInspectorExtensions(kind)`. Same-id re-registration replaces in
place (dev HMR), the test reset clears the map alongside
`pluginIdsByKind`, and registration bumps the registry version so
late-loading plugins re-render open inspector cards.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(editor): render plugin inspector extensions on the floating card

The inspector card wrapper self-resolves the single selected node
(viewer selection + scene nodes — customPanel kinds render their own
PanelWrapper, so this is the one spot every card flows through) and,
for each registered + installed InspectorExtension of that kind:

- folded card: a 16px icon button in the header, left of the collapse
  chevron — click expands the card straight to the extension's section;
- expanded card: a collapsible PanelSection after the kind's own
  controls (lazy + Suspense + WeakMap-cached like resolveCustomPanel,
  wrapped in an error boundary), auto-expanded when jumped to via the
  header icon and collapsed again once the card folds.

Install gate mirrors use-plugin-panels (useScene installedPlugins);
extension lists re-derive on the registry-version bump so async plugin
loads surface on already-open cards.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ontrols (pascalorg#668)

UX correction to pascalorg#667 after user review: clicking an extension's header
icon used to uncollapse the card and APPEND the extension as a section
after the kind's own controls. The card now has two mutually exclusive
expanded modes:

- chevron / header press → ONLY the kind's regular controls (no
  extension sections appended); pressing again folds as before;
- extension icon → ONLY that extension's content fills the card body
  (its own section chrome), regular controls hidden. The icon shows an
  active (cyan) state while its mode is on; pressing it again — or the
  chevron — returns to the regular controls without folding.

The transition table lives in lib/inspector-card-mode.ts (pure, gated by
mode-toggle tests encoding the either/or contract, incl. the stale-id
fallback to regular when the selection kind changes or the plugin is
gated off). Collapse semantics, drag, the shared
desktopInspectorCollapsed reset machinery, the installedPlugins +
registryVersion gates and the lazy/Suspense/ErrorBoundary loading are
unchanged. The mobile sheet (no header icons) keeps pascalorg#667's appended
sections. Core InspectorExtension docs updated to the new contract.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…eering card, mixed CMU/framed walls (pascalorg#669)

Three-layer exploded view works with the X-ray on the roof level (render-
only mount tag; elevations unaffected); wall-card Engineering section
(either/or modes via pascalorg#667+pascalorg#668); mixed CMU/framed walls: course-snapped
seam, PT sill + R403.1.6 bolts, zoned openings, corner butt joints incl.
width-aware acute retreats — six verify rounds across the batch, every
confirmed finding gated. 721 tests green.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…-3 verify round (pascalorg#670)

* chore: bump plugin-bones to dc1daf3 — cladding both-modes fix + verify round S7/S8/S9

- Cladding choice now reads in BOTH render modes: per-family X-ray member
  colors (brick/EIFS emitted NOTHING before) + solid-mode wall repaint via
  slots.exterior (library textures / minted flat scene materials).
- Batts compress into the layer cavity (0.15m zone-5 interpenetration fix)
  with an aggregated 'compressed' flag; INTL R fallback parity.
- Colinear dedupe now merges duplicate twins' openings (studs framed
  around doorways that only the dropped twin carried).
- Explicit stud overrides too deep for the drawn wall warn on both
  Engineering surfaces.
- Plugin dev-dep types bumped to @pascal-app/*@0.9.2 (slots API).
779 plugin tests + tsc green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* bump to f308cf36 — verify round 2: air gap spacing, misfit tolerance, single-undo cladding pick

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Snoopy147 and others added 18 commits August 18, 2026 01:21
…ndensers, cavity-fit framing (pascalorg#676)

* chore: bump plugin-bones to 05ca742 — night-4: movable outlets, AC condensers, cavity-fit framing, E4

- Movable outlets (user ask Q7): every derived receptacle/switch is
  hoverable + draggable along its wall like a door; code-aware snapping
  (RO avoidance, stud-adjacent mounting with blocking members, height
  clamps, NEC 210.52 spacing advisory); wires re-route on release.
- AC condensers (user ask): 1-N units sized by conditioned area with a
  climate-zone divisor, concrete pads outside an exterior wall,
  refrigerant line-sets to the air handler, NEC 440.14 disconnects.
- Cavity-fit framing: S1 same-wall interpenetration class retired —
  framing geometry compresses to the drawn wall (labels/takeoff stay
  nominal), one aggregated flag per class.
- E4 closed: no wire crosses room air at living height — island hops
  cross above EVERY wall; buried island feeders.
- Selected wall exempt from the dollhouse cut (finishes readable from
  any angle, dedupe-twin aware).
855 plugin tests + tsc green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* bump to 6e7dcfa4 — batch-round fixes (pad alignment, twin overrides, batt notching) + outlets experimental flag (dash ids, seeding off by default)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* bump to 8f40d06b — truthful unframed fallback + non-vacuous F3 gate (flag-gated paths)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
… seed parity, disconnect RO slide (pascalorg#677)

- AC condensers get their dedicated circuits: panel → disconnect
  homeruns (30A/10AWG ≤3 tons, 40A/8AWG above) with honest schedule +
  legend ratings and a collision-free circuit color family.
- Heat-pump service node seeds at the engine's slid anchor (sign
  stands ON the unit); inside-corner and on-wall-anchor edge cases
  guarded.
- The disconnect slides clear of fronting window/door ROs (was: box
  mounted on the glass, silently); unclearable cases warn.
Loop-verified: two adversarial rounds; visual PASS at 859e156a (the
delta to this sha is the numerically-gated color config + row text).
865 plugin tests, tsc clean.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Add roof surface placement support for items

Items (e.g. solar panels) can now be placed on sloped roof surfaces.
The placement system computes euler rotation from the roof surface
normal so items sit flush on the slope instead of going inside.

- Add roofStrategy to placement-strategies with enter/move/click/leave
- Wire roof:enter/move/click/leave events in the placement coordinator
- Add calculateRoofRotation in placement-math using surface normals
- Support full 3D cursor rotation for sloped surfaces
- Items on roofs are parented to the level with world-space rotation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fixed conflict

* feat: add Blender-style custom mesh edit mode

* fix: complete custom mesh edit mode

* feat: refine custom mesh editing experience

* fix: support elevated placement and refine mesh editing

* fix: make surface placement registry-driven

* feat(nodes): add custom mesh face materials

* fix(nodes): refine custom mesh face materials

* fix(editor): address custom mesh review findings

* feat: improve custom mesh editing and material slots

* feat: support items on custom mesh faces

* fix(editor): reject wall attachments on sloped custom mesh faces

* Rename custom mesh to block

* Move block face placement behind face host capability

* Add modal uniform scale for block editing

* test(editor): keep the block face host registered across suites

`registerTestBlockFaceHost` skipped registration whenever any `block` kind was
already present in the shared node registry. The wall drafting suite registers
its own floor-placed `block` stub, which has no `faceHost` capability, so when
it ran first the face-host stub was never installed and every face placement
resolved to null — 7 tests failing by file order alone.

Renaming `custom-mesh` to `block` is what made the two stubs collide.

Gate on the capability rather than the kind name, replacing a registered
`block` that cannot host faces.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Wassim SAMAD <wass08@gmail.com>
…tion (pascalorg#680)

Selecting several nodes of the same type now shows one inspector for their shared parametrics.groups fields, with mixed-value controls, a single undo step, and wall/ceiling follows-level vs custom height.

Co-authored-by: Cursor <cursoragent@cursor.com>
…g#682)

* fix(editor): stop read-only sessions from wiping scenes with an empty autosave PUT

Root cause of the 2026-08-16..18 scene-wipe class (a4993ec9f1ab,
1befee38f973, reproduced live): useAutoSave's store subscription attaches
before the Editor's scene-load effect (hook order), so useHostPanels'
mount-time default-installedPlugins sync marks the session dirty with zero
user edits while the store still holds the empty pre-hydration state. The
load effect then runs unloadScene(), whose transient 0-node write
re-baselines the wipe guard to 0 via trackLoadedGraph. Any effect cleanup
in that window (StrictMode simulated unmount in dev, tab close or
navigation in prod) runs flushOnExit, which checked only the dirty flag —
it serialized the empty store and PUT it with If-Match: 1, leaving v2 with
0 nodes.

Defense in depth, all three layers:
- use-auto-save: isLoadingSceneRef now starts true (autosave arms only
  after the first hydration completes), and the exit flush is decided by
  the pure decideExitFlush(), which skips any flush while a load is in
  flight — the store content in that window is transient, not user data.
- scene-loader: tracks the server's known node count (initial meta, PUT
  responses, SSE events) and refuses to PUT a 0-node graph over a
  populated server copy, with a console.error; 409 empty_graph_rejected
  responses surface as a save error instead of the conflict banner.
- PUT /api/scenes/[id]: rejects a 0-node graph aimed at a scene that has
  nodes with 409 empty_graph_rejected unless the caller passes
  force: true. A silent wipe is unrecoverable in place; an intentional
  full deletion is rare and still available via force (and every version
  stays in scene_revisions).

Gates: decideExitFlush matrix incl. the exact traced wipe sequence,
empty-graph-guard unit tests, and a route-level integration test running
the real PUT handler against a temp SQLite store (409 body, store
untouched after rejection, force path, empty-over-empty allowed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: seed the wipe-guard fixture through the raw store

CI resolved '@pascal-app/mcp/operations' to a build without saveScene
(turbo-cached dist) — the store's save() is the stable primitive the
operations layer delegates to anyway.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: guard file must sort before scene-store-server's module mock

bun's mock.module leaks process-wide to later files in the worker —
scene-store-server.test.ts stubs '@pascal-app/mcp/operations', which
starved the PUT-guard fixture of saveScene/loadStoredScene in CI
(single worker). Renamed to sort first + hazard comment.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: restore the real mcp modules after scene-store-server's mocks

bun's mock.module poisons the registry for every later file in the
process — downstream route tests saw a stub facade without
saveScene/loadStoredScene in CI. Capture + restore in afterAll.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: inject real store/operations via relative source imports

Other test files' mock.module stubs on '@pascal-app/mcp/*' stick for
later dynamic imports on linux — three CI runs starved the route
fixture of saveScene/loadStoredScene while macOS passed. The fixture
now builds a real SqliteSceneStore + facade from relative source
paths (immune to subpath mocks) and injects them via a test-only
setter.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* style: biome format

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
A wall hidden by the wall-mode pass ('down' mode, cutaway-hidden faces,
auto-mode interior-interior partitions) still raycasts at full height via
its invisible collision mesh, and the selection path stopPropagation's on
it — so clicks aimed at VISIBLE objects standing behind the hidden wall
(wall-mounted plugin device/service boxes in X-ray mode, items) select an
invisible wall instead. Night-5 evidence: arming a receptacle drag on the
demo scene's south wall selected wall_x86… two meters in front of it
(raycast hit at 1.03m vs the device at 3.93m), and the follow-up click
committed an accidental WALL move plus its auto slab/ceiling/zone sync.

WallCutout now stamps userData.wallHidden with the effective hide state,
and the wall renderer's pointer handlers early-return (no emit, no
stopPropagation) while it's set — R3F continues to the next intersection,
so hover/select/click pass through to whatever the user actually sees.
Delete mode keeps the events: hidden walls stay hover-targetable for the
deleteInvisible highlight flow.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
… tee guards, examiner fixes (pascalorg#684)

- Movable outlets default ON: every receptacle/switch drags like a
  door with exact single-undo (the drag-commit cascade that woke
  space-detection mid-commit is gone; pairs with pascalorg#683's
  pointer-transparent hidden walls).
- Tee-junction guards: parallel-wall false tees filtered, width-aware
  oblique retreats in all three consumers, junction honesty warnings.
- Blueprint examiner round: circuit legend never truncates (second
  column), condensers tagged CU (not AH), GEN color walk no longer
  wraps into the lighting band.
878 plugin tests, tsc clean.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…pickable (pascalorg#686)

* fix(editor): stop node tops lifting floor placements off the ground

pascalorg#638 made the pointer support election consider node tops by default —
`options?.includeNodeTopSurfaces === false` opted out, so every caller that
passed nothing (item placement, registry move/presets, slab drafting) started
electing them. It also gave `item` a `surfaces.top`, widening the candidate set
from wall/item/column to wall, slab, ceiling, cabinet, column, item, shelf,
block.

A ray aimed at a floor crosses every upward-facing face above that floor first.
In a finished room that is the ceiling: placing an item at the room centre
elects the ceiling's top face (nearest hit, normal.y ≈ 1) and freezes it into
the draft's authored Y via `resolveFrozenFloorPlacementPatch`, so the item sits
at ceiling height instead of on the floor. Walls the ray passes over do the
same in a narrower band.

Restore the opt-in. Keep pascalorg#638's registry-driven discovery — the kind list is
still derived from `capabilities.surfaces.top` rather than hardcoded — but only
the tools that build ON a surface ask for it: wall (already did), column, fence,
stair, block. Item placement, registry move and slab drafting go back to
placing against the floor the pointer indicates.

Also exclude the node the active interaction is placing or moving. Its mesh
rides the cursor, so electing its own top would raise it by its own height every
pointer move. The tools neuter the dragged mesh's `raycast` for their own
pointer routing, which happens to cover this today — but that is each tool's
private convention, and async-mounted item children are only neutered on the
next frame. The election owns the invariant now.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(viewer): keep batched walls answering the pointer

pascalorg#608 sews a level's walls into one mesh once they settle (8+ walls, 180ms
quiet). Each sewn wall is moved off SCENE_LAYER onto BATCHED_LAYER by
`hideBatchedWall` so it costs no draw call while staying in the graph — with
its R3F pointer handlers still attached.

R3F picks with one shared raycaster whose default mask is SCENE_LAYER alone, so
a batched wall stops being hit: no `wall:enter` (no hover outline, no paint
preview), no `wall:move`, no `wall:click`. Selection is the circular case — a
selected wall leaves the batch, but the click that would select it never lands.
pascalorg#608 saw this for measurement and added `setSurfaceRaycastLayers` for the
raycasters that module builds; the shared event raycaster was never opted in.

Enable BATCHED_LAYER on it. Additive rather than `setSurfaceRaycastLayers`,
which resets the mask — right for the private per-query raycasters it was
written for, wrong for the one every pointer event goes through.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
pascalorg#687)

* fix(nodes/wall): let a hovered wall out of its batch so it can outline

Hover feedback for a wall is an outline, nothing else: `SelectionMaterialSync`
skips walls outright and the cutaway pass tints only a selection or a delete
hover, so `default`, `paint-ready` and `paint-disabled` all reduce to
`outliner.hoveredObjects` and a colour.

`MergedOutlineNode` draws that mask with `renderer.render(scene, camera)` on the
main camera, which enables no `BATCHED_LAYER`. Once pascalorg#608 sews a level's walls
(wall mode `up`, 8+ walls, 180ms quiet) every wall on the floor is on that layer
alone, so it reaches neither the depth pass nor a mask pass and hovering it
lights up nothing at all. Selection looks fine only because `collectTintedWalls`
already pulls a selected wall back out of the batch — the click lands, the wall
starts drawing itself, and the outline appears. Hover had no such release, so on
any finished floor the wall you point at goes dark: no hover outline in select
mode, and in paint mode no preview of the surface the next click will paint.

Release on any hover, not just a delete one. Same mechanism, one more reason:
selection and delete tint through materials the merged mesh never reads, a plain
hover outlines through a camera that never sees it, and both are fixed by the
wall drawing its own geometry while it is lit. One wall is lit at a time, so the
cost is the one extra draw call the delete path already accepted.

Verified on a 12-wall level in the community editor: before, hovering a wall in
`Full height` produced no outline in select or paint mode while `Cutaway` (where
batching stands down) outlined correctly; after, both modes outline in
`Full height`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* chore: apply biome formatting to the hover test

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…#651)

* Add roof surface placement support for items

Items (e.g. solar panels) can now be placed on sloped roof surfaces.
The placement system computes euler rotation from the roof surface
normal so items sit flush on the slope instead of going inside.

- Add roofStrategy to placement-strategies with enter/move/click/leave
- Wire roof:enter/move/click/leave events in the placement coordinator
- Add calculateRoofRotation in placement-math using surface normals
- Support full 3D cursor rotation for sloped surfaces
- Items on roofs are parented to the level with world-space rotation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fixed conflict

* feat: add wall-hosted lean-to roof extensions

* feat: refine lean-to roof extensions

* fix: refine lean-to wall and eave junctions

* feat: automate roof gutters and downspouts

* fix: address roof drainage review findings

* fix: coordinate roof extensions with drainage

* fix: complete lean-to extension controls

* fix: complete lean-to extension behavior

* fix: refine lean-to appearance and tree

* fix: preserve resize values and restore roof icons

* Fix lean-to shed roof side infill

* Lock managed lean-to roof affordances

* Use selection proxy for lean-to managed roofs

* Bend curved lean-to extensions as a concentric annular band

Represent the host wall's curve as a true-arc descriptor (center + radius)
in each managed node's local frame, replacing the scalar sagitta that was
re-derived through three synthetic chords and ballooned the deck into a
filled disc. Every consumer — deck, gutter, downspout outlet, posts/beam/
rafters, and the 2D floorplan — now sweeps one shared concentric circle, so
the canopy hugs the arc as a thin band for any extent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix curved wall shed geometry

* align shed extension with architecture

* fix lean-to corner joins and curved gutters

* fix mesh editing snap configuration

* fix floating corner post and rafter on curved lean-to joins

Bend the shared corner post and corner rafter onto the wall arc like
every other member, instead of placing them at raw flat arc-length
coordinates. Identity for straight walls.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix lean-to gutter/downspout edge cases from review

Address Cursor Bugbot findings: curved-gutter downspout mapping and
prescribed eave-Y, nearest-gutter reuse on segment refresh, asymmetric
overhang reach in roof-edge attachment, manual-mode no longer silently
reattaches, terrain/slab changes now re-trigger lean-to reconcile, and
multi-node patches derive from the pre-patch node.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix biome check formatting and import order

Resolve the quality CI failure: apply Biome formatter to the downspout
and gutter-refresh edits and sort the lean-to system imports.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* raise timeout on lean-to gutter corner angle sweep test

The test assembles and renders a full gutter joint across ~127 angles,
running ~5s and tripping bun's 5000ms default per-test timeout on slower
CI hardware. Give it an explicit 30s budget; assertions are unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…#690)

* Add roof surface placement support for items

Items (e.g. solar panels) can now be placed on sloped roof surfaces.
The placement system computes euler rotation from the roof surface
normal so items sit flush on the slope instead of going inside.

- Add roofStrategy to placement-strategies with enter/move/click/leave
- Wire roof:enter/move/click/leave events in the placement coordinator
- Add calculateRoofRotation in placement-math using surface normals
- Support full 3D cursor rotation for sloped surfaces
- Items on roofs are parented to the level with world-space rotation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fixed conflict

* feat: add paintable roof accessory materials

* feat(editor): rotate roof direction while drawing

* Fix roof face consistency and editing flow

* feat: add automatic open roof valleys

Clip entering roof segments against their host without removing host surfaces.

* fix: complete roof intersection trimming

* refactor: align roof features with architecture

* test: align roof accessory paint expectations

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…st (pascalorg#692)

* chore(core): evict the last runtime R3F import from the core barrel

ElevatorRuntimeSystem (a useFrame wrapper) moves to @pascal-app/nodes'
elevator system, its only consumer; stepElevatorRuntimes stays exported
from core. A new architecture test fails core on any runtime three /
@react-three import (type-only imports are erased and allowed) — proven
red on a probe file with named, side-effect, and mixed type/value forms.

Importing the core barrel from a Next.js route handler no longer
evaluates R3F under the RSC server condition (the capture-upload 500
class of bug).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore(core): keep the registry barrel React-free

useRegistryVersion moves its export from registry/index.ts to the core
barrel: the registry barrel is reachable from server-safe graphs
(schema → spatial grid → registry), and the hook's react import crashed
Next.js route handlers that import @pascal-app/core/schema. Public API
unchanged — consumers import it from '@pascal-app/core' as before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…pascalorg#689)

* fix(nodes/wall): hidden walls stay ray targets while a door/window tool is active

pascalorg#683 made walls hidden by the wall-mode pass ('down' X-ray mode, cutaway
faces, auto-mode interior partitions) pointer-transparent so clicks reach
the visible device/service boxes behind their invisible full-height
collision meshes. But the door/window MOVE and PLACE tools drive their
whole cursor model from the wall pointer events those handlers emit
(wall:enter/move/click/leave): with hidden walls silent, the ray falls
through to the ground grid, grid:move free-follows, and the opening
detaches from its wall as a red world-axis-aligned ghost ("rotates in
place in red 90 degrees") instead of sliding along the wall. Before pascalorg#683
those drags worked in X-ray only by accident of the same defect that
misfired outlet clicks.

Fix: a counter-based hidden-wall pointer HOLD in core
(holdHiddenWallPointerEvents / hiddenWallPointerEventsHeld). The four
wall-opening tools (door + window, move + place) acquire it for their
active lifetime; the wall renderer's gated handlers keep events flowing
while a hold is live. No tool active means no hold, so plain selection
clicks still pass through hidden walls — the night-5 D4 outlet fix is
intact (MoveRegistryNodeTool never touches the hold; its cursor is the
ground plane). Delete-mode hover keeps its existing exception. The gate
predicate is extracted to wallPointerEventsSuppressed so the truth table
is pinned by tests without an R3F rig.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* style: biome format

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ut (pascalorg#693)

The sweep runs ~5.7s on a 2-core x64 CI runner — past bun's default 5s
per-test budget (2-3s locally on Apple Silicon). First surfaced by
private-editor's quality gate on the pascalorg#692 submodule bump, the first CI
to ever run this suite.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…gesture (pascalorg#694)

* fix(nodes): door/window MOVE drags ride the node's own wall and undo as one step

Two night-6 QA defects in the opening move tools, both fallout around pascalorg#689's
hidden-wall pointer hold:

1. Wrong-wall capture: with every hidden wall a ray target, nearest-hit-wins
   let a hidden wall interposed between the camera and the dragged opening's
   own wall catch the wall:move stream — the drag rode a wall the user cannot
   see and the commit silently re-parented the opening onto it. New
   `shouldIgnoreWallEventForOpeningMove` gate (shared/opening-move-wall-gate):
   while MOVING an existing opening, a hidden wall may drive the drag only if
   it is the node's own wall (grab wall / current mid-drag host); visible
   walls always pass, so cross-wall re-parenting needs an explicit, visible
   target. Ignored events don't stop propagation, so the ray falls through to
   the own wall behind. PLACE / isNew duplicates keep the all-walls behavior.

2. Multi-entry undo per drag gesture: the tools paused history with a RAW
   `temporal.pause()`, invisible to the refcounted getSceneHistoryPauseDepth.
   zundo reads isTracking AFTER a write's subscribers run, so a cooperating
   system's balanced pauseSceneHistory/resumeSceneHistory pair (the
   space-detection sync, on any mid-drag reparent that touches wall children)
   zeroed the refcount and resumed tracking mid-gesture — the mid-drag writes
   became their own undo entries (transient states: commit fired at drag-arm,
   door hidden/reparented, orphan opening at the drop spot). New
   `beginOpeningMoveHistorySession` (shared/opening-move-history) holds the
   refcounted LEASE for the gesture and opens one deliberate tracking window
   (`commitStep`) for the drop write: mid-drag tracks nothing, drop records
   exactly one entry whose past state is the restored pre-drag baseline,
   cancel records nothing. Store-level tests pin the one-entry contract, the
   exact-baseline undo, lease composition, and the raw-pause leak this
   replaces.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* style(nodes): biome format for the opening-move-history test

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Live framing recompute during door/window drags (throttled effective-
node computes riding the host's transient overrides), subfloor deck
members + translucent under-layer on the floor sheet, composed header
flags printed verbatim with flag-block pagination, engineered-header
supplier SKU rows, LOD-400 B4 (one takeoff row per material + live
climate-zone labels in all 51 states) and B5 (PT sole plates,
R317.1), roof span discipline (B2), storey-relative elevation lifts,
blocking full-extent cull tests. 950 tests at the pin.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ure behind them (pascalorg#697)

* fix(nodes/wall): hidden walls join hover/selection raycasts nearest-first

INVESTIGATION (user report: with the Bones X-ray on, mousing over a wall
highlights/selects the furniture BEHIND it):

(a) The hover + click-select path is R3F's per-mesh pointer events
    (useNodeEvents on each node's meshes -> mitt emitter wall:enter /
    item:click / ... -> SelectionManager's select-mode subscriptions, which
    set hoveredId / selection and stopPropagation). R3F delivers through
    the distance-sorted intersection list until propagation stops.

(b) Why walls lost: the Bones framing renderer auto-switches the host
    wallMode to 'down'; WallCutout stamps userData.wallHidden=true on every
    wall; the wall renderer's pascalorg#683 gate then early-returned EVERY pointer
    event (blanket pointer transparency, no stopPropagation), so R3F fell
    through to the furniture behind. The framing members that visually
    occupy the wall's volume are handler-less InstancedMeshes (never
    raycast candidates), so nothing at the wall's depth could win.

(c) SOLID / visible walls do NOT lose: wallPointerEventsSuppressed returns
    false for visible walls, the wall is the nearest interactive hit, and
    SelectionManager stops propagation on it — no ordering bug in the
    selection raycast itself. The defect is exclusively the hidden-wall
    blanket transparency.

FIX: nearest-first with wall-furniture priority. A hidden wall's gated
handlers now reduce the event's intersection list (extractWallSelectionRay)
and handle the event unless something outranks the wall:
  - its own hosted subtree (doors / windows / wall-mounted children) at ANY
    depth gap — immune to grazing-angle inflation;
  - any non-wall hit at <= wallHit + 0.35m (devices flush/proud/recessed at
    the face, objects in front of the wall);
  - wall-MOUNTED hits further down the ray — non-wall hits within epsilon
    of another wall's collision hit (the pascalorg#683 night-5 D4 receptacle behind
    an interposed hidden wall keeps winning).
Other walls' hits never compete directly, so parallel hidden walls can't
both yield and drop the event into the room behind — the nearest wall wins
by delivery order. Events without ray data fall back to pascalorg#683 transparency.

Unchanged: delete-mode hover, the pascalorg#689 hidden-wall pointer hold (door /
window MOVE+PLACE tools — pascalorg#694's own-wall gate keeps filtering those
downstream), visible walls never suppress.

Trade-off (pure host-side rule, no plugin presence flag): in a manual
'down' wall mode with no overlay rendering at the wall, the wall strip is
hover/selectable again even though it draws nothing there.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* perf(nodes/wall): reduce the pointer ray lazily — visible walls skip it

extractWallSelectionRay walks the event's intersection list and each hit's
parent chain; visible walls never suppress, so doing that per hover move
over every visible wall was wasted work. Gate the reduction on the
wallHidden stamp the predicate already consumes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(nodes/wall): rank hidden-wall selection by hit OWNERSHIP, not raw depth

QA f2 browser round falsified the premise that overlay meshes never reach
event.intersections: R3F's event raycast recurses through the level /
building wrapper groups (they carry pointer handlers), so Bones framing
InstancedMeshes land in the list at the wall's own depth (probe6/probe7:
d 4.246-4.422 vs wall 4.246) — all inside the epsilon tie-break, so the
previous rule yielded everywhere framing renders and the click still fell
through to the Double Bed. The wall's own RENDER mesh rides the same list
at identical depth, which would have made the old hostedByThisWall subtree
test self-defeating the moment walls build real geometry.

Every hit is now classified by its NEAREST sceneRegistry-registered
ancestor (selection-hit-owner.ts):
  - 'self-wall'  (own collision/render/trim)      -> neutral
  - 'other-wall'                                  -> anchor only, never a
                                                     direct competitor
  - 'selectable' (built-in selectable kinds +
                  registry capabilities.selectable,
                  e.g. bones:device)              -> real competitor
  - 'passive'    (bones:framing members, level/
                  building wrappers, zones, gizmos,
                  grid, unregistered ancestry)    -> never outranks
Competitors win via: hosted-by-this-wall (any depth), <= wallHit + 0.35m,
or within epsilon of another wall's hit (D4 interposed-wall receptacle).
A hosted door resolves to the DOOR (registered deeper than its host wall),
so 'self-wall' never swallows it. The reverse Object3D->id lookup rebuilds
lazily off sceneRegistry.revision.

This is robust standalone: even before the plugin-side raycast stub
(plugin-bones d8bcc5d) lands, framing hits classify passive; any future
overlay without the selectable capability behaves the same.

Tests replay probe7 session B's exact hit shape (framing at wall depth +
bed behind -> wall wins) plus the classifier truth table (self/other-wall,
hosted door, framing passive, device selectable, wrapper/zone passive,
deleted-node passive, revision-following lookup).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(viewer): walls can never stay degenerate — system resilience + self-heal

QA f2 probe5/probe6: a scene LOADED with the Bones X-ray already active
kept all 24 wall collision meshes as degenerate placeholder points with no
wallHidden stamps and untouched base materials — geometry rebuild, cutout
stamping, AND batching were all inert for 16+ seconds while other useFrame
consumers (camera-controls) demonstrably ran. All three live in the ONE
registry-mounted WallSystems bundle, so the failure is the bundle not
running, not a per-wall skip. Toggling the X-ray mid-session (probe7)
works, so the trigger is load-time mounting/ordering. Three fixes close
the class:

1. RegisteredSystems re-derives its kind list on useRegistryVersion().
   The list was snapshotted ONCE at mount (useMemo []), so any kind
   registering after that render — async plugin discovery, HMR — never
   mounted its system; a first render before ANY kinds register mounted
   nothing, permanently. Same staleness class SelectionManager already
   guards against.

2. Per-kind Suspense boundaries. One shared boundary meant any pending or
   failing lazy system chunk unmounted EVERY system (wall pipeline
   included) while it hung.

3. Wall self-heal sweep (wall-placeholder-sweep.ts, every 30 frames): any
   registered wall still on its mount-time placeholder geometry (stamped
   userData.placeholder, 3-vertex fallback signature) with no dirty mark
   is re-marked, so the normal rebuild path converges no matter what
   consumed the mount-time mark or when the system came up. The frame body
   now also reads the LIVE dirty set instead of a render-closure copy —
   a store-level set REPLACEMENT (scene load, plugin install) in the
   window before React commits no longer hides fresh marks.

Walls now build their collision geometry regardless of initial visibility
or load order — the invariant the hidden-wall selection gate needs to
engage at all.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* style(viewer): biome import order for the wall-system sweep import

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(viewer): hovered hidden walls glow — the X-ray hover affordance

QA f2 (REVISE): hidden walls became hover/selection ray targets
(nearest-first), but an invisible wall gave NO feedback under the cursor —
what the user saw lighting up on hover was the furniture BEHIND it.

WallCutout now tracks the select-mode hovered wall (useViewer.hoveredId,
hoverHighlightMode 'default' only, so the paint-preview snapshot/restore
flows never interleave) and draws a hovered HIDDEN wall with a hover
variant of its invisible stipple film: the same indigo emissive treatment
as the wall selection highlight at a softer blend/intensity (0.28/0.07 vs
0.4/0.12), so hover reads as "this will select" and still steps up on
click. Visible and translucent walls keep their existing hover affordance
(the post-processing outline pass) — no double-highlight.

Mechanics: the per-wall material choice is extracted into a pure
resolveWallMaterialVariant truth table (delete > selection > hover > base,
hover arm only for the invisible base) consumed via materialsForVariant;
getSelectionHighlightMaterial generalizes into getEmissiveHighlightMaterial
with per-variant cache + profile, adding getHoverHighlightMaterials. The
hovered wall id joins WallCutout's highlightKey so hover changes refresh
the material pass immediately.

Known limit (pre-existing, noted per QA): the canvas cursor stays 'auto'
over walls even in solid mode — not addressed here.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* tune(viewer): hover glow visible at normal zoom (QA: was ~2-5/255)

The F2 browser round measured the hidden-wall hover glow at mean delta
0.13/255 over the viewport — functionally correct, invisible without
A/B flipping. Blend 0.28->0.4, intensity 0.07->0.2 per the QA's 2-3x
recommendation; selection emphasis (0.4/0.12) still reads stronger
via its blend+dot treatment.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
fix: nearest-first hover/selection — hidden walls win over the furnit…
claude added 2 commits August 24, 2026 14:29
Lean-to roof extensions with automatic drainage, Blender-style custom
mesh editing, synchronized 2D viewer modes, shared-parameter editing
across a homogeneous multi-selection, plugin inspector-card extensions,
an empty-graph save guard, a batch of wall hover/pick correctness fixes,
and the autosave fix that stopped scenes being wiped during the load
window.

Thirty-five files conflicted; five of them were not real conflicts.
`integration` carries cherry-picks of upstream pascalorg#607, pascalorg#608 and pascalorg#638, so
git saw two independent additions of the same path. Four were
byte-identical to the commit they were picked from and the fifth
differed by one defensive `?.`, so upstream's newer copies were taken
outright — upstream has since fixed the same files.

Fork positions kept, each already written down in UPSTREAM.md: the
`resolveSelectionHighlight` thunk and `freezeObjectTransform` in the
wall systems, the warehouse-scale room test with no upper area bound,
the plugin-aware `graph-schema.ts`, the ownership and edit-lease checks
on the scene API, `output: 'standalone'`, the warehouse pin, and the
vendored articraft and trees workspaces. Four positions were not written
down and now are: the room-envelope height caps, which upstream raised
from 6 m to 20 m and this fork removed outright because a cap clamps
typed input as well as drag; the trees pin, which stays `workspace:*`
while the vendored copy exists; the scene-loader's floating navigation;
and the site tree's `def.tree` gate, which now lives inside upstream's
own `getTreeNodeComponent`.

Upstream's empty-graph guard tests failed on arrival for the reason the
last merge's log predicted. Their fixture builds an invented `qa:box`
kind, which upstream's validator holds to the BaseNode envelope and this
fork's refuses outright, so all three saves returned 400 before reaching
the guard under test. The fixture now calls `WallNode.parse`, which is
the rule that log already drew from `graph-schema.test.ts`.

`bun.lock` is committed unchanged. Upstream bumped plugin-bones to
85238a8e, and the lockfile records the sha512 of each GitHub tarball —
which only a machine that can reach the real api.github.com can compute.
Relock is dispatched on this branch afterwards; until it runs,
`--frozen-lockfile` is expected to fail on that hash and nothing else.

Also folds in the one item from the audit that this file was already
open for: `@pascal-app/plugin-articraft` joins `transpilePackages`. It
ships raw TypeScript and `bootstrap.ts` imports it, and it built until
now only because bun's symlink layout drops its real path outside
`node_modules`.

Gates: biome clean, `check-types` clean for every workspace that can
resolve its dependencies here, and `bun run test` green across all 16
tasks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T5bdAFduH4BkPCjvtJgFzn
A level's slab and its ceiling sit at opposite ends of the level, so a
shaft crossing floors 1-3 cuts slabs 2 and 3 but ceilings 1 and 2. The
predicate saw only the level id, so a kind had one answer for both
surfaces and was necessarily wrong at one end: slab semantics leave the
bottom floor's ceiling sealed across the shaft, ceiling semantics cut a
hole in the ceiling above the top stop.

The elevator has always had two predicates for exactly this reason; the
capability handed plugins a single one. Pass the surface kind — the sync
already holds the surface node, so it costs one argument — and the two
sides can express the same rule.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T5bdAFduH4BkPCjvtJgFzn
@ovurrsl
ovurrsl force-pushed the claude/github-push-turbopack-fix-v0z56s branch from a52e90c to 8194034 Compare August 24, 2026 14:32
@ovurrsl ovurrsl changed the title Dikey açıklık yüzeye göre cevap verebilsin Upstream 7f629b8c'yi al (55 commit) + dikey açıklık yüzeye göre cevap versin Aug 24, 2026
`floorPlaced.collides` is the spatial grid's plan rectangle: XZ only, no
height at all. That is right for furniture on a floor and wrong for
anything whose usable volume is mostly air — to the grid, a conveyor
threading the walkway under a racking run is indistinguishable from one
driven through its uprights. Kinds like that must leave `collides` off.

Turning it off turned off their move validation entirely. `boxDimensions`
is what drives `recomputeValidity`, and it was gated on `collides` alone,
so such a kind could be placed through a gate that checks it in three
dimensions and then dragged straight into solid steel with nothing
consulted again. Placement refused what the drag then allowed.

`movable.canMoveTo` is the kind's own answer, run on every pointer move of
a drag and composed with the grid's rather than replacing it. Declaring it
also turns the green/red box on, because refusing a drop with no visible
reason reads as the object simply refusing to move; Alt forces it, exactly
as it does for a colliding kind. A kind that declares neither keeps the
plain arrow cursor it has always had, which is what the extracted
`showsValidityBox` is tested on.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T5bdAFduH4BkPCjvtJgFzn
@ovurrsl ovurrsl changed the title Upstream 7f629b8c'yi al (55 commit) + dikey açıklık yüzeye göre cevap versin Upstream 7f629b8c'yi al (55 commit) + dikey açıklık ve sürükleme kapısı Aug 24, 2026
claude added 2 commits August 24, 2026 15:06
…push-turbopack-fix-v0z56s

# Conflicts:
#	bun.lock
Opening a zone's settings took the whole editor down with React 185 —
maximum update depth exceeded.

The contents selector ran through `useShallow`, which compares array
elements with `Object.is`, and it built fresh `{ label, count }` objects
on every call. Two calls over an identical scene were therefore never
equal, so the snapshot read as changed on every render and the loop never
settled. Every other `useShallow` in this codebase maps ids onto nodes
that already exist; not one constructs an object, and this is why.

The selector now returns strings — one label per node, compared by value —
and the grouping moved out into a `useMemo`. `collectZoneObjectLabels`
sits beside `collectZoneObjectIds` so the property that matters can be
tested without a renderer: two calls over an unchanged scene are
shallow-equal, and a node moving out of the zone still makes them
unequal, so the panel keeps updating. Both fail against the old shape.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T5bdAFduH4BkPCjvtJgFzn
@ovurrsl
ovurrsl marked this pull request as ready for review August 25, 2026 07:33
@ovurrsl
ovurrsl merged commit e6756f6 into integration Aug 25, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants