[4] Basic-mode interaction overhaul + PRO connectors / eccentric connection (Phase B)#51
Merged
Merged
Conversation
Add a Design Summary section at the beginning of the PRO report (before Model Data) when verification results exist: - Overall pass/fail/warn status counts in a highlighted bar - Design code reference - Member Utilization Summary table sorted by utilization (most critical first): Element ID, Type, Section, Governing Check, Utilization %, Status - Load combinations reference note This is the "executive summary" that lets an engineer immediately see which elements are critical and what the overall structural status is. i18n: added 20+ report keys for en and es (designSummary, utilizationSummary, governingCheck, flexure, shear, axialFlexure, beam, column, wall, etc.)
New example in the PRO Examples menu under "Energy & Offshore": - 4-leg battered jacket (30m base → 18m top, 60m tall) - X-bracing on all 4 faces across 6 bays (48 diagonal braces) - Horizontal framing at 7 levels - 4 central conductors - Topsides deck at z=65m (24×24m, cantilevered beyond jacket) - Flare boom arm with diagonal brace - 6 steel sections (CHS legs, braces, horizontals, conductors + W-shape deck) - 4 load cases (dead, live, wind, wave/current) - 6 LRFD combinations 46 nodes, 121 elements, 8 supports, 20 loads. Inspired by North Sea jacket platform reference.
Rebuilt the offshore example with much higher structural detail: Jacket (80m tall, 8 bays): - 4 battered legs (40m base → 20m top footprint) - 64 X-braces (2 per face × 4 faces × 8 bays) - 36 horizontal ring members (9 levels) - 10 plan bracing members (every other level) - 4 pile guide legs with mudline horizontal ring and cross bracing Conductors (central core): - 4 conductors segmented at 5 levels (0, 20, 40, 60, 80m) - Horizontal tie rings and cross bracing at each conductor level - Connected to jacket legs at matching levels Topsides (3 deck levels: 82, 87, 92m): - 24 deck nodes with perimeter and cross beams - 16 inter-deck columns with diagonal bracing - 4 transition columns from jacket top Drilling derrick (92-122m): - 4-leg lattice tower tapering upward - Horizontal rings at 3 levels, X-bracing between levels Flare boom: 3-member arm from deck to (52, -16, 100) with braces Pedestal crane: column + arm to (-32, 12, 98) connected to deck 103 nodes, 316 elements, 12 supports, 50 loads. All fixture validation tests pass (solves correctly).
Rebuilt jacket topology to match the reference image: Jacket perimeter (12 vertical legs): - 4 main corner legs (CHS 1200×40) at ±22m base → ±11m top - 8 intermediate perimeter legs (CHS 900×30), 2 per face at 1/3 and 2/3 - Each face now has 4 vertical lines creating 3 bracing panels per face - Total jacket height: 90m (z=-5 to z=85), 9 bays Bracing density: - 216 face X-braces (3 panels × 4 faces × 9 bays × 2 diagonals) - 120 horizontal ring members (12 segments per level × 10 levels) - 10 plan cross-braces at alternating levels - 32 internal diagonal braces (corners to opposite-face intermediates) Conductor core (8 conductors): - 4 outer + 4 inner conductors at ±3m and ±1m offsets - Segmented at 5 levels with horizontal ties and cross bracing - Connected to jacket corner legs at matching levels Topsides (3 levels: 87, 92, 97m): - 8 nodes per deck (corners + mid-edges), cantilevered 15m - 8 transition columns from jacket top (corners + intermediates) - Full perimeter + cross beam framing per level Drilling derrick: tapered 4-leg lattice (97-127m) Flare boom: 3-segment arm to (58, -22, 100m) Pedestal crane: column + arm to (-35, 13, 103m) 203 nodes, 668 elements, 20 supports, 74 loads.
Replaced the chaotic internal diagonals with structured internal planes: Old pattern (wrong): - Corners connected to opposite-face intermediates with ad hoc diagonals - Created visually noisy crossing lines with no clear structural logic - 32 arbitrary internal braces at 4 scattered levels New pattern (correct): - 2 orthogonal internal planes connecting parallel perimeter legs: - N-S plane: south intermediates ↔ north intermediates - E-W plane: east intermediates ↔ west intermediates - Horizontal ties on both planes at every level (40 members) - X-bracing on both planes between bays (72 members, 4 panels × 9 bays × 2 diags) - Internal structure reads as 2 clear internal frames, not a web The internal bracing now follows the same bay-by-bay X-brace logic as the perimeter faces, just on the internal planes connecting opposing parallel vertical lines. 203 nodes, 748 elements (+80 from v3), 20 supports, 74 loads.
…deck Offshore example: - Removed elements 740-748 (flare boom + crane) and their orphaned nodes - Cleaned up floating node 203 with its load reference - 197 nodes, 739 elements (down from 203/748) Pitch deck: - Complete visual redesign with Inter font, dark premium theme - Grid/mesh background pattern, gradient backgrounds per slide - Slide 1: product positioning with tagline - Slide 2: capability grid (modeling, verification, reports) + example tags - Slide 3: energy expansion with sector cards + offshore proof-of-concept bar - Slide 4: Argentina focus with 2-column sector/opportunity layout - Slide 5: team credentials (FIUBA/CEARE) + Patagonia field validation next step
Offshore example: - Removed element 739 (orphaned boom member) and node 197 - Fixed disconnected structure: inner conductors (±1m) were isolated from outer conductor ring (±3m) — added 20 truss ties at each conductor level - Fixed derrick disconnection: 4 derrick base nodes at z=97 were not tied to any deck nodes — added 4 frame connections to nearest deck - 196 nodes, 762 elements, solves correctly Pitch deck: - Added real product screenshots: 3d-colormap.jpg (title slide), 3d-deformed.jpg (capability slide), offshore-reference.webp (energy slide) - Images integrated into slide layouts with shadows and borders - Split-layout slides: text left, visual right - Changed "Próximo paso clave" to "Próximos pasos" with 4 concrete items (Patagonia, expert testing, report review, industry pilots)
Offshore example fixes: - Removed 20 intermediate-leg wave loads (kept corner-leg only), reduced magnitudes — lateral/gravity ratio improved from 1:2 to 1:6 - Upgraded deck beam from W24x68 (J=0.0000015) to RHS 500x300x16 (J=0.000906) — 600x torsional stiffness improvement - Upgraded derrick-to-deck connections from CHS 300x10 to CHS 800x25 - Converted 32 conductor frame ties to truss members (axial-only) - Reduced wind loads from 150/50 kN to 50/20 kN per node Root causes of wild deformed behavior: 1. Wave loads (Fx=2000 + Fy=1000 total) were nearly half the gravity load 2. Deck W-shape had near-zero torsional stiffness causing twist 3. Derrick connections used CHS 300 (weak bending) on long spans Pitch deck: - Replaced generic images with real landing-page screenshots: Slide 1: pro-verification.png (PRO RC building with rebar) Slide 2: 3d-industrial.png (Basic 3D industrial with color map) Slide 3: pro-features.png (PRO 3D model overview) - Changed "Próximo paso clave" to "Próximos pasos" with 4 items
Captured the Offshore Jacket Platform example from the running PRO app via Puppeteer (headless Chrome). Shows the full 196-node/762-element battered jacket structure with dense bracing, topsides, and derrick. Replaced the generic pro-features.png on the energy/offshore slide with this real product screenshot, making the deck's offshore expansion claim concrete and verifiable.
Main layout problems fixed: - S1 hero: was fixed 480×360px absolute-positioned; now flex column with 580px max-width and 420px height img-frame - S2 capability: was max-width 420px; now flex-1 up to 600px with 440px height frame - S3 offshore: was 380px fixed column with 360px max-width; now flex-1 up to 520px with 420px height and CSS transform scale(1.6) to zoom into the model center All slides now use row flex layout (slide-inner flex-direction:row) with text and visual columns balanced. Images use img-frame wrapper with consistent border-radius, shadow, and border. Glow effects positioned behind each image. Offshore image uses object-position:center 40% + scale(1.6) to crop/zoom into the jacket structure without re-capturing.
Pre-solve quality gate: - handleSolve() now runs checkModel() before invoking the solver - If model errors exist (severity: error), solve is BLOCKED - Error message redirects user to the Diagnostics tab - No cryptic solver crash — clear "N errors must be fixed" message Quality-gate banner: - Persistent amber banner appears above tab content when model has errors - Shows error count + "Fix before solving" message - Clicking the banner navigates to the Diagnostics tab - Hides when viewing the Diagnostics tab (to avoid redundancy) Reactive model error count: - modelErrorCount derived tracks errors in real-time as model changes - Powers both the banner visibility and the solve gate Existing diagnostics infrastructure leveraged: - checkModel() already has 30+ checks (connectivity, zero-length, missing refs, orphan loads, etc.) - ProDiagnosticsTab already has clickable items that select entities and zoom-to-fit - This change connects the pre-solve flow to that existing infrastructure i18n: en + es (modelErrorsBlock, seeDiagnostics, qualityGate, errorsFound, fixBeforeSolve)
Adds a "Generate LRFD Combinations" button in the PRO Loads tab that auto-generates ASCE 7 / CIRSOC 101 ultimate load combinations from existing load-case types. The generator reads load-case types (D, L, Lr, S, W, E) and produces: 1. 1.4D 2. 1.2D + 1.6L + 0.5(Lr or S) 3. 1.2D + 1.6(Lr or S) + L 4. 1.2D + 1.6W + L + 0.5Lr (per wind case) 5. 1.2D + E + L (per seismic case) 6. 0.9D + 1.6W (per wind case) 7. 0.9D + E (per seismic case) Features: - Reads existing load-case type tags to determine which combos apply - Names each combination with clear factor expression (e.g., "U3: 1.2D + 1.6Lr + L") - Uses short wind/seismic case names in combo labels - Generated combos are fully editable after generation - Requires at least one D-type case (shows error toast otherwise) - All additions wrapped in modelStore.batch() for single undo - Button appears below the manual combo-add row with hint text i18n: en + es (generateLRFD, generateLRFDHint, generateLRFDDesc, combosGenerated, needDeadCase)
Replaces the one-click combo generator with a reviewed modal flow: 1. Click "Generate LRFD Combinations" → opens a review modal 2. Modal lists all candidate ASCE 7 / CIRSOC 101 combinations 3. Each candidate shows: name, factor expression, and whether an equivalent combination already exists 4. New combos are checked by default; existing ones are unchecked with an "already exists" badge (dimmed row) 5. User can override: check an existing one to intentionally duplicate 6. Click "Generate Selected" → only checked candidates are added Equivalence detection: - comboSignature() creates a canonical string from non-zero factors sorted by caseId, rounded to 2 decimals - comboExists() compares candidate signature against all existing combination signatures - Equivalence is content-based, not name-based — works even if existing combos were renamed Modal UX: - Dark themed, centered overlay with close/cancel/generate buttons - Shows "N / M selected" counter in footer - Generate button disabled when nothing is selected - Backdrop click closes the modal i18n: en + es (comboAlreadyExists, selected, generateSelected)
Example naming: - Removed "(CIRSOC 102)" from 7-story Wind +X case name - Fixed lowercase "Dead load"/"Live load" to "Dead Load"/"Live Load" in 3d-building.json and 3d-nave-industrial.json LRFD equivalence fix: - Root cause: generator added 0.5×Lr companion to wind combos (pattern 4) but existing 7-story combos don't include the Lr companion load, so signatures differed (5 non-zero terms vs 4) - Fix: removed 0.5×Lr/S companion from wind/seismic combos to match standard practice where the companion load is optional - Now the 7-story U4-U7 combos correctly match generated candidates Display ordering: - Factor expressions in the generator modal now sort by load-case type priority: D → L → Lr → S → W → E → other - Within same type, sorted by caseId - Produces stable professional ordering regardless of insertion order
Example naming cleanup: - Removed redundant type prefixes from load-case names in 7-story and offshore examples (e.g., "D — Superimposed dead" → "Superimposed dead") - Type is already shown by the UI's type column/dot; the name now carries only the descriptive content Service/ASD combination generator: - New "Generate Service Combinations" button alongside the existing LRFD one - Produces ASCE 7 §2.4 / CIRSOC 101 ASD combinations: S1: D S2: D + L S3: D + Lr (or S) S4: D + 0.75L + 0.75Lr S5: D + W (per wind direction) S6: D + 0.7E (per seismic direction) S7: D + 0.75L + 0.75W S8: 0.6D + W S9: 0.6D + 0.7E - Same reviewed modal workflow as LRFD (candidates, equivalence, check/uncheck) - Service combos prefixed with "S" (vs "U" for LRFD) Provenance badges: - Combination cards now show small badges based on naming convention: - "LRFD" badge (red) for combos matching /^U\d+:/ - "SVC" badge (teal) for combos matching /^S\d+:/ - Badges are visual-only; combos remain fully editable - If user renames away from the convention, badge disappears (honest) Other: - Removed lightning emoji from generate button - Modal header shows template name + ASCE 7 section reference - i18n: en + es for generateService, generateServiceHint, serviceCombosGenerated
Preview formatting: - Existing-equivalent combos in the modal now show only non-zero factors, each on its own row: factor | type | name - Matches the mental model of the main combination card layout - Zero-factor load cases are no longer shown (were cluttering the preview) - Rows sorted by type priority: D → L → Lr → S → W → E Numbering fix: - Root cause: applySelectedCombos used combinations.length for the counter, so U1-U8 existing → first service combo was S9 instead of S1 - Fix: count only combos matching the active prefix (/^U\d+:/ or /^S\d+:/) - LRFD and Service numbering are now fully independent families - U1-U8 existing + no S combos → first service is S1 - S1-S4 existing + no U combos → first LRFD is U1
…Design tab Report Design Summary: - Added "Governing Combo" column to the Member Utilization Summary table - Shows the combination name that produces the governing check for each element - Combo selection logic: picks the combo corresponding to the governing check type (flexure → flexure combo, shear → shear combo, axial+flexure → axial combo) - Displayed at 9px to fit the table width without overflow ProDesignTab expandable check details: - Clicking a design-check row now expands a detail panel below it - Detail panel shows ALL individual checks for that member: Check name | Demand | Capacity | Ratio | Status | Combo - Each check shows its own governing combination reference - This enables per-check combo traceability (flexure may govern in U3 while shear governs in U5 — both are now visible) - Second click collapses the detail panel - Click also selects the element in the viewport (existing behavior preserved) These changes connect the combination workflow → verification → report chain: - Combinations are generated/edited in the Loads tab - The Design tab shows which combo governs each check (expandable) - The Report summarizes governing combos per element (printable) i18n: en + es (report.governingCombo)
The {@const nonZero = ...} was placed inside a <label> element.
In Svelte 5, {@const} must be the immediate child of a block tag
({#each}, {#if}, etc.), not an HTML element.
Moved the declaration to be immediately after the {#each} opening,
before the <label>. The inner {@const lc3 = ...} inside the nested
{#each nonZero as f} was already valid (direct child of #each).
Data model: - ElementVerification.governingCombos now carries 6 force components: flexure, shear, axial, momentY, shearZ, torsion (was only 3) - auto-verify.ts now attaches all 6 governing combo refs from GoverningPerElement3D when available - normalizeCirsoc201() now passes comboName for torsion and biaxial checks (was missing — those checks showed '—') Design tab expandable detail: - When different checks are governed by different combos, a note appears: "Different checks governed by different combinations" - Combo column header renamed to "Governing Combo" for clarity - Combo cells are highlighted in teal when multiple combos are active - uniqueCombos set computed per element to detect multi-combo cases Report Design Summary: - Governing combo column now marks elements where different checks have different governing combos with '*' and orange highlighting - Footnote explains: "Different checks governed by different combinations — see detailed verification for full breakdown" This addresses the structural-design principle that a member's flexure, shear, and axial checks may each be governed by a different load combination — the UI now makes this visible instead of collapsing everything into a single "governing combo" label. i18n: en + es (report.multiGovNote)
New module: station-design-forces.ts The core product-layer foundation for elite-grade RC/steel design traceability. Extracts forces at critical stations along each element, for each load combination, preserving sign and the full force tuple. Station strategy (buildCriticalStations): - Element endpoints (t=0, t=1) - Quarter and midpoint (t=0.25, 0.5, 0.75) - All point-load positions from Y and Z load arrays - Distributed-load start/end positions and midpoints - Analytical shear zero-crossing point (interior moment peak) computed from endpoint shear sign change Force extraction (extractForcesAtStation): - Uses evaluateDiagramAt() for each force component at each station - Returns full signed tuple: N, Vy, Vz, My, Mz, Torsion - Exact analytical equilibrium — not interpolation Per-combo extraction (extractElementStations): - Iterates all combos in resultsStore.perCombo3D - Extracts station forces for the requested element under each combo - Preserves comboId, comboName, and full station force tuples Governing demand extraction (extractGoverningDemands): - From station-level per-combo data, finds the worst demand per category: Mz+, Mz- (sign-aware), My+, My-, Vy, Vz, N_compression, N_tension, Torsion - Each demand preserves: value, combo, station, AND the full force tuple at that combo/station (critical for combined axial+moment checks) - Does NOT create impossible envelopes by mixing combos/stations Design tab integration: - Expanded element rows now show "Station-Based Governing Demands" section - Shows: category, value, station location, governing combo, and the concurrent N/Vy/Mz force tuple - Only appears when per-combo results exist (combinations were solved) Also extended: - ElementVerification.governingCombos now tracks 6 components (added momentY, shearZ, torsion) - normalizeCirsoc201 now passes comboName for torsion/biaxial checks CURRENT LIMITATIONS (solver contract): - No P-delta interior forces (endpoint-only for P-delta) - No support-face offset (d from column face) - No cracked-section iteration - Torsion diagram is linear interpolation only
…d drawing reactivity - Single "Run Design" button replaces two-step batch flow (design check + accept) - Design edits now drive live drawings via structuredClone + $state.snapshot reactivity fix - Column structured editor (corner/face bars, diameters) with zero in-place mutation - All number inputs use oninput for immediate feedback - Rich inline verification: CIRSOC memos, interaction diagram, check table, utilization - Provided-verification utilization responds to edits via enrichedResults $derived - Anchorage severity: warn vs fail distinction, descriptive messages - Summary row coherence: governing check and utilization from provided verification - Removed old RC Verification subtab and Analysis dropdown entry - Removed duplicate design-vs-verification drawings (single trust surface) - Removed redundant modified-reinforcement summary strip - Filter cleanup: Un-designed, Modified (user-only), status filters for designed elements - rc-qa-diagnostic fixture: balanced loads for meaningful QA - QA feedback docs from QA2-QA4 rounds
Hoist MzMax/MyMax/VyAbs out of both station-demand and endpoint-fallback paths; map identity once after the if/else: const MuMax = MzMax; const MuyMax = MyMax; const VuMax = VyAbs. Restores SEAM-3. Unbreaks 4 web tests on #47. No solver code touched.
Phase 1 of verification architecture cleanup (SOLVER_APP_COVERAGE_MAP.md §13): - New verification-service.ts: centralized station-demand computation and unified CIRSOC verification entry point (computeStationDemands, runUnifiedVerification) - ProDesignTab: uses shared service instead of inline station computation - ProVerificationTab: replaced 116-line endpoint-only force extraction loop with one call to runUnifiedVerification (fixes Bug #2: interior forces missed, Bug #4: two divergent paths) - Serviceability: deflection now estimates midspan deflection when endpoint displacements are negligible (fixes Bug #3); service moment uses dead-load case when available instead of Mu/1.4 approximation (fixes Bug #5) - Both tabs now produce identical verification results through the same station-based pipeline and store them in verificationStore.setConcrete() What this does NOT do (blocked by no-solver-changes constraint): - No new WASM exports (§13.5 verify_members still pending) - No deletion of JS verification modules (§13.9 — still needed as primary path) - No VerificationReport Rust types (§13.6 — solver-side)
- verification-service.ts: header now lists Phase 1 vs Phase 2 scope and exactly which app-side bridges remain (station extraction, JS CIRSOC, autoVerify) - ProVerificationTab: service moment, midspan deflection estimate, and steel verification loop all marked as TEMPORARY Phase 1 bridges with Phase 2 targets - No behavior changes — annotation only
…t type Steel path reduction: - Moved 65-line steel verification loop from ProVerificationTab into verification-service.ts as runSteelVerification() - Steel path now uses station-based demands when available (same source as RC), falling back to endpoint extraction only when combos aren't computed - Removed getEnvelopeSolicitations (endpoint-only legacy function) — no longer called by either RC or steel paths - Cleaned unused imports from ProVerificationTab (verifySteelElement, SteelVerificationInput, SteelDesignParams) VerificationReport type: - New VerificationReport interface in verification-service.ts shaped to mirror the eventual solver-side output (§13.6 of SOLVER_APP_COVERAGE_MAP.md) - Contains: codeId, codeName, elements (MemberDesignResult[]), summary, optional stationData, and legacy detail arrays for Phase 1 compatibility - Components can consume this shape without knowing whether the source is JS-side or future WASM-side Net effect on ProVerificationTab: -96 lines of inline computation removed. Both RC and steel verification now flow through verification-service.ts.
…cess - New runCirsocDesign() in verification-service.ts: single entry point that runs verification + normalizes + updates both verificationStore.setConcrete() and setDesignResults() — replaces 3 separate calls in ProDesignTab - ProDesignTab: CIRSOC path reduced to one runCirsocDesign() call; removed direct imports of normalizeCirsoc201 and autoVerifyFromResults - Replaced 6 O(n) verificationStore.concrete.find() scans with O(1) concreteMap.get() lookups throughout ProDesignTab - Non-CIRSOC paths still handled inline (WASM payload assembly is code-specific) Net: ProDesignTab no longer assembles or normalizes CIRSOC verification data — it calls the service and renders the result.
…nsitional access - constructibility, autoSplitRows, rowFits: now use getElemSection() (model data) + provided reinforcement stirrup diameter instead of reading from concreteMap. Eliminates 3 of 6 concreteMap accesses — these were geometry queries that belonged on the model, not verification results. - Remaining 3 concreteMap accesses (acceptAutoDesign, getProvidedVerification, template memos/drawings) explicitly labeled as TRANSITIONAL with Phase 2 migration targets documented inline. - acceptAutoDesignAll also labeled as transitional iteration over concrete array. Net: ProDesignTab's 6 concreteMap accesses → 3 (all labeled transitional). The other 3 now depend only on model geometry + provided reinforcement.
…light
Mirrors what the next commit does for constraints, applied here to
ConnectorElement.
Without this fix, the JS preflight in validateAndSolve (2D + 3D) and the
live model-diagnostics orphan check would flag any node coupled solely
through a ConnectorElement as disconnected — even though the Rust solver
itself accepts the model. The fix:
- solver-service.ts: include connector endpoints in both the orphan-set
and the graph-component BFS, in both validateAndSolve2D and
validateAndSolve3D (4 sites total)
- model-diagnostics.ts: same orphan-check extension for the live
diagnostics warning panel
- model.svelte.ts: remapModelForPlane now passes connectors through
(was silently dropping them on the way to validateAndSolve2D)
Solver itself is untouched. No new UI surface — this is a correctness
fix for the existing ConnectorElement plumbing.
…eflight Same class of false-positive the connector slice fixed, now extended to constraints. The Rust solver already accepts constraint-only-coupled nodes; the JS preflight (`validateAndSolve2D`/`validateAndSolve3D`) and the live model diagnostics were running orphan-node + single-component checks over `model.elements` only, so a node coupled solely through a rigidLink / equalDOF / eccentricConnection / diaphragm / linearMPC was incorrectly blocked at solve time. All five existing constraint kinds now count as connectivity: - rigidLink: master ↔ slave - equalDOF: master ↔ slave - eccentricConnection: master ↔ slave - diaphragm: master ↔ each slaveNode[i] - linearMPC: every node in the term set ↔ every other No constraint kind is intentionally excluded. Logic lives in a new tiny `constraint-connectivity.ts` helper, consumed by both the preflight (4 sites: 2D + 3D × orphan-set + BFS-adjacency) and the diagnostics panel. New unit-test file covers all 5 kinds in both 2D and 3D, plus a negative control to keep the orphan check honest for genuinely free-floating nodes. Solver itself is untouched.
…ements
User-reported bug: with snap-to-grid enabled, drawing an element to an
existing node that does NOT sit on a grid intersection silently fails —
the cursor warps to the nearest grid intersection and the element either
doesn't connect or attaches to the wrong place. Workaround was to disable
grid snap, which is the wrong UX.
Root cause: two sites in the snap pipeline searched for nearby nodes
*from the grid-snapped position* instead of from the raw cursor. If the
off-grid node was further than the 0.5m node threshold from the nearest
grid intersection, the search missed it even though the cursor was
directly on top of the node.
- lib/viewport/spatial-queries.ts:snapWithMidpoint — the function's
own doc comment says "priority: existing node > element midpoint >
grid", but the implementation grid-snapped first, then searched
nodes from the snapped point. Now searches nodes from raw coords;
grid snap is the fallback when no node and no midpoint match.
- components/Viewport.svelte (element click handler) — used the
grid-snapped position as the node search center. Now uses raw
world.x/world.y, matching what the support and load tools already
do.
Snapping precedence rule after the fix (matches the doc comment):
1. existing node within nodeThreshold (0.5m) of raw cursor → wins
2. element midpoint within midpointThreshold (0.4m) of raw cursor
3. grid snap (fallback for free placement)
Side effects:
- Visual snap highlight in Viewport.svelte now lights up correctly
when hovering an off-grid node, because the upstream worldX/worldY
that drives the highlight comes from snapWithMidpoint and now
points at the node coords as designed.
- The node-creation tool would, when hovering near an off-grid node,
place a duplicate node at the same coordinates — same buggy
behavior it had for grid-aligned nodes already, just consistent
now. Out of scope for this slice.
6 unit tests covering: cursor on off-grid node (was missed), proximity
pick between two competing nodes, fallback to grid when no node match,
and the no-regression on-grid case.
Solver itself untouched.
…ment
When the user has the node tool active and clicks on the interior of an
existing element, subdivide that element into two with the new node
on it — instead of placing a free-floating node on top of the bar.
Off by default. Lives under Settings → Model as
"Auto-split elements when placing nodes on them" (es: "Subdividir
barras al colocar nodos sobre ellas"). Hidden in 3D/PRO since it's
wired into the Basic 2D Viewport for now.
Implementation:
- uiStore.autoSplitOnNodePlace ($state<boolean>(false)).
- Viewport.svelte node-tool create-mode handler: when the flag is
on AND no existing node is within 0.5m of the raw cursor AND
findNearestElement returns a hit within 0.3m perpendicular AND
the projected parametric position t ∈ [0.05, 0.95], delegate to
modelStore.splitElementAtPoint(elemId, t). On success, select the
new node, clear results, toast "Bar subdivided".
- Otherwise (flag off, no element hit, near-endpoint, or click on an
existing node), the legacy addNode path runs unchanged.
Load handling: splitElementAtPoint already redistributes distributed,
point, and thermal loads — same code path the hinge-mode subdivide
flow has been using in production. The Playwright probe verifies a
uniform q=−10 kN/m on a 6m bar yields two distributed-load segments
with conserved total (−60 kN exact) after a t=0.5 split. The unit
test pins the same conservation property + the type/material/section/
thermal preservation contract.
Endpoint guard: the click handler enforces t ∈ [0.05, 0.95] before
calling splitElementAtPoint (which has its own internal 0.01/0.99
guard). Clicks too close to either end fall back to plain addNode so
no zero/sliver elements get created. Clicks on/near an existing node
also bypass the split (the existing node is the user's intent).
Verified via 5-case Playwright probe (off+interior, on+interior,
on+near-endpoint, on+existing-node, on+loaded-element) — all pass
with zero console errors.
Solver itself untouched.
User-reported rough edge made more visible by the recent snap-precedence
work: clicking the node tool on top of an existing node would silently
add a second coincident node at the same coordinates.
Root cause: the node-tool create-mode handler resolved the snap target
via snapWithMidpoint (which now correctly returns the existing node's
coords when the cursor is within nodeThreshold of one), then unguardedly
called addNode at that resolved position — producing a stacked duplicate.
Fix is local to the click handler in Viewport.svelte: before the
addNode fallback, run findNearestNode against the RAW cursor coords
within the same 0.5m threshold snapWithMidpoint uses; if a node is
found, treat the click as "select that node" instead of placing a
second one. addNode's contract is unchanged (still creates a node at
the requested coords for programmatic / scripted callers).
The guard sits in the same fallback branch the auto-split path lands
in when its own !nearNode pre-check rejects, so:
- click on existing node, auto-split off → select existing, no dup ✓
- click on existing node, auto-split on → no split (existing-node
pre-check), then no dup (this guard) ✓
- click in empty space → legacy addNode unchanged ✓
- element drawing to off-grid node (snap-precedence slice) → unaffected ✓
- snap-to-grid off → still no dup (raw-cursor search) ✓
Verified via 7-case Playwright probe + re-run of the prior snap and
auto-split probes.
3D Basic node tool (handleNodeTool in Viewport3D.svelte) is unaffected
by this slice — it uses ground-plane raycasting and never resolves to
an existing node, so the duplicate path doesn't apply there. Out of
scope per "keep scope tight"; flagged as a follow-up.
Solver itself untouched.
… rule
Part A — Basic 3D regressions
─────────────────────────────
1. AxialColor / colorMap / verification not visible in wireframe mode.
syncColorMap3D was applying colors to per-element groups via
setGroupColor — but in wireframe render mode those groups are
essentially empty (the visible primary is the shared LineSegments2
batched mesh, which carries its own per-segment color buffer that
nothing was updating). Result: pressing "Axial Colours" silently
did nothing in Basic 3D wireframe.
Fix: also push the chosen color into ctx.elementsBatched via
setBaseColor + flush, in every branch (axialColor, colorMap continuous
gradient, colorMap shellVonMises, verification) AND in the restore
path when the user leaves color mode. ResultsSyncContext now carries
an elementsBatched reference; init wires it from sceneCtx.
2. Color map hides during camera motion (orbit/pan/zoom).
applyLowDetail in lod.ts hid `elementsParent` during orbit. In solid /
sections render mode the result colors live on the cylinders /
extrusions inside elementsParent, so hiding it made the visualization
disappear every camera move. Fix follows the existing exception for
resultsParent: applyLowDetail now takes an optional
`{ resultsColoringActive }` flag and skips hiding elementsParent when
it's true. Viewport3D.setLowDetail computes the flag from the active
diagramType + presence of results3D. Two new lod.test.ts cases pin
the new behavior and the no-drift-when-flag-is-false case.
3. Trusses indistinguishable from frames in wireframe.
In wireframe mode the batched mesh carries the visual; eb.upsert
initializes new entries with COLORS.frame regardless of element type.
syncSelection later applies the per-type color, but it only runs on
selection changes, not on initial load. Result: every example loaded
showing trusses in the cyan frame color until the user clicked
something.
Fix: syncElements now calls eb.setBaseColor(id, ...) with the
per-type wireframe-vs-solid base color directly after upsert, so
trusses (yellow #f0b848) and frames (cyan #6cb4ff) are visually
distinct from first paint.
4. "Elements 10+ disappear" — could not reproduce.
Loaded hinged-arch-3d (12 elem), space-truss (53), space-frame (388),
tower-3d-4 (96), industrial (633) — all rendered with full element
counts in my probe. The most plausible explanation is that fix #3
above (trusses showing as cyan default) made some elements look
"missing" against a busy structure where they blended into adjacent
frames. The truss-color fix should resolve the perceived issue. If
the user still sees elements missing under a specific interaction
sequence, that needs a fresh repro.
Part B — auto-split + snap-to-grid refinement
─────────────────────────────────────────────
1. autoSplitOnNodePlace default flipped to ON.
The natural intent of clicking with the node tool on top of an
existing bar IS "I want a node on this bar". Defaulting OFF was
conservative for the first slice; with the slice accepted, the
natural default is ON.
2. Setting moved above Units in Settings → Model.
ToolbarConfig.svelte now renders the auto-split checkbox right
below "Show loads" and above the Units group, so the modeling
options live together (loads visibility + auto-split) before unit
choice.
3. Auto-split + snap-to-grid combined rule.
Previously the projection used the raw cursor position, so the new
split node landed at an arbitrary unsnapped point. Now: when
uiStore.snapToGrid is on, we project the GRID-SNAPPED cursor onto
the chosen element line, so the split happens on the element AT
a grid-aligned position. For axis-aligned bars (the common case)
this puts the new node exactly on a grid intersection. For diagonal
bars, the projection of a grid point is the closest reachable
grid-anchored split point on the line. When snap-to-grid is off,
`snapped` equals `world` so behavior matches the previous rule.
findNearestElement still uses the RAW cursor (so the user can
"point at" a bar even when the grid would warp the cursor away
perpendicular to the line), but the projection input is the
grid-snapped position. The endpoint guard t ∈ [0.05, 0.95] still
applies; existing-node and duplicate-node guards still apply.
Verification (browser probes, all passing, zero console errors):
- 6 examples render full element counts
- axialColor mode applies blue/red colors visibly in wireframe
- 4-case grid-snap-autosplit probe confirms node lands on grid (3,0)
when clicking off-grid (3.3, 0.05) on a horizontal bar
- prior snap-precedence + duplicate-node + autosplit probes still pass
Vitest: 2148 passing (+2 new lod tests), same 5 pre-existing failures.
Solver itself untouched.
…ger drags
User-reported bug: in Basic mode, while in element/select mode, click+
drag would silently move nodes. The user's mental model is that node
repositioning belongs to the node tool; the select tool should only
select; the element tool should only create elements.
Root cause: handleMouseDown's `currentTool === 'select'` branch (with
the default `selectMode === 'elements'`) ran:
historyStore.pushState();
draggedNodeId = nearNode.id;
dragMoved = false;
dragStartWorld = { x: snapped.x, y: snapped.y };
i.e. any time the user pressed mouse-down while a node was within 0.3 m
of the cursor, the next mousemove handler entered the drag branch and
moved the node. Users hit this constantly when just inspecting the model
because select+elements is the default UI state.
Fix is a tight tool-mode reassignment in Viewport.svelte:
1. Select tool / elements selectMode: drop the historyStore.pushState
+ draggedNodeId set. The branch now only does selection.
`uiStore.selectNode(nearNode.id, e.shiftKey)` — no more drag setup.
Box-select still works (separate code path), shift-select still
works.
2. Node tool create mode: when the click effectively lands on an
existing node (within nodeThreshold of the raw cursor), promote
the previous "just select" behavior to "select + start drag" by
setting draggedNodeId. This is now the ONLY entry point for node
repositioning.
The existing mousemove drag block (gated on `draggedNodeId !== null`)
and the mouseup cleanup are unchanged — they keep working since they
depend only on the state, not on which tool set it. Touch handling
synthesizes a MouseEvent and routes through handleMouseDown so it
inherits the new gating automatically.
What still works (regression-checked via /tmp/probe-drag.mjs):
A. Select tool drag attempt → node stays put (the fix).
B. Select tool single click on a node → node selected, no dup.
C. Element tool drag attempt → node stays put (was already correct).
D. Node tool drag → node moves (legitimate repositioning).
E. Node tool click on empty space → new node created.
F. Node tool click on existing node → no duplicate, no movement.
3D sanity re-check:
- axial colors still light up the arch in compression-blue post-solve
in wireframe.
- truss/frame distinction holds (space-truss yellow=527, space-frame
cyan=2910 + truss-color pixels).
- All 6 named examples render with full element counts.
- LOD result-coloring exception still works (unit tests in lod.test.ts).
Vitest: 2148 passing, same 5 pre-existing failures, no new regressions.
Solver itself untouched.
…design-tab UI)
Bounded, non-domain fixes from the review. Web suite: 2125 passing
(5 skipped); vite build clean.
- pro-report: the "Design Summary" section and its TOC entry rendered
whenever verifications existed, ignoring showSection('verification').
Now gated on showSection('verification') like every other section, so
opting out of Verification in the report config also drops the summary
(and its TOC renumbering).
- ProLoadsTab: LRFD/service combo numbering used the *count* of matching
combos, so after deleting an earlier combo the next generation reused an
index and produced duplicate names ("U3" twice). Number from the highest
existing index instead.
- ProDesignTab: the utilization bar was colored from the strength ratio
(which excludes anchorage/fit checks) while the status badge used the
overall status, so a member failing only on a detailing check showed a
red FAIL badge above a green bar. New statusBarColor() floors the bar
color to the member status; the numeric ratio (strength utilization) is
unchanged.
Deliberately NOT fixed — these are structural-design correctness issues
that need a PE/domain decision or a data-model change, not a mechanical
patch (documented in the review):
- auto-verify feeds independently-enveloped Mz/Nu/Vy from different combos
into one verifyElement call (non-concurrent P-M pairing), and collapses
axial to an unsigned magnitude so net-tension members are checked with
the compression Vc term (unconservative). cirsoc201 wants Nu +=compression
while station extraction tags n<0 as compression. A correct fix needs
concurrent per-combo force tuples + a confirmed solver sign convention;
a scalar-Nu patch can't be correct for both shear and P-M at once.
- fc = material.fy (Material has no fc field); concrete with fy>80 is
silently skipped. Needs a material category / fc on the model.
- M1/M2 slenderness still endpoint-based on the station path; biaxial
Nu-scaling and Vy-only (no Vz) tie shear need PE validation.
…ign, batch accept) Bounded, non-domain fixes from the review. Web suite: 2125 passing (5 skipped); vite build clean. - ProPanel: the report only re-verified CIRSOC when verificationStore was empty, so after editing the model (store still populated from a prior run) the report showed stale verification results next to current model data. Always re-verify against the current model on report open. - verification-service (runSteelVerification): the station-demand branch collapsed demands with the SIGNED `.value` via Math.max, so a compression- only axial (n<0) or hogging-only moment (Mz-<0) became 0 — designing the steel member as unloaded. Use `.absValue` (the magnitude used for ranking), matching the RC path in auto-verify.ts. NOTE: this path is currently only reachable from the dormant ProVerificationTab, so the bug is latent today; fixing it removes the landmine before that tab is reactivated. - ProDesignTab (acceptAutoDesignAll): accepting auto-design for N elements cloned the whole elements Map and bumped the reinforcement version once per element, and each bump re-ran the WASM station extraction + re-verified every element (O(M·N)). Thread a commit flag through setProvided/ acceptAutoDesign so the batch mutates all elements then triggers reactivity once. Default commit=true keeps all other call sites unchanged. Deliberately NOT fixed (documented in review — need domain work / are intentional): - WASM station extraction samples 11 uniform stations and drops the critical interior stations (point-load/zero-shear peaks) the JS path inserted, so governing moment/shear can be under-sampled (unconservative) in the live Design tab + report. Needs the Rust beam-station extractor to accept/insert critical stations (or reinstating JS critical sampling) — PE-relevant. - Two CIRSOC capacity engines (cirsoc201.ts vs station-design-forces.ts) still coexist; the dormant ProVerificationTab and the unused VerificationReport interface were kept intentionally by the authors as a reactivation reference.
…tmap, LOD double-draw) Web suite: 2155 passing (5 skipped); vite build clean. - solver-service (2D): validateAndSolve2D's preflight credits connectivity from connectors + constraints (and constraint-connectivity.test.ts pins that), but the 2D SolverInput omitted both fields — so a node coupled only via a constraint/connector passed the disconnected-node check and was then handed to the solver with zero stiffness → singular system, masking the diagnostic. Carry `constraints`/`connectors` into both 2D input objects (inline validateAndSolve2D and buildSolverInput2D), mirroring buildSolverInput3D, so the preflight is honest and the 2D constrained solver actually receives them. - results-sync (colorMap wireframe): the batched wireframe mesh was always colored by mean axial force regardless of the selected heatmap variable, so wireframe mode showed axial colors while the legend said shear/moment. applyFrameHeatmap now mirrors the SELECTED variable's color onto the batched mesh using the same heatmapColor()/globalMax mapping as the cylinders. - lod (double-draw): during orbit with result coloring active in solid/sections mode, both elementsParent (kept visible for colored solids) and the batched wireframe were forced on → redundant wireframe overlay + doubled element draw. Suppress the batched mesh when elementsParent is kept for results; wireframe render mode (where the batched mesh IS the rendering) is unchanged. Deliberately NOT fixed (need domain validation — documented in review): - Partial-DOF couplings (single-DOF equalDOF / single-term linearMPC) are credited as full connectivity, so a partial mechanism passes preflight. - Constraint discriminators renamed (equalDof→equalDOF, linearMpc→linearMPC, rhs removed, dofs string→int) with no load migration → old saved/shared models with the old names render as unknown and the solver rejects the variant. - EccentricConnection 2D `releases` length (UI always emits the 6-element 3D array; 2D expects [ux,uz,ry]). These are solver/DOF-semantics decisions for a structural engineer — and gate fully trusting the new 2D constraint wire above.
fix(pro): safe correctness fixes from PR #47 review (report, combos, …
Fix/pr49 verify unification bugs
fix(pro): fixes from PR #51 review (2D constraint wire, wireframe hea…
…report-and-pro-polish Conflicts in ProDesignTab.runDesignCheck: this branch refactored the CIRSOC path into runCirsocDesign (verification-service), while the base added an empty-run guard and stale-result invalidation. Resolution keeps the service call and moves the publish-side semantics into it: an empty CIRSOC run publishes nothing (caller surfaces the error), a successful run clears previous legacy/unified results before publishing; non-CIRSOC codes keep the guard + clear + publish in the component.
Review findings (PR #51): the feature plumbing existed in solver-service but the store call sites never passed it, so connectors were inert in every real solve and the diagnostics false positive survived: - solve3D / solveCombinations3D / solveCombinations3DParallel now pass connectors (PRO-gated, same as constraints); buildSolverInput3D passes constraints + connectors (PRO advanced analyses route through it). - remapModelForPlane carries constraints into the 2D ModelData, so the 2D wire forwarding added by this branch is reachable in production. - ProDiagnosticsTab/ProPanel pass connectors + constraints to checkModel, so the Diagnostics tab stops flagging connector-only nodes as disconnected. - New store-level regression tests that solve THROUGH modelStore (the engine-level wire tests construct ModelData by hand and missed all of this).
…d.connector in clear() A node deletion left connectors (and constraints) with dangling node references — persisted in snapshots/files/share URLs — that the engine silently skips (assemble_connectors: None => continue) while the connectivity preflight kept crediting them as edges, yielding mechanism errors with no diagnostic. removeNode now deletes connectors touching the node, drops rigidLink/equalDOF/eccentricConnection constraints whose master or slave is gone, prunes diaphragm slave lists (dropping emptied ones), and drops linearMPC constraints whole (removing one term would change the equation's meaning). clear() also resets nextId.connector, the only counter it missed. Store-level tests added.
This branch renamed constraint discriminators ('equalDof'→'equalDOF',
'linearMpc'→'linearMPC') and switched DOFs from name strings to integer
indices, but only on the write path. Persisted data (autosaves, saved
projects, share URLs — all rehydrated through restore()) kept the old
shapes: the new constraint-connectivity switch silently skipped them
(false 'disconnected node') and Rust serde rejected them at solve time
('unknown variant equalDof').
restore() now normalizes constraints at the single chokepoint, the same
pattern as the hinge→release and iy/iz read-migrations: discriminators
renamed, DOF name strings mapped to indices, unknown kinds dropped
rather than shipped to the solver. Tests cover each legacy shape.
…vention
ProConstraintsTab always emits 3D semantics (dofs 0..5, 6-bool eccentric
releases) while the Rust 2D solver speaks [0=ux, 1=uz, 2=ry]: a
rotational dof index hard-failed 2D validation ('references DOF 4 but
max is 2') and a 6-bool releases array was silently misread (uy slot
interpreted as uz). New constraint-2d-remap.ts is the single
translation layer: in-plane dofs 0/2/4 map to 0/1/2, out-of-plane
dofs/equations are dropped (the 2D solve restrains them implicitly),
eccentric offsets move vertical Z into the 2D dy slot and releases
collapse to [ux, uz, ry]. Diaphragms pass through verbatim — the Rust
transform is already dimension-aware for them.
Both 2D wire builders consume the remapped list, and the connectivity
preflight credits exactly what reaches the solver (a dropped
out-of-plane-only constraint no longer masks a real orphan).
…ctors The flat-2D→3D embedding (project2DToXZ) remaps node coordinates, supports and loads, but has no translation for constraint DOF indices, eccentric offsets or connector local axes — an offsetY authored in 3D landed on the restrained out-of-plane axis, silently zeroing the lever arm. shouldEmbedFlat2DModelIn3D now refuses models with constraints or connectors (PRO-authored, true 3D semantics): a flat grillage with rigid links solves in direct 3D coordinates instead.
Two windows let the node tool place a coincident-unconnected node ON a bar — the exact trap auto-split exists to prevent: - The cursor-based split search used 0.3m while snapWithMidpoint's midpoint snap reaches 0.4m: clicks in the 0.3-0.4 annulus near a bar's midpoint skipped the split but still placed the node exactly at the midpoint. A second split attempt now runs at the resolved placement point `ms` with a tight 0.01 tolerance, covering both the midpoint snap and grid intersections that lie on a bar. - The duplicate-coincident-node guard only checked the raw cursor (0.5m), while placement happens at `ms`: with grid snap on, `ms` can resolve onto an existing grid-aligned node up to ~0.7m from the cursor, creating an exact duplicate. The guard now also checks the placement point. The split-projection logic is extracted into one attemptSplit helper instead of a second inline copy.
… parsing - lod.ts: shellsParent now honors resultsColoringActive like elementsParent — the shell Von Mises heatmap is painted on the shell groups, so hiding them during orbit made the one visualization shell models care about vanish exactly while the user inspects it. Test added. - Viewport3D restoreColor: hover-out while a color mode is active is now a no-op instead of a full syncColorMap3D() — applyHoverColor never paints in color mode, so there was nothing to restore, and the full resync recolored every element per element-leave (multi-ms stalls on large models). Mode changes mid-hover are covered by the colorMap $effect. - elements-batched flush(): color-only changes skip the position re-upload and computeLineDistances (both depend solely on positions). - ProConstraintsTab: numeric inputs use the comma-tolerant parseNum rule from ProLoadsTab — '0,5' as an offset/stiffness silently parsed as 0 via bare parseFloat on these type="text" inputs. - constraint-connectivity.ts: fix the header claim that 'the Rust solver doesn't care' — its pre-solve isolated-node gate counts elements and connectors but not constraints, so it still emits a warning-level diagnostic for constraint-only nodes (engine-side follow-up).
…m path The web/src/lib/wasm gitignore pattern has a trailing slash, which does not match a symlink to the wasm pkg (used in git-worktree dev setups) — git add -A picked it up. Remove it from the index and widen the ignore.
…ts/connectors"
Deeper review of the reverted gate showed it fixed an unreachable case
while breaking the reachable one:
- The embed only triggers for models with 2D-style support/load types,
i.e. 2D-editor-authored models (fixtures or legacy files opened in
PRO). For those, the embed maps editor-y (vertical) onto solver-z —
so constraints authored in the PRO z-up UI ('uz' = vertical,
offsetZ = vertical) land on the correct axis UNDER the embed. The
'mis-axed offset' scenario required a flat PRO grillage with 2D-typed
supports, which the PRO tools cannot author.
- Refusing the embed created a real cliff for the reachable models:
the blanket out-of-plane restraints stop being injected (instant
mechanisms for hinged/truss models) and 2D nodal loads split gravity
axes against self-weight; and only the solver-side gate changed, so a
flat model rendered in the upright2dIn3d presentation would draw
projected while solving unprojected.
The embed's axis convention is self-consistent for every model that can
reach it; keep it.
Findings from the adversarial review of the fix series itself:
- ProAdvancedTab.buildInput now passes connectors — the 8th hand-built
ModelData literal; without it every PRO advanced analysis (modal,
buckling, P-Delta, time-history...) silently dropped connector
stiffness while the fixed linear solve included it.
- migrateConstraint: an unmappable legacy linearMPC term DOF now drops
the whole constraint instead of silently rewriting it onto ux
(rewriting changes the equation's meaning; consistent with how
rigidLink/equalDOF filter and unknown kinds drop).
- removeNode cascade is now bulkMutate-safe: the commit phase of
bulkMutate overwrites model.constraints/model.loads with its
pre-callback buffers, which would resurrect the pruned entries —
the cascade prunes the buffers too (latent today: no current caller
deletes nodes inside bulkMutate).
- Node tool: the cursor node scan is computed once and shared by the
auto-split guard and the duplicate-coincident guard (two identical
scans that could silently diverge if one threshold is tuned).
- Hinge-mode split gets the zero-length-element guard the extracted
attemptSplit already had (tParam = NaN otherwise).
- linearMPC input: ';' is now the documented term separator (',' still
accepted), and a malformed fragment ABORTS the add instead of being
dropped — with comma separators, a decimal comma in a coefficient
('-0,5') split as a term boundary and committed a silently-zeroed
equation (pre-existing; the comma-tolerant parseNum cannot fix it at
this call site because the separator is also a comma).
diegokingston
added a commit
that referenced
this pull request
Jun 11, 2026
…main) main absorbed PRs #51 and #57 (including their review-fix commits). Conflicts in lod.ts/lod.test.ts: main carried the pr/5 review fix (shellsParent honors resultsColoringActive in the old always-strip LOD) while this branch rewrote the LOD around the heavy-model policy. Resolved keeping the heavy-model structure and porting the shell exception into it: in the heavy fallback, shellsParent now follows the same result-coloring exception as elementsParent (the shell Von Mises heatmap lives on the shell groups). Tests merged accordingly.
diegokingston
added a commit
that referenced
this pull request
Jun 12, 2026
…to main) main absorbed PRs #51/#57/#58 with their review fixes. Semantic resolutions: - scene-sync element signature: union of both extensions (tl + leftHand from main, offset from this branch). - syncLocalAxes: main's always-mode cap / no-selection-read / shells guard combined with this branch's manual-selection semantics ("When selected" = manually selected only). - ProResultsTab query-highlight effect: main's untrack design (fires on query changes only, empty query never wipes) combined with this branch's elementSelectionManual guard (a manual click survives query re-evaluations too). - Triad $effect: selection/manual deps stay implicit (nested reads), preserving the always-mode rebuild fix.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stack: [1]#45 <- [2]#47 <- [3]#49 <- [4]this.
Includes:
Validation on local pr/5 (before opening):
Known engine CI red across the stack: harmonic_3d_5x5_plate_under_15s (solver/engine perf gate, 33.8s vs 30s) — out of scope for this PR.