Skip to content

feat: deliver the fork delta since the 1108c sync wave - #853

Merged
Blogbotana merged 537 commits into
Apibim-com:masterfrom
axisrow:chore/upstream-delta-1108c
Nov 9, 2026
Merged

Blogbotana merged 537 commits into
Apibim-com:masterfrom
axisrow:chore/upstream-delta-1108c

Conversation

@axisrow

@axisrow axisrow commented Nov 8, 2026 •

Copy link
Copy Markdown
Collaborator

What this changes

Delivers the fork delta accumulated since the 1108b sync wave's delivery (#852) as one batch
commit on top of master (the single-batch delivery the fork sync policy prescribes). Two
feature lines, delivered whole:

  • Schedule templates as model definitions (I05): templates are first-class model records
    (crates/model/src/schedule.rs); the schedule command family authors, applies and lists
    them (crates/commands/src/schedules.rs, apps/beamcraft-cli/src/schedule_cmd.rs), user
    attribute templates and element attribute admission follow the same shapes
    (crates/commands/src/attribute.rs), the layer engine validates and edits the records
    (crates/layer-engine/src/authored.rs and friends), and the I05 design note documents the
    model.
  • The six bug-hunt fixes, each with its regression coverage: an arc sweep now builds on
    its authored circle instead of its mirror image (vertex-level test against the circle
    through the three authored points); a bound member's placement is applied exactly once in
    magnetic grid follows and the GR-N22 guard covers placement rewrites; sheet content that
    overlaps the title block refuses LIMIT_EXCEEDED (Liang-Barsky stroke clipping, touching
    the border allowed); a revision with a plate without geometry stays viewable — export-view
    names the skipped plates and create-plate warns on a kept draft; the headless screenshot
    draws and fits the project grids from the same framed export-view --with-grids reply the
    window refreshes with; a bolt group is carried with the part an update-element moves and
    its generated holes regenerate in the same transaction.

The fork-only policy (its workflow forms, docs/plan/, visual evidence, fork tooling) is not
part of this delivery; the upstream-owned Python CI tools are untouched.

How you verified it

  • cargo fmt --all -- --check (recorded gate stage, exit 0)
  • cargo clippy --workspace --all-targets -- -D warnings (recorded gate stage, exit 0)
  • cargo test --workspace (recorded gate: 2707/2707 runnable tests executed with nextest
    0.9.149 on macOS aarch64, plus mandatory doctests and cargo doc -D warnings, all exit 0)
  • The fork's sync merge behind this batch absorbed the feat: magnetic grid bindings for members (T01.8 slice 1) #852 delivery squash, so this tree
    is tested against current master, not against a pre-squash base.

Never-crash

Every model file, IFC/DSTV import and keystroke is untrusted input (AGENTS.md §4).

  • No new unwrap/expect/panic!/todo! outside tests
  • No indexing or slicing with input-derived positions; allocations sized by input are capped
  • No unsafe

Provenance

Read AGENTS.md §1 and §2 before adding any asset.

  • Nothing taken from Tekla Structures or any Trimble product (assets, code, documentation, tables)
  • No GPL/AGPL code copied in
  • No new assets

Anything a reviewer should look at first

  • The magnetic design note keeps the wording the feat: magnetic grid bindings for members (T01.8 slice 1) #852 review settled; the placement fix rides
    on top of it (T01.8 storage section).
  • The title-block refusal lifts existing sheet fixtures clear of the A4/A3 block; documents
    that previously drew over the block now refuse at placement time, and export-view skips
    bodiless plates instead of refusing the whole revision.

🤖 Generated with Claude Code

github-actions Bot and others added 30 commits October 27, 2026 23:54
A cumulative fork branch once rolled back other people's upstream commits on
its way to a mirror PR. The gate makes that failure mode impossible to repeat
silently: it refuses a branch whose base is behind upstream/master (printing
how many upstream commits are missed), refuses deletions of files that exist
in upstream/master unless each path is passed to --allow-delete, and prints a
changed-files and +/- lines summary otherwise. The script is read-only bash +
git; CONTRIBUTING.md gains the matching step 8.

Co-Authored-By: Claude Code <[email protected]>
…from Apibim-com/bug/r01-, Display foreign references instead of validating, bug: admit references under model-sized USD budg
git detects renames by default, so an upstream file moved to a new path
showed as R and slipped past the deletion check of upstream-pr-gate.sh;
the old path vanished from upstream without hitting --allow-delete. Pass
--no-renames so the moved-from path lands in the deleted list.

