feat(formwork): gangs, crane and logistics — and the fourth castable element - #654
feat(formwork): gangs, crane and logistics — and the fourth castable element#654areebhammad wants to merge 128 commits into
Conversation
Add the attachments module (Attachments/Attachment/AttachmentPreview/ AttachmentRemove) from elements.ai-sdk.dev and update AiChatPanel to match the current PromptInput demo: header with inline attachment previews, action menu with attachment upload + screenshot capture, footer toolbar. Model-select and web-search controls from the demo are omitted since the chat route uses a single fixed Bedrock model.
…nels, ties, walers)
…r panel - Double-sided panels (panel-front/panel-back) with through-ties and walers on both faces - Scaffold generation: uprights, ledgers, diagonal braces (both faces) when scaffoldRequired - FormworkHostSummary read-only inspector showing host wall construction params - AI harness updated: instructs AI to ask about scaffold access, sets scaffoldRequired - All 7 formwork tests pass
- Shimmer: animated gradient text shimmer for loading states - Reasoning: collapsible block for model reasoning/thinking text - Suggestion: clickable chip buttons for starter prompts - Task: collapsible grouping for multi-step tool call sequences - scroll-area: shadcn scroll area wrapper (dependency of suggestion) - All ported from upstream ai-elements to local @/ import conventions - biome format applied
…gestions, auto-hide - Item 5: selected node context chips above composer, prepended to messages - Item 6: file attachment rendering in messages (FileUIPart -> Attachment) - Item 7: chat history persistence per-scene via localStorage - Items 10/11: browser-chrome auto-hide header (hide on scroll-down, reveal on up/top) with New chat button - Items 13-16: wire Reasoning (always open), Suggestions (starter chips), Task (tool call grouping) - Fix: remove standalone busy shimmer (redundant with streaming content) - Fix: keep Reasoning open always (prevents disappearing content after stream ends)
…pty state - On 'New chat': save current messages to localStorage archive list instead of discarding - Show 'Previous conversations' section in empty state listing archived sessions - Each archive shows a preview of the first user message + message count - Click an archived session to restore it as the active chat - Archives persist across page reloads
- Suggestions now sit directly above the prompt input (not centered mid-panel) - Archive preview strips the injected 'Context: ... (id: ...)' prefix so it shows the actual user message - Empty state now shows title + Previous conversations list, suggestions moved out
- Empty state: gradient icon tile, balanced copy, polished 'Recent chats' cards with message-count badge - Archive preview now computed from stored messages at render time (previewOf) instead of a saved string, so stale archives that predate the Context-prefix strip also render the actual user message - Dropped the stored preview field from ChatArchive
- parseContext strips the 'Context: <label> (id: <id>)' prefix at render time - Context nodes show as secondary badges above the clean message body - Model still receives the ids in the sent text; only the display is cleaned
- secondary badge matched the bg-secondary bubble and read as plain text - switch to outline variant with translucent bg + border so it reads as a pill
faint outline pill was invisible on the dark bubble; use bg-primary/20 with a primary ring so it clearly reads as a badge
empty state now shows only Recent chats when present; suggestions above input remain the primary affordance
Guards against a `dist/` the app would bundle but no source in the repo produces. Builds the library packages in CI so the check has something to compare against. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The domain research the formwork engine is built against: which faces need forming and why, the pressure/design standards, and the catalog products. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Replaces the single `formwork-system` node with two kinds that match how the work is actually divided: - `formwork-assembly` — the shutter for one (element × segment × lift). A 9 m wall is not formed in one go, and each lift is separately erected, struck, and paid for, so each needs its own node to hang parts and overrides off. - `construction-joint` — the interface between two pours. The joint owns the work (roughening, starter bars, shear key, waterstop), so hanging it off either element would make the other one's shutter wrong. `CastableFields` and `ShutteringFields` are shared by wall, column and slab so the coverage engine reads one shape regardless of host kind. Wall's inline formwork fields move onto `ShutteringFields`; wall, column and slab now host both new kinds as children. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The rename has to carry the id, because `objectId('formwork-assembly')` rejects
a `formwork-system_…` id — leaving it alone would fail parse and drop the node.
Renaming the key means every host's `children` entry is rewritten in the same
pass, or the wall keeps pointing at an id that no longer exists.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contract measurement rules — IS 1200-5, NRM2, HKSMM4, CESMM4, POMI. The same element yields materially different quantities under each: what is deducted for an opening, and whether a narrow face is billed by the metre or by area, are clause decisions rather than geometry. Banding turns a face into the item the standard actually bills. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`CastableElement` normalises a wall, column or slab into one shape — a centreline, a footprint, and its openings — so the coverage engine reads one thing regardless of host kind. Footprint overlap and junction detection find where elements meet, which is what decides whether a face is buried against a neighbour or exposed and needs forming. Force-added: the repo's bare `coverage` gitignore rule (for test-coverage output) also matches this source directory. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An opening is not simply subtracted: each standard sets a threshold below which it is not deducted at all, and the reveals returned into it are formwork the face gains. Trim removes the run buried where a neighbour lands on a face, with `ownsOverlap` deciding which of the two elements bills a shared overlap so a takeoff does not count every junction twice. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A tall wall is poured in lifts capped by tie capacity and the pressure envelope; a long one is cut into segments so no single bay exceeds the batch plant's supply or the shrinkage limit. Both are geometric constraints, so the split is derived rather than annotated. `HardCut` carries the joints the solver may not move — an expansion joint is a hard partition no pour may cross. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A pour unit is the cross product of segment and lift — one shutter erected, poured and struck on its own. Every cut between two units is a real construction joint carrying roughening and starter bars, so `jointsForElement` emits it as a spec: work that exists on site has to exist in the takeoff. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…d, and idle money is priced The cut list now recognises a floor that repeats: identical board fingerprints across levels nest once, and the sheet counts are the purchase for the repeated floor — one set plus the replacements a stated sheet life implies, never one set per level, with the reuse stated out loud (6.4). An owned asset used past its stated life is charged as the replacement the overrun implies rather than as further uses of a dead panel, the line, both counts and the money named (7.5). A stated finance rate prices the money the job ties up over the programme's own span, beside the cash total and never inside it, undated pours named (7.6). And the datasheet research on the six unseeded systems found four of them are column or slab lines the wall-panel registry cannot represent, Mivan has no public item list, and only Doka Frami's item list awaits transcription — the registration reasons now say exactly that.
…hey form, and provenance gets its vocabulary The FINANCE and OVERRUN rows land in the bom CSV beside the total, and the cost-basis sentence stops saying "no finance" the moment a finance figure appears below it. The datasheet research on the six unseeded systems is turned into a capability decision: a system declares which element kinds it forms, the per-kind rating replaces the wall-pressure invariant, and a system used on a kind it does not support is refused with both names — recorded in design.md, spec'd, and task 4.6 re-pointed at it. And the provenance group's vocabulary ships: Verification is now certified/derived/ secondary/unverified with the fold stated, the converted APA Plyform values are honestly derived with their unit factors beside the table citation, the column clamp capacities name the document that would certify them, and the open-items table is a per-item register naming what closes each row. The Frami item-list attempt is recorded honestly: its grid is attested, its weights need the PDF read in hand.
…ravels, and the clash checks get their inputs Co-Authored-By: Codebuff <noreply@codebuff.com>
… the forms meet the line TIES_THROUGH_REBAR, PROPS_ONTO_SLAB_BELOW and FORMWORK_OUTSIDE_BOUNDARY — the last three clash passes, reading the group 9 inputs. Each reports its own silence per element (notChecked entries gain elementIds, rendered by the panel), and the solve's own falsework evidence reaches the prop check through ShutterEvidence rather than a second design pass. 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
The delta specs from formwork-plan-remainder — catalog seed, asset amortisation, cut-set covering, deferred clashes, rated pressure, solver pipeline, standards provenance and value engineering — are now main specs under openspec/specs/, so archiving the change will not lose the spec'd requirements. 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
…then measured Groups 11 and 12. Core saving-patch.ts owns the keyed saving proposal — class, target, alternative, money from the same cost model as the printed total, a decision-only key that outlives rate edits and dies with the decision, the superseded refusal, and the no-total caveat. Nodes apply-saving.ts derives the five classes: substitution off the shipped system comparison, cycle off the resequencer's clearable moves priced against the hire they avoid, and the other three answered with the nothing-cheaper / missing-input distinction the spec demands — then plans the whole-or-none writes and reports a second solve's measured saving against the claim, symmetric in either direction and unmeasured rather than confirmed where it cannot re-derive. 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
…how much of it is certified The value-engineering loop closes: `formwork_savings`/`apply_saving` on all three surfaces over one core pair — a saving keyed `class|target|alternative` with the money deliberately excluded, applied whole-or-none and judged from a second solve with the measurement the answer in either direction, and the parity test pinning that a key produced by one surface is taken by the other two (the fix it forced: a cleared shortage reads as zero hire rather than unmeasured). Beside it, 8.5 puts the verification level on the face of every document — the cut sheet, elevation and design report now print the same weakest-wins note the caveats and CSV carry — 8.8 proves a certification changing a number is attributable to the certification, 4.7 records the H20 permissible-versus-design conflict with both values and both sources and reports it wherever the beam governs, and 4.5 completes the APA Plyform set with Class II. 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
…he two seeding blockers open The change is 68/70 tasks — the two remaining (Mivan 4.4, PERI/Doka 4.6) are blocked on vendor datasheets that are not publicly obtainable, not on code, and their content now lives in the wiki's open-items register so the procurement stays actionable. All eight delta specs were already synced to the main tree. Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
…ion shipped The strength/maturity striking criterion landed in the same change the at-a-glance P6 row predates; the row's closing claim is corrected to say the criterion ships report-only by design, with maturity over a temperature history the deliberate non-implementation. Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
…astable Three of the plan's "Later" items close together: architectural faces can now state the symmetric tie-hole module they are read by (rows sit on it uniform, an overload answers with a lower rate rather than a moved grid, and the layout cuts panel joints and tie stations to the lines), a formwork-box-out node kind lets a sleeve or penetration be a first-class void the host shutter cuts around, and beams become a castable element — two wall-chain side shutters tied across the width over a propped soffit, with coverage, design, parts, striking and the AI surfaces all taking them. 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
| return { | ||
| status: 'noop' as const, | ||
| message: 'Every wall, column and slab already has formwork configured.', | ||
| } |
There was a problem hiding this comment.
Beams omitted from construction plan
Medium Severity
planConstructionPackage still treats only walls, columns, and slabs as castable, while chat-ai already includes beam. Beams without formwork are skipped by the scene-wide planning pass even though the comment claims the same set of kinds, so the fourth castable never gets auto-planned.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 9b42760. Configure here.
| const { answer } = await hook | ||
|
|
||
| const result = await applyConstructionAnswer(stored, elements, answer) | ||
| return { status: 'done' as const, ...result } |
There was a problem hiding this comment.
Stale scene after plan wait
High Severity
The workflow loads the scene once, then can suspend for a long time on the construction question hook before mutating and saving that same snapshot with the original expectedVersion. Edits made during the wait are overwritten or the save conflicts, so the answered plan may never land on the live graph.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 9b42760. Configure here.
| // Scene changed under us (another editor/MCP session saved first). | ||
| // The AI's answer already streamed to the client either way. | ||
| console.error('POST /api/chat: failed to persist mutation:', error) | ||
| } |
There was a problem hiding this comment.
Chat mutations fail silently
High Severity
Tool calls mutate the in-memory graph and stream success to the client, but a failed store.save on version conflict is only logged. The user sees completed construction tools while the scene never updates, with no error surfaced in the chat UI.
Reviewed by Cursor Bugbot for commit 9b42760. Configure here.
…e children union rejected the void id, so AnyNode.parse threw and the whole scene failed to load. Admit formwork-box-out ids to both unions and extend the castable-children guard (beam as a host, box-out on wall and slab) so a scene round-trip can never drop a void again. Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
| ) | ||
| } | ||
|
|
||
| const CASTABLE_TYPES: readonly AnyNode['type'][] = ['wall', 'column', 'slab'] |
There was a problem hiding this comment.
MCP inventory omits beams
High Severity
list_castable_elements filters with a local CASTABLE_TYPES that excludes beam, while castableOrRefusal in shared.ts accepts beams. Agents reading the inventory never see beams in elements or unshuttered, so bills and attach flows can look complete while beam formwork is missing.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 29d9558. Configure here.
|
|
||
| /** The description every surface's attach carries. */ | ||
| export const ATTACH_FORMWORK_DESCRIPTION = | ||
| "Generate or update the formwork for a wall, column or slab, built for that kind: two tied faces for a wall, a clamped box or wrapped shaft for a column, a propped soffit deck plus edge forms for a slab. Only the faces the pour sequence actually leaves exposed are formed. An element with a lift cap or an expansion joint gets one assembly per pour unit, since each is erected, poured and struck separately. Call this after set_element_construction once formworkType is not 'none' — the user wants to see the formwork, not just set the properties. Safe to call again on an element that is already shuttered: it reconciles rather than duplicating, so a pour unit that still exists keeps its existing shutter and every per-part decision on it, only genuinely new pour units are built, and shutters whose pour unit has gone are removed. Call it again after any change to the pour limits, and read back what it reports — if it says decisions were discarded, tell the user which." |
There was a problem hiding this comment.
Attach copy ignores beams
Medium Severity
ATTACH_FORMWORK_DESCRIPTION (and related part-inspection copy) still describe only walls, columns, and slabs. Chat and MCP feed this string to the model, so agents are not told how beam shutters differ (side ties plus propped soffit) even though beams are now attachable.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 29d9558. Configure here.
…n and moved The box-out node existed only in the engine: nothing in the app could make one, and a beam once placed was frozen at its placement defaults. Both surfaces now have the writes they were missing. add_box_out (shared core builder, MCP tool, chat tool) records the void on its wall or slab — parented, in the host children so a scene load keeps it — and the shutter re-cuts around it with the release details on the reveals. The beam gets a two-click centreline draw tool with grid and 15-degree angle snap and a live ghost, plus the generic XZ move, so a placed beam can be positioned. Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
…Frami research captured Open item 3 gets its first close: WISA-Form Birch 18/21 are in the catalog with bending and stiffness transcribed per grain direction from the UPM datasheet (characteristic f_m divided by the 1.3 material factor the sheet itself states, to the permissible basis). Rolling shear is not published by UPM, so it is carried at the band value 2.4 and the entries stay secondary rather than certified. Open item 7 gets its research: the Frami Xlife item list and the 40 kN/m2 DIN 18218 pressure were extracted from the Doka PDFs and recorded in products.md, correcting the sections earlier 50 kN/m2 guess. The seed stays a named refusal because the tie-hole chains and tie capacities sit in the manual drawings, and the layout must not draw ties where the frame is not drilled. Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
| const settings = formworkSettingsFor(Object.values(nodes)) | ||
| const candidate = hostId ? nodes[hostId] : undefined | ||
| if (!candidate || !(CASTABLE_TYPES as readonly string[]).includes(candidate.type)) { | ||
| return { host: undefined, units: [], settings } |
There was a problem hiding this comment.
Beams omitted from castable lists
High Severity
BeamNode is the fourth castable, but several CASTABLE_TYPES filters still list only wall, column, and slab. Project solve, BOM, schedule, value engineering, the design report (BeamReport is unreachable), the construction-plan workflow, and MCP list_castable_elements therefore skip beams even though chat tools and the shared summary already accept them.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit b5520e1. Configure here.
The Handbook of Finnish Plywood (the reference the UPM datasheet itself cites) Table 3-7 tabulates planar shear per thickness and direction — 18 mm 2.67/2.34, 21 mm 2.59/2.41 N/mm2 characteristic — so the carried 2.4 assumption is gone and every input of the WISA-Form Birch entries is a transcribed published value, converted per direction with the documented 1.3 material factor. Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
The generic movable translated position, which the centreline body ignores; the beam now owns a bespoke mover (start/end translate together), an endpoint-drag action + tool for reshaping the span, and a 2D floorplan trio (band builder, move target, endpoint affordance) with corner-picker and side-arrow handles in 3D. ToolManager gains a beam branch in its endpoint reshape dispatch. Also captures the Frami extraction attempt: the tie-hole chains are drawing-only, but the manual's text layer yields the tie-count rules and the anchoring hardware list.
| 'not the sides. Faces that butt concrete already cast are not formed, so a wall between ' + | ||
| 'earlier-cast columns gets two sides and no stop-ends, and a column in the plane of a wall loses ' + | ||
| 'the faces embedded in it — that follows from castOrder, so ask about pour sequence rather than ' + | ||
| 'assuming everything is freestanding. ' + |
There was a problem hiding this comment.
Chat prompt ignores beam shutters
Medium Severity
The chat SYSTEM_PROMPT tells the model it can inspect and form walls, columns, and slabs, and describes only those three shutter machines, while CASTABLE_TYPES and the tools already accept beams. The model is steered away from stating construction and attaching beam formwork correctly.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit b68e87f. Configure here.
A linear-resize handle anchored at the soffit, sitting over the beam midpoint at the top edge — drag it up and the side shutters get taller, with the floating dimension pill and a 0.2 m floor. The inspector stays, but the section is now editable in the viewport like the fence's height.
| surfaceClass: 'horizontal', | ||
| thicknessM: scoped.coreThickness, | ||
| soffitHeightAboveSupportM: scoped.soffitHeightAboveSupport, | ||
| }) |
There was a problem hiding this comment.
Beam soffit thickness uses width
High Severity
Beam soffit faces pass thicknessM from coreThickness, which is the beam’s plan width. Soffit thickness banding and prop/falsework staging need the concrete depth (depth), so a deep narrow beam is billed and staged as if it were only as thick as its width.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit cb3449b. Configure here.
…rops Attach formwork to a beam and the parts list returns both machines the beam owns: the side shutters (a wall chain lying on its side, tied across the width) and the propped soffit (bearers and props off the floor below). Guards the join where a bill of only sides would total cleanly and omit the propping entirely.
…corrected
The plan's §12 note had lumped CESMM4 in with POMI's "no deduction
≤ 1.00 m²" reading; the CESMM4 Class G full text says the opposite —
openings and holes not exceeding 0.5 m² are ignored — which the shipped
standard already guessed but flagged unverified. It is now certified and
the POMI-only flag stands.
Also corrects the plan's EN 13670 reference: striking is §5.7 "Removal
of formwork and falsework", not §5.5 ("Special formwork").
| // body aligned with start→end. | ||
| const heading = Math.atan2(endZ - startZ, endX - startX) | ||
| body.rotation.y = -heading | ||
| group.add(body) |
There was a problem hiding this comment.
Beam formwork wrong coordinate frame
High Severity
Beam shutter geometry is built in a local centreline frame (X from start along the span), but the beam body is drawn in level coordinates at the real start/end with a heading rotation, and the assembly stays at identity. The shutter only lines up when the beam already runs along world +X from the origin; otherwise sides, ties, and soffit appear in the wrong place.
Reviewed by Cursor Bugbot for commit 7c12e76. Configure here.
Dragging a beam endpoint now carries every sibling beam sharing that corner with it, in 3D and in plan — the corner cascade walls get, lean: no bridges, no pivot planning. Alt detaches and leaves the neighbours put. Shared helpers live in cascade.ts so the 3D action and the 2D affordance cannot drift. Also covers the 3D handles with tests (corner pickers, depth arrow, side moves).
|
|
||
| try { | ||
| const run = await start(planConstructionPackage, [parsed.data.sceneId]) | ||
| return NextResponse.json({ runId: run.runId }) |
There was a problem hiding this comment.
Missing CORS on plan success
Medium Severity
Successful construction-plan responses use bare NextResponse.json, while OPTIONS, guards, and error paths go through sceneApiPreflight / sceneApiJson. Cross-origin clients that pass preflight can still fail to read successful start, poll, or resume payloads.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 128ec12. Configure here.
The endpoint-drag angle label previously measured against walls and fences only, so a beam junction showed nothing. Linked beams sharing the corner are now part of the neighbour segments, tracked live as the cascade moves the junction, so the angle reads against the beam it meets. The picker moved to a pure module (angle-label.ts) and got tests: the lone-beam null, the linked-beam junction, the wall junction, and the mid-drag cascaded corner.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
There are 11 total unresolved issues (including 10 from previous reviews).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit cf11cf1. Configure here.
| formworkCoverage: | ||
| n.type === 'wall' || n.type === 'column' || n.type === 'slab' | ||
| ? formworkCoverage(n, bridge, standardId ?? DEFAULT_MEASUREMENT_STANDARD_ID) | ||
| : undefined, |
There was a problem hiding this comment.
Beam coverage never returned
Medium Severity
The describe_node tool text says beams with a formworkType return formworkCoverage (tied sides and propped soffit), but the runtime gate only calls formworkCoverage for wall/column/slab, and that helper’s host type excludes beam. Beam describe replies always omit coverage.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit cf11cf1. Configure here.
…shows the beam Two 2D/3D parity closes for the beam: - the floor plan now emits the angle at a junction with a sibling beam (the 2D twin of the endpoint-drag angle pill), from ctx.siblings so the builder needs no scene access - the whole-move drag renders the translucent BeamGhost at the live preview span, so the side-move arrows show where the beam lands instead of only the cursor Tests cover the plan junction angle (present when selected + a sibling shares an endpoint, absent otherwise).
…one shared picker The beam junction-angle pill now has a shared pure picker (shared/junction-angle.ts) that the wall floor plan also consumes: a selected wall whose endpoint meets a sibling wall shows the angle at the shared corner, the 2D twin of the wall 3D endpoint-drag pill. The beam module became a thin re-export so both kinds read the same way. Review suggestion applied: the per-id linked-beam subscription trips the rules-of-hooks linter (it cannot prove the id list stable), so it derives from one subscription with the why recorded.


What does this PR do?
Builds out the formwork engine end-to-end across the previous sessions' plan: the schedule and logistics half (pour timing, gang cost, crane lifts, holding cost, set counts, concrete arrival) and the design half (panel layout, ply sawing/nesting, tie design, striking criteria, lift joints, alternate-bay curing, value-engineering against other catalog systems with certified savings). It also closes the MCP/AI surface: pours can be stated and moved, named parts can be addressed, and refusals name what would clear them.
This session adds the three remaining "Later" items:
specifiedTieGridMmfield (settable via the construction tool) that fixes the tie module symmetric about the wall's midpoint with a centre-line column; when the grid overloads a tie, the design now lowers the pour rate to bring the pressure under capacity rather than silently moving the exposed-face ties.formwork-box-outnode kind — a real void-former node (position, size, draft angle, chamfer strips) registered alongside door/window openings, so a host wall cuts its panels around the void, returns all four reveal faces, and carries the release details on its part descriptions.BeamNode(centreline start/end, width, depth, soffit elevation) with side shutters tied across, a propped formed soffit (falsework chain), per-systemsupports.beamsratings, striking targets, and full coverage/BOM/AI-surface support.How to test
bun run check-types && bun test— all suites green (geometry, design, coverage, construction-patch, catalog, MCP, chat-AI).Screenshots / screen recording
N/A — non-visual change (engine, schema, MCP surface, and tests).
Checklist
bun devbun checkto verify)mainbranchNote
High Risk
New authenticated API routes stream AI tool calls that mutate and save scene graphs, plus durable workflows—security, concurrency, and Next server/client bundling boundaries need careful review.
Overview
Adds an in-editor AI assistant wired through a new
/api/chatroute that streams Vercel AI SDK responses, mutates the scene JSON viabuildToolsfromlib/chat-ai, and persists drafts with optimistic concurrency on finish. The AI sidebar tab (AiChatPanel) layers selection context, attachments/screenshots, grouped tool-call UI, per-scene localStorage history/archives, and live updates viaapplyAgentSceneGraphToEditorwhen SSE pushes a newer graph version.Construction planning gets durable workflow HTTP endpoints: start
planConstructionPackage, poll run status, and resume paused runs through/api/construction-plan/*and/api/construction-plan/answer.Tooling/docs: vendors a full OpenSpec skill set under
.agents/skills/, pins.openspec-targettocodex, extends CI withturbo buildforpackages/*plusbun run check-dist, and expands AGENTS.md with a four-command “done” gate (types, tests, biome, build) and server-import rules for API routes.UI foundation for the chat experience adds shadcn-style primitives (
button,badge,collapsible, etc.) and a largeai-elementscomponent library (conversation, prompt input, tools, reasoning, code blocks).Reviewed by Cursor Bugbot for commit 295b745. Bugbot is set up for automated code reviews on this repo. Configure here.