Skip to content

feat(formwork): gangs, crane and logistics — and the fourth castable element - #654

Open
areebhammad wants to merge 128 commits into
pascalorg:mainfrom
areebhammad:feat/formwork-gangs-crane-logistics
Open

feat(formwork): gangs, crane and logistics — and the fourth castable element#654
areebhammad wants to merge 128 commits into
pascalorg:mainfrom
areebhammad:feat/formwork-gangs-crane-logistics

Conversation

@areebhammad

@areebhammad areebhammad commented Aug 14, 2026

Copy link
Copy Markdown

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:

  • Architectural tie grids — a specifiedTieGridMm field (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-out node 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.
  • Beams as the fourth castable element — a BeamNode (centreline start/end, width, depth, soffit elevation) with side shutters tied across, a propped formed soffit (falsework chain), per-system supports.beams ratings, striking targets, and full coverage/BOM/AI-surface support.

How to test

  1. bun run check-types && bun test — all suites green (geometry, design, coverage, construction-patch, catalog, MCP, chat-AI).
  2. Open the editor, drop a wall, and in the chat tool state a tie grid (e.g. "600 mm tie grid"): the tie rows sit uniform on the module, symmetric about the wall's centre, and an overload reports the slower pour rate instead of widening spacing.
  3. Add a box-out to a wall: the shutter panels split around it, four reveal faces appear, and its part descriptions carry the draft/chamfer.
  4. Drop a beam (start/end centreline, width, depth): side shutters tie across and a propped soffit forms underneath; the design report shows the beam chain and falsework chain; the BOM includes the beam's ties, props and bearers.
  5. Value-engineer the project with a system that supports beams and check the difference report.

Screenshots / screen recording

N/A — non-visual change (engine, schema, MCP surface, and tests).

Checklist

  • I've tested this locally with bun dev
  • My code follows the existing code style (run bun check to verify)
  • I've updated relevant documentation (if applicable)
  • This PR targets the main branch

Note

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/chat route that streams Vercel AI SDK responses, mutates the scene JSON via buildTools from lib/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 via applyAgentSceneGraphToEditor when 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-target to codex, extends CI with turbo build for packages/* plus bun 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 large ai-elements component 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.

areebhammad and others added 30 commits July 27, 2026 02:22
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.
…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>
KALS and others added 10 commits August 14, 2026 18:44
…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.',
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

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 }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

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)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

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']

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

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."

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 29d9558. Configure here.

Comment thread packages/nodes/src/formwork-assembly/design.ts
KALS and others added 2 commits August 15, 2026 00:48
…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 }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit b5520e1. Configure here.

KALS and others added 2 commits August 15, 2026 01:01
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. ' +

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

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,
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit cb3449b. Configure here.

KALS added 2 commits August 15, 2026 01:44
…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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

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 })

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

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.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Fix All in Cursor

❌ 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,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit cf11cf1. Configure here.

KALS added 2 commits August 15, 2026 02:14
…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.
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.

1 participant