Co-Authored-By: Claude Code <[email protected]>
…er interface (Apibim-com#733), Install the software Vulkan prerequisite for Lin, Show axis labels and readable actions in the grid editor, Complete T01.6 local grid line tools and interactiv

The workbench conflict was resolved by the owner's decision to adopt the
upstream Inter interface wholesale (apps/beamcraft-ui/src/app/workbench.rs
taken from upstream); every other file follows the fork-sync contract.
…ivery (#263)

* docs: accept MVP amendments and map the M5 CLI delivery dependencies

* docs: record published CLI slices and avoid duplicate prerequisite work

* docs: consolidate the master CI repair reference

* docs: record merged P14 and current-master CI repair
…eshes (P13) (#270)

* feat: persist circular member paths with true arc quantities (P13)

* fix: retain sweep deviation admission before arc length capacity

* docs: state the P14 placement boundary for circular members

* fix: carry the curved flag through the fabrication member adapter

---------

Co-authored-by: axisrow <[email protected]>
chore: add an upstream pull request gate script
…requisite) (#281)

* fix: use the shared fixture in D07 drafting tests

* feat: replace and clear plate corners in one linked-group edit

* fix: return a public typed CLI capacity error for corner lists

* fix: qualify the D07 JSON limit across parent fixture updates

---------

Co-authored-by: axisrow <[email protected]>
* chore: run the local test gate with nextest

The local gate (CLAUDE.md, CONTRIBUTING.md, the PR template and
fork-sync.sh gate 3/3) still ran `cargo test --workspace`, which executes
the 151 test suites serially: 382 s warm. nextest, already the CI runner
and documented in docs/development.md, runs the same 1827 tests with
process-level parallelism in 132 s (measured locally, #285). Doctests
stay a separate gate line because nextest never runs them, exactly like
CI.

Part of #285.

Co-Authored-By: Claude Code <[email protected]>

* chore: describe the nextest plus doctest gate in fork-sync usage

Same stale-gate class as the main change: the usage block still named
bare 'test --workspace' as the gate step.

Co-Authored-By: Claude Code <[email protected]>

---------

Co-authored-by: axisrow <[email protected]>
Co-authored-by: Claude Code <[email protected]>
* test: build the CLI integration suites into one test binary

The 76 integration suites of beamcraft-cli each linked their own ~200 MB
test binary, and `cargo test` runs test binaries one after another, so a
run waited for the slowest case of every suite in turn while the other
threads sat idle. One `cli` binary (tests/cli.rs, one module per suite
file) links once and lets libtest schedule all 454 cases on one pool.

Every suite file stays where it is and keeps its cases; only the shared
`support` module moves to the crate root. The two suites that re-run their
own binary as a crash child now name the child by its module path. The
nextest `fast` profile filters the same suites by module, and documented
`--test <suite>` commands become `--test cli -- <suite>::`.
`macos_icon` checks the desktop app's icon and stays its own target.

Claude-Session: https://claude.ai/code/session_01HF6nmZMJd1rwsnpfakAu3Z

* test: run the independent offline-client cases side by side

The C05-01 suite-scale case authored 32 plates in workspace A and then 32
in workspace B one process at a time; with every commit re-verifying the
draft history this was the longest CLI test by far. The two workspaces
are separate stores, as two offline machines would be, so they are now
authored on two threads; publication, rebase and every assertion stay
sequential and unchanged.

The crash-window case looped over seven commit points with a fresh
fixture each; each point is now its own test so they run in parallel and
a failure names its window.

Claude-Session: https://claude.ai/code/session_01HF6nmZMJd1rwsnpfakAu3Z

* test: fail when a CLI suite file is not part of the cli binary

With `autotests = false`, a new file under apps/beamcraft-cli/tests that
is not declared in tests/cli.rs would never build or run, silently. A
guard test now reads the directory and requires every suite file to be a
module of the binary. docs/development.md says how to run one suite.

Claude-Session: https://claude.ai/code/session_01HF6nmZMJd1rwsnpfakAu3Z

---------

Co-authored-by: Claude <[email protected]>
… work (A13, A03, A04, A12.3, P13, CLI test speed-ups) (Apibim-com#734)

Conflicting files keep the upstream version: the merged delivery is the
new superset and carries the review follow-ups (A13 persistent ordinals
and retired identities, fresh instance bodies, the A04 kind guard, the
edit-drafting replay fingerprint and the restored strict drafting test).
Fork-only infrastructure keeps its fork version.

Co-Authored-By: Claude Code <[email protected]>
…C06) (#296)

* feat: compensate a published operation as a forward draft operation (C06)

A compensation appends one ordinary evidence-bearing draft operation that
restores the target's semantic write groups to the accepted state before it,
materialized against the current published head with the rebase materializer.
The C04 planner runs first and refuses with the bounded report naming every
accepted operation after the target that intersects the target's footprint, so
a later edit by another actor is never rolled back or silently overwritten.
The identity replays exactly once across processes, entry points and crash
windows; the compensated draft publishes through the ordinary path. The wire
operation compensate and the coordinator-compensate client run the same local
semantics with the token actor, who must own the workspace. Acceptance
C06-01..C06-07 is executable in tests/compensation.rs.

Refs #293, refs #12, refs #160.

* fix: refuse a compensation on a dirty draft so unsent work is never dropped

The compensation candidate replaces the whole draft content, so a draft with unsent operations would silently lose them. The precondition now requires a clean draft (draft head at the synced sequence) with a typed remedy, like the C03/C04 discipline of retaining or explicitly renouncing unsent work; the disjoint-edit acceptance now publishes its extra plate before compensating and asserts it survives.

---------

Co-authored-by: axisrow <[email protected]>
…03) (#294)

The coordinator wire only spoke head and publish, so a client had to run
rebase-workspace as a separate local process between publications. The new
rebase operation runs the same guarded local rebase with the token-derived
actor as the only trusted actor, answers the same typed result plus the
current head, never moves the published head, and replays one request
identity across both entry points and crash windows. Acceptance
C03-R01..C03-R08 runs against a real serve-coordinator process in
tests/coordinator_rebase.rs.

Refs #290, refs #12, refs #160.

Co-authored-by: axisrow <[email protected]>
…ead refusal branch (#297)

The coordinator's rebase (C03, #294) and compensation (C06, #296) both derived the trusted
actor by re-parsing the authenticated id's canonical text, so every request carried a refusal
branch that validation had already made unreachable. Uuid7::from_bytes builds the typed host
id from the model id's validated big-endian bytes instead; the canonical text is derived, so
the construction is infallible and the dead INVALID_ARGUMENT branch is gone from both wire
operations. A host unit test pins the canonical formatting and its round trip through parse.

The acceptance specification's header still listed the coordinator rebase (C03) and
compensation (C06) as specified only; both run executable as of 2026-10-30
(tests/coordinator_rebase.rs, tests/compensation.rs), so the header now points at the suites
and names the semantic-conflict workflow (C04) as the part that remains specified only.

Refs #294, refs #296, refs #12, refs #160.

Co-authored-by: axisrow <[email protected]>
PR #288 moved gate 3/3 of tools/fork-sync.sh to cargo nextest, but the
cron runner never installed it, so every pending sync died with
"no such command: nextest", rolled the merge back and exited 3, leaving
the fork behind upstream. Reuse the pinned taiki-e/install-action step
from ci.yml with the same pinned nextest version.

Co-authored-by: axisrow <[email protected]>
Co-authored-by: Claude Code <[email protected]>
The first sync after the nextest fix reached the full test gate and died
in beamcraft-ui: a UI test panics with "a native or software wgpu adapter
is required" on the adapter-less ubuntu runner. Upstream CI installs
mesa-vulkan-drivers for the same tests; give the sync runner the same
package so its gate runs the same code in the same environment.

Co-authored-by: axisrow <[email protected]>
Co-authored-by: Claude Code <[email protected]>
…ontext parser an, feat: simplify properties and create parts inter
…uthored-By trailer (#301)

* feat: fail CI when a PR commit or description carries the Claude Code attribution trailer

* fix: take the PR description from the event payload so the scan cannot fail silently

---------

Co-authored-by: axisrow <[email protected]>
#309)

The K4 row and the deferred paragraph still said C03-C06 were deferred,
but master has moved past that: C03 (#294) and C06 (#296) are merged,
C04 is delivered (upstream #503 closed completed) and C05 is delivered
at suite scale (32 + 32) with its 1000 + 1000 acceptance open (#292,
upstream #504). The row now states the per-package facts in the K6
style, the deferred paragraph lists the remaining post-MVP collaboration
work instead, and the later-work paragraph names the M6 cards C07 and
C08 (upstream #506, #507) that the roadmap did not mention.

Refs Apibim-com#738, Apibim-com#739.

Co-authored-by: axisrow <[email protected]>
…319)

The C04 rows have been executable since PRs #171 (mechanics) and #180
(C04-01..C04-10), but the status line still said "remains specified only".
The implemented, tested conflict-report budget is MAX_REBASE_CONFLICTS = 256
(crates/engine/src/rebase.rs), not the sketched 4096.

Fork issue #291.

Co-authored-by: axisrow <[email protected]>
The original Q01 scenario ends at numbering, publication and a drawing;
the expanded acceptance (fork #318, upstream #334) asks the story to carry
the model tools, views/filters, storey model data, invalid-input and
cancellation regressions, and exact stored outputs, with a record of what
actually executed. The new suite adds those steps as one fresh-binary
story next to the delivered one, and docs/design/q01-alpha-matrix.md
separates the executed cells (macOS/Apple silicon, CLI plus the existing
run_ui suites) from the cells that stay open, with the open dependency
issues named as reasons. The record is evidence, not a gate pass.

Co-authored-by: axisrow <[email protected]>
* docs: add the I04 user-attributes design note

The storage, engine and CLI design for defined user attributes (text,
number, integer, boolean, choice, with units) on any element, their
attribute sets, and the unknown-attribute rule: values shown, never
dropped, definitions mutable in a dedicated schema scope.

* feat: add typed user attributes on any element (I04)

Defined attributes (text, number, integer, boolean, choice, with units
and named sets) live as mutable schema metadata under
/World/Definitions/Attributes; values are bim:attr:<name> properties of
any element, written through the existing edit publication path. A value
may outlive its definition: such an unknown attribute is shown by
attribute-get and never dropped, because every edit is a targeted
property write. Commands: attribute-define, attribute-undefine,
attribute-set, attribute-clear, attribute-get, attributes. A schema
change conflicts as one AttrSchema target; a value change writes only
the element's attributes group. The changeset dependency encoding gains
a v3 magic without retagging v2, and the conservative changeset reads
cover the new group.

* docs: fix the broken intra-doc link of the attribute module

* fix: align the attribute view, note and unit admission with review

values_of no longer presents a leftover value as a defined one when the
attribute was re-defined with another type (it stays in the unknown
list alone); the design note names NOT_FOUND for setting an undefined
attribute; a unit label refuses control characters, as its comment
promised, since it is stored verbatim in USD and JSON.

---------

Co-authored-by: axisrow <[email protected]>
axisrow and others added 21 commits November 6, 2026 12:03
windows-latest currently resolves to the windows-2025-vs2026 image, whose
hosts fail the install-action step with the bash startup failure
(actions/partner-runner-images#169) before any test starts; reruns of the
failed master shards landed on the same image twice. windows-2022 is the
previous stable pool and avoids the affected image, mirroring the macos-15
pin for the same class of outage.

Co-authored-by: axisrow <[email protected]>
The expanded Q01 story walked every delivered modelling surface except the
P14 section placement. Fold it in as its own published store, so the numbered
frame keeps the counts the manual runbook records: a plane/depth placement
with offsets is a pure shift with the exact moved world box, the end offsets
shorten the solid while the authored axis stays put, the flagless default
stays centred (no placement record), refusing placements write nothing, and
the publication round trip preserves the exact placements, bounds and meshes.

Covers fork issue #318 (Q01 expanded alpha, P14 cell).

Co-authored-by: axisrow <[email protected]>
…om#769 follow-up (#523)

* docs: record the owner decisions on items 4 and 6a-6d of the Apibim-com#769 follow-up

The mirrored follow-up (#500, upstream
Apibim-com#769) closed its test items with #488, #493, #502,
#503 and #504; the owner recorded the remaining decisions on 2026-11-06
so the open questions resolve in the plan document: Windows coverage
stays out of the pull-request route with the platform track pinned to
the windows-2022 pool (#521), bent-plate features follow the
developed-contour path of the A10 design (6a), the faceted IFC export
stays (6b), snapping and welding keep facets with the carried analytic
metadata (6c), and a B-Rep kernel stays out of scope, tracked as #522
(6d).

* docs: mark the 2026-11-06 entries as fork-owner decisions

For the upstream mirror the decision-maker is the upstream maintainer
(Blogbotana), and Apibim-com#769 has no answer from them on
items 4 and 6. The recorded decisions are the fork owner's, so the
entries name that subject explicitly and the upstream mirror is left
open instead of reading as settled.

---------

Co-authored-by: axisrow <[email protected]>
…#525)

* fix: decide the bolt grip from the actual faces, not the parts' boxes

A rotated part inflates its conservative box along the shank, so a
knee-brace group's length requirement exceeded every real library length
and #514 had to add a 160 mm entry to unblock the demo commit (issue 519,
defect B). The crossing admission keeps the A05 box rule unchanged; the
grip itself now pairs the shank's crossings with the world triangles of
each part's stored scene mesh: exact under any placement rotation, a
bearing inside a part grips its remaining material, and a bearing over a
pre-drilled hole grips nothing there. A part without a usable stored mesh
falls back to its conservative box, which errs long and never under.

* docs: link the face-interval rule without naming the private helper

* fix: heal a bolt group's own holes and merge the shared-edge double hit

A generate group's re-place ran over the meshes it rewrote itself: its own
clearance holes are coaxial with the shanks, so the grip collapsed and a
plain rename stored a shorter length while a retry of the accepted create
mismatched into OP_ID_REUSE. The parts whose hole records the group owns
now grip per their conservative box wherever the axis crosses no face.

A ray through a shared triangle edge hit both triangles a few ulps apart
and paired into zero-length intervals, collapsing the grip to nothing; hits
that land on one boundary now merge into one crossing (review of PR #525,
issue 519 defect B).

---------

Co-authored-by: axisrow <[email protected]>
Reverts the data change of #514 (b633c22). That entry existed only to
absorb the engine's conservative-box grip for the knee-brace joint;
since #525 the joint resolves its real ~60 mm grip from the actual
faces, so no shipped bolt needs the 160 mm length any more and the
sample list returns to its previous stock lengths.

Co-authored-by: axisrow <[email protected]>
…eavy R01 test (#530)

* docs: pair nextest with doctests in full-suite claims and bound the heavy R01 test

nextest does not run doctests, and the development docs placed standalone
nextest lines beside CI's component checks without saying so: a reader
following them could believe they ran the full gate. State the pairing rule
(nextest plus cargo test --workspace --doc, or the recorded gate that runs
both), recommend reading a sweep's verdict from redirected output rather than
a pipe, and note the omission in CLAUDE.md.

Give the load-sensitive R01 bounding test its own 240-second budget through a
versioned nextest override instead of weakening it: its 9,500-prim source is
what proves the bounded reply, and under concurrent heavy local builds it
outgrows the default 120 seconds and dies on the deadline like a regression.

Fork issue #529 (mirror of upstream Apibim-com#767).

* fix: name the cli test binary in the timeout override filter

The R01 suites are modules of the one `cli` integration binary, so the
override's `binary(reference)` matched no binary and nextest refused the
config at parse time, failing the gate's prepare stage. Filter by the real
binary and the test's full module path; validated with
`cargo nextest show-config test-groups`.

* fix: match the timeout override by exact test name only

---------

Co-authored-by: axisrow <[email protected]>
…ce 3) (#531)

* feat: preview the bolt row and its holes from the bench form (V19 slice 3)

The Bolt bench could place and edit a group, but the viewport showed nothing
until the command committed: a wrong offset or spacing became visible only
after publishing. This adds the display-only preview the owner chose for V19
(the projected 2D markers of the slice plan): the row line, a marker at every
bearing and the outline of every generated hole, drawn over the active view
while the form fills.

The engine decides the preview, as it decides the commit: preview_row derives
the frame, the bearings and the expanded spacings from the group's inputs
alone (the parts' reachability and the length stay the commit's decision),
and generate_holes now takes that row, so the one hole decision serves both
the commit and the preview. The UI reads the parts' bounds, placements and
member identities from the snapshot it already shows, and fetches the form's
definition by the read-only show-library a user types, once per
library-and-designation pair of the loaded listing. A refusal draws nothing;
Cancel still publishes nothing and the saved result stays the CLI's.

* fix: lift the preview's hole rings into world space by the part translation

The engine's generated hole polygons are plate-local XY; the preview lifted
only the mid-plane z, so a translated plate drew its rings off the bearings
the commit cuts (review finding on PR #531). The world lift is the part's own
translation - identity rotation is the only way a plate reaches the hole
decision - extracted into hole_world_rings with its own translated-plate test.

---------

Co-authored-by: axisrow <[email protected]>
#528)

* feat: ride a built-in catalogue set as the default viewport colouring (issue 526)

* feat: pin the owner-approved catalogue hues (issue 526)

* fix: keep the automatic catalogue evaluation off the command lane (issue 526)

* docs: state where the catalogue hues are recorded (issue 526)

* fix: keep the automatic catalogue default quiet and out of perf runs (issue 526)

* docs: cite the recorded colour decision in issue 526

---------

Co-authored-by: axisrow <[email protected]>
… rehearsal) (#533)

* fix: ship the fastener and section libraries in release packages

A clean install could not place a bolt group: the UI bolt panel asks for a fastener library path and the shipped files were not in the archive (rehearsal round 1, fork issue #532).

* fix: flatten branch separators in the release dispatch package tag

A workflow_dispatch on a branch ref put the raw ref name into the archive name, so the smoke test could not find the binaries (rehearsal round 3, fork issue #532).

* chore: restart CI checks after a wedged hosted runner

---------

Co-authored-by: axisrow <[email protected]>
* feat: add screenshot-view, a fitted PNG render of a store head

Visual acceptance needs screenshots without anyone's hands: the window's
default camera sits inside the model and driving the live window loses the
focus war to other apps. The new command renders the verified head through
the native viewport renderer, the same Renderer the window draws with, now
exposed by beamcraft-ui --screenshot with the camera fitted over the whole
scene; the CLI relays it by the same neighbourhood rule the window uses to
find the CLI, so no crate depends on another app.

* test: list the screenshot suite as a module of the ui test binary

autotests is off, and the meta-test requires every tests/*.rs file to be a
module of tests/ui.rs; a separate [[test]] target never built and failed
the gate.

* feat: point the screenshot camera with --yaw and --pitch

Side views like a detail drawing need a direction other than the fitted
default: the angles are radians, pitch above the horizon, refused outside
the orbit bounds, and None keeps the fitted frame byte-compatible with
the previous behaviour. The CLI relays the options verbatim; the ui
validates them where the camera lives.

* ci: retrigger checks after a cancelled run left a tombstone

* fix: refuse screenshot-view inside a session with the typed word

Route::ScreenshotView was missing from the session refusal list, so inside a
session the word fell through dispatch into run_store_command and was refused
with a generic INVALID_ARGUMENT over its unknown --out instead of the typed
cannot-run-inside-a-session refusal export-view gets. Add it to the matches!
and cover the refusal with a session test.

---------

Co-authored-by: axisrow <[email protected]>
…ls (#536)

* feat: give screenshot-view the camera controls of a detail view

Matching a reference picture needs the orthographic projection, a zoom and
a screen-plane pan, not only the orbit direction: a perspective frame of
the whole store never lines up with a drawing-style view of the joint.
--projection ortho, --zoom and --pan X,Y ride on the fitted camera like
the window's own navigation; absent keys keep the fitted perspective
frame byte-compatible.

* feat: colour the screenshot with the default catalogue set

A screenshot-view render drew every element in the neutral shaded colour while
the window colours a freshly opened model by the built-in Catalogue set (526):
plates yellow, members blue-grey. Evaluate the same set by the window's own
query-elements path and build the scene with the overlay, so a screenshot looks
like what the viewport shows (issue 535). Only the built-in set: a headless
screenshot reads no colour-sets file.

* fix: admit a fully specified screenshot-view invocation

The camera options grew the KEY VALUE-pairs bound to 18 argv entries, but the
command now documents ten options, and a call naming every one of them is
twenty entries long and was refused as malformed. Raise the bound to cover the
documented surface and pin it with a fully specified invocation in the
end-to-end test.

---------

Co-authored-by: axisrow <[email protected]>
…#538)

Matching the base-joint demo against its drawing-style reference picture
showed the palette, not the geometry, off (issue 537): concrete took the
member hue because the Catalogue rules match the element type only, and the
renderer's dark clear colour had no light alternative.

screenshot-view gains `--colour-set (Catalogue | "Part classes" | FILE)`,
default Catalogue, keeping today's output byte-for-byte when absent. A
builtin preset name picks that preset; anything else is read as a
colour-sets file and decoded by the existing `colour_sets::Sets::decode`,
refused unless it holds exactly one set of its own; the chosen set is
evaluated by the same `colour_sets::evaluate` path the window uses.
`--background light|dark`, default dark, sets the renderer's public
`background` field to a paper tone near `rgb(245, 246, 248)` after
construction, so the screenshot's own lit-pixel accounting follows it.

The flags ride the CLI adapter verbatim into `beamcraft-ui --screenshot`
like the camera options; the ui's argv caps rise by the two new pairs.
The registry entry and the help section name the new keys, COMMANDS.md is
regenerated, and the end-to-end suite asserts the light background bands,
the file set's hue by channel dominance, the built-in preset path and the
three refusals.

Co-authored-by: axisrow <[email protected]>
…pibim-com#835 closing-reference policy

BeamCraft-Test-Gate: {"version":1,"commit":"025c17c88a475171dd6a2f8171436bdedfc637e7","tree":"2a6318ef7251f92e952df1cf7ad515e7e89b5b45","base":"025c17c88a475171dd6a2f8171436bdedfc637e7","merge_parent":"1cd0aafb9814e524c3cdd7a023303cb7aaba755a","os":"macos","arch":"aarch64","scope":"full","ui_independent_reason":null,"ui_tested":true,"rustc":"rustc 1.99.1 (58cf8f9c3 2026-10-13)","cargo":"cargo 1.99.1 (5f94df478 2026-08-27)","nextest":"cargo-nextest 0.9.149 (3fe4ef319 2026-10-20)\nrelease: 0.9.149\ncommit-hash: 3fe4ef31972f24e0c94cfe291718eab0fe678260\ncommit-date: 2026-10-20\nhost: aarch64-apple-darwin","expected_tests":2665,"executed_tests":2665,"ignored_tests":["beamcraft-test-gate::process::process_fixture"],"source_unchanged":true,"stages":[{"name":"format","exit_code":0,"elapsed_ms":2863,"timed_out":false},{"name":"clippy","exit_code":0,"elapsed_ms":33237,"timed_out":false},{"name":"prepare","exit_code":0,"elapsed_ms":90681,"timed_out":false},{"name":"tests","exit_code":0,"elapsed_ms":232292,"timed_out":false},{"name":"doctests","exit_code":0,"elapsed_ms":2917,"timed_out":false},{"name":"docs","exit_code":0,"elapsed_ms":7123,"timed_out":false}]}
)

The contract row promised a refusal for a grid "named by a saved view",
but saved views carry no grid references and live outside the element
store, so that precondition cannot arise. The honest evidence is the
chain that does exist: an element relationship targeting the grid prim
makes every axis referenced, and the engine guard refuses the removal.
Extract the referenced-axes decision out of mutate_grid and cover it
with a commands-level test over the real bounded reader; record the
delivered limits in the T01 contract.

Co-authored-by: axisrow <[email protected]>
* docs: answer O-G5 and specify the magnetic grid mode

T01.8 was blocked on the owner decision O-G5; the owner delegated the
resolution on 2026-11-08. The magnetic switch becomes the element's own
explicit grid binding: bind-grid/unbind-grid, grid-frame following,
read-time derivation so a shared-grid edit writes only the grid,
GRID_IN_USE for a grid with followers, atomic detach on hand edits, and
FOREIGN_READ_ONLY for pinned foreign grids. The contract section 7.5
is specified, the acceptance cases GR-P17 and GR-N20 to GR-N23 are
written, and every O-G5 deferral note is updated. The delivery itself
is T01.8; the card stays open until the feature lands.

* docs: reconcile the GR-N14 guard with the magnetic binding

The line-removal guard marks every axis referenced whenever any
relationship targets the grid, so a binding relationship would refuse
every line edit and contradict the frame-following semantics. Specify
the blocker-kind distinction: referenced_axes ignores frame-following
bindings (bim:followsGrid) by kind, any other relationship still pins
every axis, and grid deletion answers to every relationship. Mark the
O-G5 row of the open questions answered.

---------

Co-authored-by: axisrow <[email protected]>
* feat: magnetic grid bindings for members (T01.8)

A placed member can now follow a grid frame: bind-grid stores its axis
in the grid's local frame, and the world geometry is derived at read
time, so a shared-grid edit writes only the grid and never rewrites a
section element outside its scope. unbind-grid re-materializes the
world axis and refreshes the derived mesh and quantities.

Guards: a grid with followers refuses deletion (GRID_IN_USE) — the
first real producer behind the delete-side check; line edits stay free
because referenced_axes ignores frame-following relationships by kind
(the GR-N14 reconciliation); any shape rewrite of a bound element
refuses until the binding is released (GR-N22), so the local record and
the world snapshot never diverge silently; a pinned coordination grid
is never followed (FOREIGN_READ_ONLY).

The stored world axis of a bound member is the frozen snapshot of its
last binding event; the layer schema requires it, and the release
overwrites it with the current world. The stored mesh and quantities
lag the frame while bound and are exempt from the strict derived
checks until the release re-materializes them.

CLI: bind-grid and unbind-grid on the grid route; COMMANDS.md
regenerated. Design note: docs/design/t018-magnetic-mode.md.

* fix: address the T01.8 review findings

The pinned-grid refusal now uses pinned_owns_element over the store
(the colon-form selector never matches a bare grid id, so the check was
unreachable and a pinned bind answered NotFound). A shape rewrite of a
bound member is covered end to end: the refused edit leaves the
geometry unchanged and the same edit passes after the release (GR-N22).
The storage docs and the design note describe the delivered semantics:
binding keeps the stored world axis as the frozen snapshot of the
binding event, and a rebind replaces the record while the member stays
bound, so only a release re-materializes the mesh and quantities.

---------

Co-authored-by: axisrow <[email protected]>
…pibim-com#828 view-manager wave and the Apibim-com#837 test fix

BeamCraft-Test-Gate: {"version":1,"commit":"ed0ad4b0af0f50e3466e7dd0668de9c876bde98f","tree":"649dab7ccb8147bb2f0d1f5c23bdc86ea04aa3e2","base":"ed0ad4b0af0f50e3466e7dd0668de9c876bde98f","merge_parent":"b277ffd552033b00989bc2a74505fab4c620d2e5","os":"macos","arch":"aarch64","scope":"full","ui_independent_reason":null,"ui_tested":true,"rustc":"rustc 1.99.1 (58cf8f9c3 2026-10-13)","cargo":"cargo 1.99.1 (5f94df478 2026-08-27)","nextest":"cargo-nextest 0.9.149 (3fe4ef319 2026-10-20)\nrelease: 0.9.149\ncommit-hash: 3fe4ef31972f24e0c94cfe291718eab0fe678260\ncommit-date: 2026-10-20\nhost: aarch64-apple-darwin","expected_tests":2692,"executed_tests":2692,"ignored_tests":["beamcraft-test-gate::process::process_fixture"],"source_unchanged":true,"stages":[{"name":"format","exit_code":0,"elapsed_ms":1696,"timed_out":false},{"name":"clippy","exit_code":0,"elapsed_ms":12294,"timed_out":false},{"name":"prepare","exit_code":0,"elapsed_ms":36694,"timed_out":false},{"name":"tests","exit_code":0,"elapsed_ms":205192,"timed_out":false},{"name":"doctests","exit_code":0,"elapsed_ms":1716,"timed_out":false},{"name":"docs","exit_code":0,"elapsed_ms":4882,"timed_out":false}]}
The M5 delivery decision requires schedule templates to be shared, owned model
USD entities with revisions, footprints, commit and undo; the delivered export
took the template only as a file, so the decision's storage form was missing.
A template now saves as a definition under /World/Definitions/Schedules and
exports by name through the same decode as its file, so the two forms render
byte-identically. The definitions share the attribute schema's conflict domain
and its shared-data rule, and keep the file form's honesty when the attribute
state moves: a displaced column stays, renders empty and is named.

Co-authored-by: axisrow <[email protected]>
)

* Build an arc sweep on its authored circle, not on its mirror image

chord_arc measured the start, on and end angles as atan2(e1 part, e2 part)
while it rebuilds the points as centre + R(cos e1 + sin e2). The faceted arc
was therefore reflected about the diagonal through the centre: a curved beam
stored the right axis and quantities but drew its solid metres away, with the
chord perpendicular to the authored one. The existing quarter-arc test only
checked the volume, which a reflection preserves, so the new test checks
every vertex against the circle through the three authored points and that
the authored ends lie on the mesh.

Fixes #542

Claude-Session: https://claude.ai/code/session_01HF6nmYPqtjLF8X4P7BggmV

* Apply a bound member's placement exactly once in magnetic grid follows

bind-grid mapped the member's element-local axis into the grid frame as if
it were world, and the read-time decode wrote the derived world axis back
into the element-local field, so every consumer that applies the placement
(scene, snap) moved a placed member twice while inspect did not. A member
with a placement offset split away from its magnetic position after a grid
rotation, and consumers disagreed about where it was.

The grid-local record now holds the placed world axis: bind applies the
placement first, decode and unbind map back through the inverse placement,
so the derived axis stays element-local. The GR-N22 hand-edit guard now
also covers placement rewrites of a bound member, which would otherwise
split the record from the member again.

Fixes #549

Claude-Session: https://claude.ai/code/session_01HF6nmYPqtjLF8X4P7BggmV

* Refuse sheet content that overlaps the title block

The whole-sheet fit check only kept strokes inside the frame, so a placed
detail or its annotation could be drawn straight over the title block on
both the SVG and the PDF surface. Every content stroke is now clipped
against the block rectangle (Liang-Barsky, since a glyph or edge can cross
the block with both ends outside) and a crossing of the block's interior
refuses LIMIT_EXCEEDED like content beyond the frame; touching the border
is allowed.

The sheet unit fixtures and two CLI stories sat inside the A4/A3 block and
are lifted clear of it.

Fixes #546

Claude-Session: https://claude.ai/code/session_01HF6nmYPqtjLF8X4P7BggmV

* Keep a revision viewable when it holds a plate without geometry

D-7 retains a wrongly built plate as a degenerate draft, but export-view
then refused the whole revision with DEPENDENCY_UNAVAILABLE, so one bad
contour made the store unviewable in screenshot-view and the window, and
nothing said so at create time. The create-plate help still promised an
INVALID_ARGUMENT refusal that the code no longer gives.

The view bundle already leaves bodiless plates out of its snapshot; export
-view now ships it and names the skipped plates in skipped_plates instead of
refusing. create-plate answers a kept draft with a warning, and its help
describes the retention. Numbering, BOM and IFC keep refusing such a
revision. The F04 on_invalid_shape reject default stays unimplemented; the
K1 note records that the owner decides whether to restate it.

Fixes #545

Claude-Session: https://claude.ai/code/session_01HF6nmYPqtjLF8X4P7BggmV

* Draw and fit project grids in the headless screenshot

screenshot-view rendered elements only and fitted the camera over them,
while the window draws the project grids with their labels and fits the
union of model and grids, so the PNG and the live viewport showed different
scenes for the same store.

The screenshot now loads the same framed export-view --with-grids reply the
window refreshes with, fits GridScene::framing over the scene, and paints
the lines and labels with the window's own GridScene::paint through one
headless egui pass drawn by egui_wgpu over the resolved render. The reply
counts grid_lines; --grids false keeps the model-only picture and fit.

Fixes #543

Claude-Session: https://claude.ai/code/session_01HF6nmYPqtjLF8X4P7BggmV

* Carry a bolt group with the part an update-element moves

Moving a bolted part with update-element wrote only its placement: the
generated holes travelled with the plate while the fasteners stayed at their
absolute points, so the node visibly fell apart and nothing warned.

A bolt group now belongs to its main part. When update-element moves,
rotates or re-orients a connected part, the same transaction carries the
part's world geometry by the rigid delta new * old^-1, moves the row of a
group whose main part moved, re-places every group over the part and
regenerates the generated holes of all of them in one pass. A part that no
longer crosses the row, or a hole the engine cannot generate, refuses the
whole edit with nothing written. A mirror (not rigid) and a move combined
with a shape or material change of a bolted part refuse.

Fixes #544

Claude-Session: https://claude.ai/code/session_01HF6nmYPqtjLF8X4P7BggmV

---------

Co-authored-by: Claude <[email protected]>
BeamCraft-Test-Gate: {"version":1,"commit":"12e8a5bb96e3ac35f55a60a5ce53bb780c6d9d91","tree":"fbc2eba7cfce752f47ea181d2cb232d3ad59d8e1","base":"12e8a5bb96e3ac35f55a60a5ce53bb780c6d9d91","merge_parent":"3ba696a78c79d90fed744868701b2a3f6d8f42f2","os":"macos","arch":"aarch64","scope":"full","ui_independent_reason":null,"ui_tested":true,"rustc":"rustc 1.99.1 (58cf8f9c3 2026-10-13)","cargo":"cargo 1.99.1 (5f94df478 2026-08-27)","nextest":"cargo-nextest 0.9.149 (3fe4ef319 2026-10-20)\nrelease: 0.9.149\ncommit-hash: 3fe4ef31972f24e0c94cfe291718eab0fe678260\ncommit-date: 2026-10-20\nhost: aarch64-apple-darwin","expected_tests":2707,"executed_tests":2707,"ignored_tests":["beamcraft-test-gate::process::process_fixture"],"source_unchanged":true,"stages":[{"name":"format","exit_code":0,"elapsed_ms":1610,"timed_out":false},{"name":"clippy","exit_code":0,"elapsed_ms":20676,"timed_out":false},{"name":"prepare","exit_code":0,"elapsed_ms":60564,"timed_out":false},{"name":"tests","exit_code":0,"elapsed_ms":167103,"timed_out":false},{"name":"doctests","exit_code":0,"elapsed_ms":1715,"timed_out":false},{"name":"docs","exit_code":0,"elapsed_ms":4340,"timed_out":false}]}
One batch commit carrying the fork delta accumulated since the 1108b delivery
(Apibim-com#852): schedule templates stored as model definitions (I05) - the model-level
template records, the schedule command family that authors, applies and lists
them, the layer-engine validation and edit paths, and the I05 design note - and
the six bug-hunt fixes with their regression tests:

- Build an arc sweep on its authored circle, not on its mirror image: the chord
  angles were measured against the rebuilt basis, so a curved beam stored the
  right axis and quantities but drew its solid metres away; the new test checks
  every vertex against the circle through the three authored points.
- Apply a bound member's placement exactly once in magnetic grid follows: the
  grid-local record holds the placed world axis, decode and unbind map back
  through the inverse placement, and the GR-N22 hand-edit guard covers
  placement rewrites of a bound member.
- Refuse sheet content that overlaps the title block: every content stroke is
  clipped against the block rectangle (Liang-Barsky, since a glyph or edge can
  cross with both ends outside) and a crossing refuses LIMIT_EXCEEDED; touching
  the border is allowed.
- Keep a revision viewable when it holds a plate without geometry: export-view
  names the skipped plates in skipped_plates instead of refusing, create-plate
  warns on a kept draft and its help describes the retention.
- Draw and fit project grids in the headless screenshot from the same framed
  export-view --with-grids reply the window refreshes with, painted through one
  headless egui pass.
- Carry a bolt group with the part an update-element moves: the rigid delta
  re-places the group's row and regenerates the generated holes of all its
  groups in one transaction, refusing the whole edit when a hole cannot be
  regenerated.

Fork-only policy paths are not part of the delivery.
@axisrow
axisrow requested a review from Blogbotana as a code owner November 8, 2026 16:10

@axisrow axisrow left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Approved

Reviewed the full delta against upstream master (46 files, +2557/−152): the I05 stored schedule templates and the six bug-hunt fixes with their regression tests and design-note updates. The diff is clean over the merge-base and matches the delivery message.

Checked in depth:

  • Arc sweep (#542): the chord angles now use the same atan2(e2, e1) convention the rebuild below uses (centre + e1·radius·cos + e2·radius·sin), so the mirror-image bug is gone. The new test checks every vertex against the independently solved circumcircle for four cases, including both bulge directions and a three-quarter arc.
  • Magnetic binding (#549): grid_local_axis/grid_world_axis thread the placement through; all three callers (bind, unbind, plate decode) are updated consistently, and the new GR-N22 placement guard closes the drift the old decode allowed. Bindings recorded under the old semantics are covered by the standing retire-no-migrate rule documented in t018-magnetic-mode.md.
  • Bolt follow (#544): follow_moved_part refuses mirror and move+shape/material combos before anything is written, carries faces and bounds conservatively, re-places every group over the moved part, and regenerated_holes drops and rewrites shared-plate record sets once with continued ordinals. Refusal paths and the single-undo-unit property are covered by the new test.
  • Title block clip (#546): the Liang–Barsky check matches the drawn block rectangle exactly, border touching stays allowed, and the existing drawing fixtures that sat over the block were moved clear instead of weakening the check.
  • Stored schedule templates (I05): definition prim, conflict domain and shared-data rule mirror the I04 attribute pattern; the publish pipeline is reused via publish_model with identical replay semantics; define-time admission equals the file form and byte-identical export of both forms is asserted, including the unknown-and-stale behaviour after an attribute is undefined.
  • export-view (#545): skipped_plates replaces the refusal for a revision holding a bodiless plate, create-plate warns at create time, and help, registry and COMMANDS.md all describe the same surface.
  • Headless screenshot grids (#543): empty grids fall back to the scene framing, the egui pass renders into the same Rgba8Unorm target with LoadOp::Load, and a missing font degrades instead of failing. The CLI relays --grids and validates it before any render.

Two non-blocking observations for the record, neither introduced by this delivery: a shape-only edit of a bolted part still leaves its generated holes untouched (the new guard covers only a move combined with shape or material change), and the behaviour flip of export-view on bodiless plates intentionally changes two existing test expectations, which are updated alongside.

Requesting no changes; ready to merge from my side.

@Blogbotana
Blogbotana merged commit 1938e11 into Apibim-com:master Nov 9, 2026
27 checks passed
axisrow added a commit to axisrow/BeamCraft that referenced this pull request Nov 9, 2026
…elivery squashes

BeamCraft-Test-Gate: {"version":1,"commit":"978895022f0e95d87c1cf933d2944144d3ffb6a6","tree":"4726355e8bef11ef0082b824034e86156e571003","base":"978895022f0e95d87c1cf933d2944144d3ffb6a6","merge_parent":"7c21d0248c24ce7830d9841b6bbe9a567e30a5d9","os":"macos","arch":"aarch64","scope":"full","ui_independent_reason":null,"ui_tested":true,"rustc":"rustc 1.99.1 (58cf8f9c3 2026-10-13)","cargo":"cargo 1.99.1 (5f94df478 2026-08-27)","nextest":"cargo-nextest 0.9.149 (3fe4ef319 2026-10-20)\nrelease: 0.9.149\ncommit-hash: 3fe4ef31972f24e0c94cfe291718eab0fe678260\ncommit-date: 2026-10-20\nhost: aarch64-apple-darwin","expected_tests":2709,"executed_tests":2709,"ignored_tests":["beamcraft-test-gate::process::process_fixture"],"source_unchanged":true,"stages":[{"name":"format","exit_code":0,"elapsed_ms":1797,"timed_out":false},{"name":"clippy","exit_code":0,"elapsed_ms":27432,"timed_out":false},{"name":"prepare","exit_code":0,"elapsed_ms":80354,"timed_out":false},{"name":"tests","exit_code":0,"elapsed_ms":183280,"timed_out":false},{"name":"doctests","exit_code":0,"elapsed_ms":1620,"timed_out":false},{"name":"docs","exit_code":0,"elapsed_ms":4542,"timed_out":false}]}
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.

2 participants