GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Fix picking of I-section members and plates with holes (#63) by axisrow · Pull Request #187 · Apibim-com/BeamCraft

Conversation

@axisrow

@axisrow axisrow commented Oct 23, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Collaborator

What this changes

Mirror of axisrow#84 (fixes fork issue #63): ray picking now hits bodies
without FeatureIds — I-section members and plates with through holes — by their mesh:
the nearest front-facing triangle names its element only, with feature_id None.
Bodies a query cannot consider at all (no engine mesh, the point form, no face target)
stay in PickResult::skipped rather than being approximated.

This is the first of two stacked mirrors from the fork: the R01 read-only reference
work builds on this picking behavior and follows as a separate PR.

How you verified it

  • cargo fmt --all -- --check — clean.

  • cargo clippy --workspace --all-targets -- -D warnings — clean.

  • cargo test --workspace — all suites green, 0 failed across the workspace, including
    the new/updated pick tests: I-beam and holed-plate mesh hits, near-parallel crossings,
    point-form skips.

  • Applied onto master (236ce9b) as a single cherry-pick of the fork squash commit;
    only an auto-merge in apps/beamcraft-ui/tests/edit_tools.rs, no manual conflict
    resolution.

  • cargo fmt --all -- --check

  • cargo clippy --workspace --all-targets -- -D warnings

  • cargo test --workspace

🤖 Generated with Claude Code

Issue tracking

Fixes #375

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@axisrow
Fix picking of I-section members and plates with holes (#63) (#84)
11e25ec
* Pick mesh bodies at element level so every rendered part is selectable

`pick` only tested the canonical-box features (F06 section 9), so an
I-section member or a plate with a through hole never produced a hit and
the UI cleared the selection on click (the engine listed the body in
`skipped` and the editor read only `hits`). A ray pick now also
intersects the body's mesh: the nearest front-facing triangle answers an
element-level hit with `feature_id` `None`, so box bodies keep their
stable feature ids and mesh bodies become selectable without inventing
roles for them. The point form and `snap` still consider box features
only, as before; the hit record never carries a triangle or buffer index.

Fixes #63

* Record the element-level mesh pick amendment in the V04 note

* Bring the V04 note's evidence up to the mesh-pick test series

---------

Co-authored-by: axisrow <[email protected]>
@axisrow
axisrow requested a review from Blogbotana as a code owner October 23, 2026 12:49
@axisrow axisrow mentioned this pull request Oct 23, 2026
feat(view): display attached read-only reference geometry (#95) #188
Merged
3 tasks done
axisrow
axisrow commented Oct 23, 2026
View reviewed changes

@axisrow axisrow left a comment

Copy link
Copy Markdown
Collaborator Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Review of #187: promotion of the mesh-picking fix (head 11e25ec)

Requesting changes: one documentation-precision finding; the code itself is sound.

What I checked and consider correct:

  • The blob layout math in mesh_hit matches the kernel format exactly: 32-byte header, vertices at 32 + 24i as three f64 LE, triangle indices at 32 + 24vn as three u32 LE; every read is bounds-checked, so a residual mismatch surfaces as NumericUncertain, never a panic.
  • The ray-AABB prefilter bounds the scan; the slab test and the barycentric closed-triangle test are standard and correct; degenerate triangles are skipped; ties keep the first scanned triangle, which is unobservable, as the code comments.
  • The pick() integration keeps the contract: box bodies keep their FeatureIds, mesh bodies answer element-level hits with feature_id None only on the ray form with a face target, point form and other targets still skip; ordering handles the None feature id deterministically.
  • feature_id becomes JSON null in the CLI output; the V04 amendment records the F06 hit-record extension as an explicit decision.
  • Tests are proportionate and derived, not read back: engine unit tests on a real kernel prism (front and back faces, include_backfaces, miss beyond the slanted edge while still scanned, skipped cases), CLI end-to-end through the binary on the exact issue shapes (I-section, holed plate, a ray through the hole hits nothing), and a real click-path UI test through the camera ray and command::select.

The finding: the skipped documentation lists a missing engine mesh as a skip cause, but that state cannot occur and the code answers it with an error, not a skip. Details inline.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread crates/engine/src/query/pick.rs Resolved

Uh oh!

There was an error while loading. Please reload this page.

@axisrow @claude
Drop the impossible no-engine-mesh skip cause from the picking docs
44f9fed
slot_of resolves every element mesh digest at snapshot build and fails the
build otherwise, and mesh_hit answers a missing mesh with NumericUncertain on
the ray form, not a skip: "no engine mesh" cannot occur as a skip cause.
Named only the real causes in the module doc, the PickResult::skipped doc and
the V04 amendment.

Co-Authored-By: Claude Code <[email protected]>
axisrow
axisrow commented Oct 23, 2026
View reviewed changes

@axisrow axisrow left a comment

Copy link
Copy Markdown
Collaborator Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Re-review of #187 (head 44f9fed)

Requesting changes: the no-engine-mesh clause was correctly dropped from all three places (module doc, skipped field doc, V04 amendment), but the module doc edit left a stray duplicated line behind: the replaced sentence already ends with rather than approximated, and the old closing line approximated survives after it, so the paragraph reads approximated approximated. Delete the leftover line. Nothing else changed and the rest of the previously reviewed diff stands as approved-quality.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread crates/engine/src/query/pick.rs Resolved

Uh oh!

There was an error while loading. Please reload this page.

@Blogbotana Blogbotana mentioned this pull request Oct 23, 2026
FeatureIds for non-box bodies: holed and contour plates, I-section members #190
Closed
Blogbotana
Blogbotana previously approved these changes Oct 23, 2026
View reviewed changes

@Blogbotana Blogbotana left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Approved as an interim step within the plate work. Mesh hits with a null feature_id are not an F06 change: F06 0.1.0 stays as it is, and giving these bodies FeatureIds is tracked in #190. One documentation fix from the earlier review is still to land before merge: a missing engine mesh is not a skip cause (the snapshot build refuses it and the ray pick answers it with an error). The wording is corrected in the module doc, the PickResult::skipped doc and the V04 amendment.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@axisrow @claude
Drop the stray duplicated "approximated" from the module doc
5cc03b8
The previous sentence already ends with "rather than approximated"; the
conflict resolution left a second copy of the word on its own line.

Co-Authored-By: Claude Code <[email protected]>
@axisrow
axisrow dismissed Blogbotana’s stale review via 5cc03b8 October 23, 2026 13:11
axisrow
axisrow commented Oct 23, 2026
View reviewed changes

@axisrow axisrow left a comment

Copy link
Copy Markdown
Collaborator Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Re-review of #187 (head 5cc03b8)

Approving. The stray duplicated approximated line is gone and the module doc now reads correctly; the no-engine-mesh clause remains dropped from the module doc, the skipped field doc and the V04 amendment, exactly as requested. Nothing else changed since the last review, and the rest of the diff stands as previously assessed: the blob layout math matches the kernel format, the scan is bounded, the pick contract is kept, and the test evidence is proportionate and derived.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Blogbotana
Blogbotana approved these changes Oct 23, 2026
View reviewed changes

@Blogbotana Blogbotana left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Re-approving after 44f9fed and 5cc03b8: documentation only, the impossible no-engine-mesh skip cause is gone from the module doc, the PickResult::skipped doc and the V04 amendment. FeatureIds for these bodies are tracked in #190.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@axisrow
axisrow merged commit d3cad42 into Apibim-com:master Oct 23, 2026
6 checks passed

Uh oh!

There was an error while loading. Please reload this page.

axisrow referenced this pull request in axisrow/BeamCraft Oct 23, 2026
@axisrow @claude
Merge upstream master into the fork: R01 reference geometry (#188), I…
df53bcb
…-section mesh picking (#187), inspection cache used_by index (#184), craft coordination docs (#186), assembly op_id replay (#183), K1 plate contour cleanup (#174)

The upstream squashes carry the same work the fork already has under its own
SHAs, plus the promotion adaptations: the doc fixes from the #187/#188 review
round, the used_by index widening #184, and the #184 design doc rewritten for
the upstream provenance. All six conflicts are the double-landing of promoted
work, so the upstream versions are taken as canonical per the 1:1 contract:
apps/beamcraft-cli/src/plate.rs, apps/beamcraft-cli/src/view_export.rs,
apps/beamcraft-cli/tests/inspection_cache.rs,
crates/engine/src/query/pick.rs, docs/design/issue-26-inspection-cache.md,
docs/design/v04-picking-snaps.md. Nothing fork-exclusive was dropped
(diff master..upstream/master checked per file before the resolution).

Co-Authored-By: Claude Code <[email protected]>
@axisrow axisrow mentioned this pull request Oct 23, 2026
Support rigid placement for members through view and picking axisrow/BeamCraft#117
Merged
@axisrow
axisrow deleted the ao/planning-mode-design/promotion-mesh-picking branch October 24, 2026 06:54
@axisrow axisrow mentioned this pull request Oct 25, 2026
PR #187: Fix picking of I-section members and plates with holes (#63) #375
Closed
Blogbotana added a commit that referenced this pull request Nov 6, 2026
@axisrow @claude
@github-actions @Blogbotana Yury Isachenkov
feat: deliver the fork delta since the 1106 sync wave (#834)
25c4fd1
* feat: render a published drawing version to PDF and print sheets (D08) (#244)

D08 adds the two export surfaces that D03's publication lacked. Both bind to
one exact published document version, reusing the strictly resolved stroke
geometry that the SVG export already produced, so fonts and symbols stay
BeamCraft's own contributor-original stroke drawing: no font is embedded,
referenced or substituted anywhere (AGENTS.md sections 1 and 2).

- engine `drawing::pdf`: deterministic single-page PDF 1.4 built from
  path-painting operators only. No /ID, clock or random field: identical
  version inputs give identical bytes. Literal-string metadata escapes
  backslash, parentheses and every non-printable byte as three-digit octal.
- engine `drawing::print`: the print pipeline shape. Resolved strokes are
  laid out 1:1 in paper millimetres, centred on a fixed A4-A0 portrait
  sheet; a drawing that cannot fit its sheet refuses with LIMIT_EXCEEDED
  instead of silently rescaling, because a rescaled print would misstate
  every drawn size against its model-millimetre label.
- svg refactor: the resolved stroke groups, bounds and glyph/arrow helpers
  are shared by all three export surfaces; SVG bytes are unchanged.
- CLI `export-drawing` gains `--format svg|pdf` and `--sheet a4|a3|a2|a1|a0`
  (pdf = print-ready centred sheet page); the drawing contract document
  module map records the new submodules.

Native printer spooling (OS dialogs, CUPS/GDI) stays a desktop-app
follow-up; the headless pipeline now ends in the print-ready page.

Checks: cargo fmt --all; cargo clippy --workspace --all-targets -- -D
warnings; cargo test --workspace; cargo run -p beamcraft-cli -- --version.
Engine unit tests cover PDF structure (xref offsets, no /Font), print fit
refusals and translation-invariant centring; the CLI end-to-end test prints
one published version onto A4 twice and asserts identical bytes, refusals
for unknown sheet/format and untouched document heads.

Fixes #232

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

* feat: lay plate thickness off the contour plane (A12.1) (#251)

* feat: lay plate thickness off the contour plane (A12.1)

A plate gains the input thickness_side: top, middle or bottom. The contour
stays in the element-local plane z = 0; the side says where the thickness t
is laid off from it. Top is the default and keeps every older plate: absent
input, absent patch field and absent stored token all read as top, and the
top path is bit-identical to A02.

The engine lays the plain kernel mesh off with the production rigid map and
re-certifies it with its own face table, normals and tolerance, so holes
pierce the laid-off band and plane-cut records replay on the laid-off body.
The side is stored as the optional steel:plate:thicknessSide token and
changes the body cache key and the shape signature. Numbering compares
side-blind facts, so two plates differing only in the side share a number.

Refs #192 (slice A12.1 of the complex-contour issue; A12.2 and A12.3 follow).

* fix: encode the thickness side in the UpdateElement fingerprint

An op-id retry that differed only in thickness_side hashed equal and
silently replayed instead of refusing with OP_ID_REUSE. The side now rides
behind the append-only tag 5, like translation_mm and level, so the
fingerprints of older patches are unchanged.

---------

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

* feat: fastener definitions and libraries pinned by digest (A14.1) (#259)

* feat: fastener definitions and libraries pinned by digest (A14.1)

Slice A14.1 of the complete bolt groups card (refs #194): a fastener
definition in a fastener library in the A07 format, pinned into the
project by content digest, with a synthetic example library and one
European DIN set. The group, its placement and the generated holes are
the following slices (A14.2-A14.4); this slice adds no element and
touches no engine command.

- beamcraft_model::fastener: FastenerSpec with the bolt amendment's
  fields (kind, hex head, nut, washer, hole clearance, standard
  lengths, thread protrusion, declared mass, washer and hole defaults)
  admitted by the F04 codes and caps plus the definition's own
  consistency rules; DefinitionCategory::Fastener.
- beamcraft_layer_engine: Definition::Fastener rendered as a kind
  "fastener" definition at /World/Definitions/Fasteners/FST_<sha256>,
  re-read and re-admitted like a profile; the mapping descriptor gains
  the fastener line.
- beamcraft_cli: load_fastener reads one fastener library file with the
  profile loader's budgets, identity and refused-entry reporting,
  entries under /Library/Fasteners; list-library and show-library take
  --fastener-library (an alternative to --library/--environment, no
  environments yet per O-B2); define-fastener pins one entry into the
  store as an immutable definition, so the model never needs the
  library file again; add-library refuses fastener records (the
  shipped file is the provenance record).
- assets/libraries/synthetic-fasteners.usda: three contributor-original
  synthetic entries (washered bolt, bare bolt, screw with hole_mode
  none). assets/libraries/din-fasteners.usda: M12/M16/M20 hex bolts
  with DIN EN ISO 4032 nuts and DIN EN ISO 7089 washers, clearances
  from the EN 1090-2 standard hole series, re-entered as our own data;
  both recorded in ATTRIBUTION.md.
- docs/design/a14-1-fastener-libraries.md records the slice, its
  decisions and its limits.

Acceptance covered here: an unknown designation is refused (NOT_FOUND,
never a near match); every shipped value has a recorded source and
licence; pinning is proven end to end (the digest the listing predicts
is what the store pins, and a fresh process reads the definition with
the library deleted). "A group never needs the library after pinning"
is completed by A14.3 when groups consume the digest.

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

* fix: stop the public fastener docs linking a private loader item

The quality job documents with RUSTDOCFLAGS=-D warnings; the intra-doc
link from load_fastener to the private load_bytes_as helper broke that.
The link is replaced by the public load_fastener_bytes chain it already
describes.

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

* fix: the index checks the fastener definition category like the other two

The U03 index guards still knew two definition categories: the
kind/category cross-check admitted a fastener-kind definition at a
profile path and a section kind at a fastener path, a malformed leaf
under /World/Definitions/Fasteners passed unflagged, and a digest-less
fastener link target lost its digest diagnostic. DefinitionCategory
now maps each bim:definitionKind to its category, all three guards
cover Fastener, and the index test carries the fastener cases.

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

---------

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

* feat: V11 selection switches and snap panel (#255)

* feat: add the V11 selection switches and the snap panel

The model window's status bar now carries the eight selection switches
(parts, assemblies, bolts, welds, holes, grids, construction objects,
items) and a Snap switch that opens a snap-kind strip above the bar. A
switch is picking state only: it is consulted when a pick or a grid
hit-test runs and read by nothing else, so it can never move the model
or the snapshot token.

The engine's pick query carries the switches as a SelectionFilter (a
class that is off is left out of the scan without a refusal or a skip
entry), and the CLI's pick exposes it as --exclude with the class
tokens, so the same filter is reachable headlessly. Placement point
resolution is deliberately untouched: it is a tool's point resolution,
not a selection, and the tools consume the snap panel's kinds in T01.6.

Switches for classes that have no registered snapshot kind yet (bolts,
welds, holes, construction objects, items) are accepted and exclude
nothing until those kinds arrive.

Fixes #197

* docs: update the status bar doc comment to the V11 switches

---------

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

* feat: typed forward path for resolution receipts across undo and redo (C04) (#256)

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

* test: prove the C05 two-offline-client acceptance end to end (#258)

The C05 acceptance rows of the C03-C06 note (docs/design/c03-c06-acceptance.md) had no
executable tests: the offline two-client harness, the retry path after a base mismatch,
the exactly-once publication across commit crash windows, the interleaved coordinator
publications, forced identity collisions and the bounded failure surface. This adds them
as one real-binary suite, offline_clients.rs, without touching production behavior.

The suite bounds its element count (32 + 32) because every commit re-verifies the whole
model; the 1000 + 1000 sweep stays recorded as out of scope in the note. The coordinator
rebase (C03 remainder) and compensation (C06) rows stay specified only: their runtime is
deferred after the MVP by owner decision and needs one focused PR per C-package.

Refs #160

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

* fix: make the fork-sync PAT the only path for workflow-file pushes (#278)

* bisect: fork-sync without the checkout token line

* bisect: fork-sync without the workflows permission

* fix: make the fork-sync PAT the only path for workflow-file pushes

The previous attempt granted the default token `workflows: write` next to
`contents: write`. GitHub rejects that: the whole fork-sync.yml fails
startup validation on every push and the schedule cannot start at all
(cron dead since 13:27Z, bisected 2026-10-27 - the file without the
workflows scope validates, with it does not).

The job therefore keeps `contents: write` only. The optional
FORK_SYNC_TOKEN secret stays the opt-in path for upstream commits that
touch .github/workflows/*: without the secret such a sync stops at the
push with the script's exit 4 (a red run is the alarm, per the sync
contract); with it, the push carries the account identity and every
synced master tip gets a CI verdict.

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

---------

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

* feat: own drawn paper objects in the drawing document (D07 slice 1) (#271)

The drafting card needs geometry primitives that belong to a drawing and
never to the model. Add bounded drawn objects (line, polyline, arc, circle,
rectangle) as document-owned intent: one full UUIDv7 each, exact paper
millimetres, radians, admitted through the same D01 admission and persisted
in the closed canonical intent USD under /Drawing/Drafting (schema
descriptor extended, canonical re-encode comparison unchanged).

The new edit-drafting command replaces the drawn set from the document
alone - it takes no model store, proving the card's acceptance that drawn
objects never touch the model. Published versions stay immutable: an edit
after publication lands as a fresh draft on top and the old version still
exports its exact bytes. inspect-drawing reports the drawn objects.

Refs #231. Text, hatching, symbols and layers/line types are the later
slices; SVG/PDF rendering of drawn objects follows separately.

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

* feat: loft a ruled surface between two contours (G13) (#273)

* feat: loft a ruled surface between two contours (G13)

Two planar contours of equal vertex count form one closed certified solid: straight
rulings join the index correspondence, wall quads follow the contour edges (split
along the interior diagonal when a skew correspondence folds them non-convex), and
the G05 cap triangulations close both faces. The sheet thickness is the distance
between the contours themselves, so a bent plate authors its outer and inner bend
contours and a lofted plate its two face contours directly.

Arc contours carry the proven G05 sagitta bound; cylinder and cone patches are the
translated-arc and two-radius lofts, checked in the fixtures against the annular-sector
and cone closed forms. Fold lines report the rulings at which an authored contour does
not continue straight, and the seal verifies the exact oblique-prism volume for exact
translations (Cavalieri).

Refs #186: slice 1 of the card; the acceptance gate stays with the issue.

* test: seal a folded-wall skewed loft through the interior-diagonal split (G13)

Review follow-up: every accepted fixture had convex wall quads, where both
diagonals are valid, so the diagonal choice in wall_triangles was untested on the
acceptance path. The new fixture (a skewed five-point loft from the seeded random
scan over random contours and end frames) seals only through the interior-diagonal
branch: with the default split forced, the certification refuses this exact body
(checked by mutation). The loft carries independent volume bounds (positive, under
its bounding box) and an area floor (the two cap areas), plus the divergence-theorem
read-back of the blob.

Refs #186.

---------

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

* feat: sweep a member along a polyline path with one mitred section (P12) (#257)

* feat: sweep a member along a polyline path with one mitred section (P12)

A polybeam is a member whose axis holds a polyline of three to 1024 element-local
points instead of two; the point count alone distinguishes it from the P03 straight
member, and the stored `bim:axisPointsMm` attribute and its schema are unchanged. The
body is the G12 sweep of the one pinned section along that path: every interior vertex
is a shared mitre ring, the end rings stay perpendicular to the end tangents, and the
section is parallel-transported with no authored roll.

The quantity of record is the effective section area times the polyline path length.
G12 corollary 1 carries the acceptance: for a section centred on the path every mitre
plane crosses the centroid fibre at the vertex, so the certified mesh volume is
area x path length and the mitre angles drop out; the seal cross-checks the mesh
against that analytic value before anything is stored. Cuts keep the certified replay
volume.

Point editing is a typed `UpdateElement` patch field: the complete path replacement,
authored as `Change::Axis` on the canonical part of an instance group. Two points
straighten a polybeam member, three or more make it a polybeam again; the body, mesh
and quantity records follow. `create-polybeam` exposes the command in the CLI, and the
BOM and numbering member length becomes the body's own path length.

Fixes #187

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

* style: reflow a polybeam test assertion after the mesh bound fix

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

* fix: clear the member roll when an update bends it into a polybeam

An update that bends a rolled straight member into a polybeam kept the authored
rollRad the sweep silently ignores; the opinion is now cleared and authored as
removed on the canonical part. A patch that names path points on a plate target
is refused with UNSUPPORTED_PROPERTY instead of dropping the field, and the
module doc no longer links the private commit tail.

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

---------

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

* feat: five viewport render modes on user-remappable shortcuts (V16) (#276)

* feat: five viewport render modes on user-remappable shortcuts (V16)

The engine defines the modes in one query (RenderMode: wireframe,
transparent, grey, shaded, selected only); the window keeps one loaded
scene and only switches the drawing pipeline, so a mode change never
reloads, re-uploads or moves the camera. Selected only reads the chosen
element with one engine query into a cached single-instance draw of the
mesh the device already holds.

Shortcuts live in shortcuts.json in the user configuration directory
(BEAMCRAFT_CONFIG_DIR or --config-dir with --store): Ctrl+1 to Ctrl+5 by
default, a damaged file keeps the defaults and is reported, and one key
never drives two modes.

--perf-run gains a phase per mode name, so one run records the frame-time
baseline of every mode over the loaded scene.

* test: a config directory is accepted with a store window

--config-dir now names the shortcuts file's directory in the model
window too, so the usage test refuses the combinations that are still
invalid: an author without a model and a metrics run with a config
directory.

* fix: a perf mode phase resets the render mode on its boundary

A mode phase left App.mode set after its last frame, so a following
phase without a mode of its own drew its frames in that leaked mode
while the report labelled them with its own name. The boundary frame
now re-states the next phase's mode, or shaded after the last mode
phase, so a mixed plan measures what its report says.

---------

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

* refactor: share the changed_layers publication tail and the CLI test fixtures (#272)

The copy/edit command adapters each carried an identical changed_layers
computation; publication::changed_layers replaces those 13 copies. The CLI
integration suites each carried a private temp-store fixture, binary runner
and JSON accessors; support::StoreFixture now backs them and the shared
text/at accessors replace the local copies, keeping command-specific change
construction, admission and replay decisions, evidence and id-map harnesses
and domain expectations local. Behavior and the CLI output contract are
unchanged; the workspace suite passes before and after the refactor.

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

* feat: place a member's section about its axis (P14) (#274)

* feat: place a member's section about its axis (P14)

A straight member's section could only sit centred on its axis. Real
detailing needs the section off the axis: a beam flush with a grid line,
a column face on a boundary, a solid that starts below a floor slab while
its axis stays on the grid. The axis points stay the modelling line; the
position moves the material.

create-member authors an optional SectionPlacement: plane (left/centre/
right) and depth (front/centre/back) with additive offsets in the rolled
local section frame, and start/end offsets along the axis that move the
solid's faces, so its length follows and must stay positive. The rotation
about the axis stays the existing roll. Columns share the whole path.

The default placement keeps the kernel's centred body bit for bit: the
P03 vertex expressions are used verbatim when nothing is displaced, so
mesh bytes, quantities and signatures of members authored without the
fields are unchanged. Position is stored as six optional USD attributes
(all six or none, absent = centred); the schema descriptor changes, so
older pre-alpha stores are refused without migration, the standing
pre-alpha policy. Body-cache keys and the P-Mass gross signature include
the placement. Design note: docs/design/p14-section-placement.md.

* style: cargo fmt on the P14 end-to-end tests

* fix: the kernel checks the P14 offset caps itself

The frame-error mapping re-checked the offsets, but it only runs when the
frame is refused, so a finite offset past the dimension cap built through
a direct kernel call or an untrusted committed store re-evaluated without
the model admission. Every builder now admits the offsets (finite, within
1e6 mm) before the frame, with kernel and engine regressions.

* fix: adapt the P14 replay to the polybeam member slice

---------

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

* fix: restore CI after shared D07 fixture and G13 guard changes (#279)

* fix: use the shared fixture in D07 drafting tests

* fix: simplify the G13 finite sweep admission guard

* feat: treat plate contour corners with a chamfer or a rounding (A12.2) (#275)

* feat: treat plate contour corners with a chamfer or a rounding (A12.2)

Slice A12.2 of the complex-plate-contours task (refs #192, the F04 plate
amendment draft section 2). A contour vertex may carry a chamfer (one or two
cut distances) or a rounding (a radius) as an authored parameter; the sharp
contour stays stored. The evaluator derives the treated outline in f64 (arcs
faceted to at most the 0.05 mm chord deviation of the round-hole rule), the
kernel validates it with its exact predicates, and arc_deviation_mm reports
the largest kept deviation. A treated origin rectangle takes the contour
path, so quantities are those of the faceted body under the new
kernel-a12-corner-treatment-volume label.

A treatment on a corner that does not really turn, and treatments whose
consumed lengths reach an edge length, are INVALID_ARGUMENT (field corner);
values are bounded like other coordinates, and a derived outline beyond the
F04 contour cap is LIMIT_EXCEEDED. Holes and plane cuts replay on the treated
outline. The five parallel steel:plate:corner* attributes author together or
not at all; copies, instances, rebases, undo and reopen carry them exactly.
Numbering compares the evaluated outline, so plates whose treated outlines
differ share no number; quantity signatures gain the block only for treated
plates, keeping older stores' records current. No UpdateElement patch and no
corner editing commands: those are the A12.3 slice.

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

* docs: correct the numbering outline comment and bound arc segments early (review notes)

The outline_of comment still said the origin rectangle has no outline fact;
a treated origin rectangle carries its treated outline (A12.2). The arc
faceting now refuses, before it allocates, a rounding whose segment count
alone exceeds the F04 contour cap, so a radius near the coordinate cap cannot
expand to hundreds of thousands of points on its way to the same refusal.

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

* test: finish migrating the drafting suite to the shared fixtures

The merge of #272 left tests/drawing/drafting.rs on the removed Fixture
fields and the unscoped Limits import, so the pick suite - which includes
it through a path module - no longer compiled and clippy refused the whole
workspace. This is the tail of that refactor, included here to unblock CI;
no production code changes.

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

---------

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

* fix: drop the unused Limits import from the drafting test (#283)

A12.2 (#275) removed the last Limits use from tests/drawing/drafting.rs but
kept the import, so cargo clippy --workspace --all-targets -D warnings fails
on master (test target pick) and the fork-sync gate refuses every sync.
Remove the dead import.

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

* feat: line, polygon and part cuts, fit, chamfer, split and combine as A-2 records (A13) (#282)

* fix: review follow-ups for the A13 fabrication slice

Rebased onto current master, so the A12.2 corner-treatment files that an
earlier stale-base push showed as deleted are untouched. The split replay
now requires the created half and returns both halves from the head; a
combine refuses a parallel axis offset across the survivor's line; the
split fingerprint hashes the label bytes; a member target of a polygon or
part cut is the documented UNSUPPORTED_GEOMETRY; polygon cuts and part
cuts carry the member admission; tests cover the split retry, the offset
refusal and the member target.

* fix: a split replay compares both halves with the probe

Presence of the created element alone still replayed a retry that renamed
the created half or fired after the target gained a further record. The
hold now compares the head created state with the probe created state, so
any changed content under the accepted operation id is a reuse refusal,
and the a13 suite covers the renamed retry.

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

---------

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

* feat: switch between a perspective and an orthographic camera in one window (V13) (#249)

* feat: switch between a perspective and an orthographic camera in one window (V13)

The orbit camera gains an orthographic projection next to the perspective one,
switched from the ribbon's View tab (PROJECTION group). The switch keeps the
target, direction and distance, and the orthographic window always follows the
distance (half-height = distance * tan(half FOV)), so the model does not jump
and zoom, pan, fit and the distance bounds mean the same in both projections.

One shader draws both: params.x carries the field angle in perspective and the
half-height in orthographic, so the lateral division is unchanged; the mode
slot picks the w (view depth vs 1) and the depth mapping. The pick ray follows
the image: under the orthographic projection every pixel is a parallel ray
along -back from the laterally offset eye point, so what a click selects and
where a placement lands keep matching what is on screen (acceptance).

The camera uniform grows from 80 to 96 bytes for the mode slot.

Closes #198

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

* fix: pass the selection filter to pick_in in the orthographic click test

The rebase onto master kept two-argument pick_in calls; the T02 helper
integration added the SelectionFilter argument. ALL matches the sibling
click test's semantics.

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

---------

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

* chore: add an upstream pull request gate script

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

* bug: surface moved upstream files as deletions in the PR gate

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

* feat: update cut records atomically with immutable retry evidence (A03) (#264)

* feat: remove cuts without reusing record or semantic cap identities (A03) (#265)

* feat: update hole records with canonical immutable replay (A04) (#266)

* feat: remove hole records with persistent non-reusable identities (A04) (#267)

* fix: replay cut creation from immutable accepted evidence (#268)

* docs: accept MVP amendments and define the non-duplicating M5 CLI delivery (#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

* feat: author circular member paths with true arc length and bounded meshes (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]>

* feat: atomically replace and clear plate corner treatments (A12.3 prerequisite) (#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 (#288)

* 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: speed up the CLI test suite without dropping a case (#287)

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

* test: avoid redundant CLI fixture preparation (#286)

* feat: compensate a published operation as a forward draft operation (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]>

* feat: rebase an unsent draft through the authenticated coordinator (C03) (#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]>

* refactor: build the trusted rebase and compensation actor without a dead 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]>

* chore: install cargo-nextest in the fork-sync workflow (#298)

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

* chore: install software Vulkan for the fork-sync UI tests (#299)

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

* CI: block PRs whose commits or description carry the Claude Code Co-Authored-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]>

* docs: reflect the K4 delivery state and add the post-MVP C07/C08 cards (#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/BeamCraft#738, Apibim-com/BeamCraft#739.

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

* docs: mark the C04 acceptance rows executable and fix the C04-09 cap (#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]>

* test: add the expanded Q01 alpha story and its coverage matrix (#321)

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

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

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

* feat: schedule the verified head through attribute templates (I05) (#323)

* docs: design I05 schedules on the shared BOM and filter layers

* feat: schedule the verified head through attribute templates (I05)

* fix: quote schedule CSV headers and bound the template read

---------

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

* feat: edit plate contours vertex by vertex (A12.3) (#325)

* feat: edit plate contours vertex by vertex (A12.3)

The A12.3 prerequisite (atomic --corners replacement) could only retype the
whole sparse list; editing a contour after creation meant rewriting it from
scratch. This delivers the amendment's five editing commands on top of that
replacement: move-plate-vertex, move-plate-edge, insert-plate-vertex,
delete-plate-vertex and set-plate-corner.

The engine part is the pure contour_edit module: each edit maps the authored
contour and its sparse corner list to the edited pair, remapping treatment
positions after an insertion or deletion and carrying the deleted vertex's
treatment away. UpdateElement grows a complete contour_points_mm replacement
(fingerprinted append-only, so earlier patch fingerprints are unchanged); the
CLI commits each edit as one shape-changing contour + corners patch through
the existing update path, so the kernel revalidates the new contour, every
hole and cut replays, a linked definition group updates atomically and the
revision is one undo unit. delete-plate-vertex refuses on a three-vertex
contour whatever the caller asks; invalid geometry refuses before publication.
keep_degenerate (P-Degenerate) remains outstanding.

* fix: separate the contour-edits help segment from the next one

---------

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

* feat: build fastener geometry prototypes (A14.2) (#326)

* feat: build fastener geometry prototypes (A14.2)

* fix: count the shank-through-head overlap once and keep the profile codes

---------

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

* refactor: dispatch CLI commands from the registry and list access and parameters (slices 2-3 of #753) (#760)

* refactor: dispatch CLI commands from the registry (second slice of #753)

The binary chose an adapter with a chain of `c == "..."` comparisons in
main.rs plus a `handles()` function per adapter, a second hand-kept
inventory that could drift from the registry.

Each registry entry now names its adapter (`Route`), and main.rs matches
on `registry::route(word)`. A word that is not registered cannot reach an
adapter and takes the store-level path, which refuses it as before, so a
command that is dispatched but unregistered is no longer possible. The
per-adapter `handles()` functions are removed.

Behaviour, exit codes and output of existing commands are unchanged.

* test: wait for the dropped rebase request before the retry

The coordinator answers each connection on its own thread, so in
lost_answers_and_a_killed_server_replay_exactly_once the retry could
overtake the request whose client was dropped and do the merge itself,
answering workspace_rebased instead of workspace_rebase_replayed. It failed
once on the ubuntu CI runner.

Wait until the draft head moves, which happens when the dropped request has
merged, before sending the retry.

* refactor: list access and parameters of every CLI command (third slice of #753)

`beamcraft commands` now carries, for every command, whether it reads or
writes (Access) and its options: value shape, whether it is required and,
for alternatives, a one_of group. The five common request identity options
are listed on the commands that take them (Ids::Common) instead of being
repeated in 125 entries.

The data was taken from each adapter's allow-list and required checks, not
from the help prose; where they differ (for example update-element
--corners and --axis-point, create-grid, create-plate --material) the code
wins. A rule that links optional options (--width with --length) stays in
the family help.

Tests: the listing carries access and parameters; an unlisted option is
refused as unknown (so the probe is valid); every listed option is accepted
by its command, which fails if a listed name is wrong or removed.

---------

Co-authored-by: Yury Isachenkov <[email protected]>

* docs: generate the CLI command reference from the registry (slice 7 of #753, first part) (#761)

COMMANDS.md renders every command from the registry as one usage line
(required options bare, optional ones in brackets, one-of groups in
parentheses) with its family, tier and access. A test fails when the
committed file differs from the generated text, and BEAMCRAFT_BLESS=1
rewrites it, so the reference cannot drift from `beamcraft commands`.

The README is not shortened yet: it carries rules and examples that the
registry does not hold.

Also lists three options the adapters accept but the registry missed:
update-element --profile and --profile-digest (added by #757) and
create-project --environment (added by #755). The "listed option is
accepted" test cannot see an omission, so every undeclared sibling option
was probed once by hand; no other command accepts an unlisted option except
the shared drawing allow-list and a few options that are accepted and
ignored or refused later.

Co-authored-by: Yury Isachenkov <[email protected]>

* docs: interactive-assist features need no CLI command (#762)

An agent builds a model by giving coordinates and ids directly, so cursor
picking and snapping and the start screen (settings, environments,
project-name suggestion, recent projects) do not need a CLI command. Narrow
the headless-first rule in AGENTS.md section 3 to features an agent or
script needs, and record the decision. The CLI commands themselves are
removed in a separate change; the engine APIs stay for the window.

Co-authored-by: Yury Isachenkov <[email protected]>

* refactor: remove the interactive-assist CLI commands and the tier (slices 4-5 of #753) (#764)

An agent builds a model by giving coordinates and ids directly, so the CLI
no longer needs cursor picking, snapping or the start screen. Remove pick,
snap, show-settings, set-settings, list-environment, suggest-project-name,
list-recent and add-recent (owner decision of 2026-10-31, see AGENTS.md
section 3). The engine and host functions behind them stay. list-feature,
lookup-feature, element-edges and work-plane stay: preview-drawing takes a
feature id.

With no ui-assist command left, the tier is empty information: drop
`Tier`, the `tier` field of `commands` and `commands --tier`.

The window still runs pick and the settings commands as a child process, so
its start screen, theme persistence and click selection are broken until
#763 moves it to the engine; the six UI tests that need them are ignored with
that issue's number.

The CLI tests of the removed commands are deleted (construction_snaps.rs,
the pick and snap tests, the settings tests) or cut down to the commands
that remain. The agent skill and the design notes no longer present them.

Co-authored-by: Yury Isachenkov <[email protected]>

* docs: point the CLI README at the generated command reference (slice 7 of #753) (#765)

The README repeated a usage block for most commands. They had drifted
(create-plate and update-element lacked many options the commands take) and
the generated COMMANDS.md now states them from the registry, with a test
that keeps it current. Replace each usage block with a link to the command's
entry there; the rules, examples and acceptance notes of each section stay.
The one worked example (define-profile then create-member with concrete
values) stays too.

Co-authored-by: Yury Isachenkov <[email protected]>

* feat: construction objects as stored helper snap targets (T02) (#330)

Helper points, lines, circles and planes join the model as owned `construction`
elements under /World/Construction with identity placement and world-millimetre
geometry. They are snap and placement targets only: the BOM and the numbering
preview keep reading plate, member and assembly records alone, so a helper is
never scheduled or numbered, and deletion is the ordinary DeleteElement
lifecycle.

Snapping gains a construction source family with its own gate
(--construction-snaps) and six kinds (point, line ends and nearest point,
circle centre and nearest circumference point, plane foot); helper lines also
feed the generic midpoint kind. Continuous kinds follow the nearest switch, and
source and point-kind gates run before truncation, as for every other family.

The UI compiles with the new source setting and resolves body/reference/grid
snaps as before; a viewport slice that draws helpers will pass them into the
snap context like the CLI already does. Copy, rotate and arrays remain T03.

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

* feat: shared typed element filters with class rules (V14) (#329)

* feat: shared typed element filters with class rules (V14)

Port the reviewed filter evaluator of the lost pre-reset delivery onto the
name-keyed attribute schema: one bounded engine Dataset admits records and
rules before matching, so unknown fields, unknown attributes and incompatible
operators refuse before matching, and a missing value never satisfies a
comparison, including ne. Rules cover type, name, class (P15) and user
attributes (I04) with all/any groups. query-elements runs the same evaluator
over the verified head through the CLI, with sorted pagination and a head
manifest pin. Saved sets, view bindings and the UI editor stay with the views
layer (V12, W01), which is not re-delivered yet; the evaluator and rule JSON
are exactly what they will evaluate.

Refs #206.

* fix: stale choice options stop targeting and accept the none model pin

A value that outlived its undefine into a redefinition with other options
passed the record's type check but refused the whole admitted dataset, so
every query on such a store failed forever; records now include only values
the engine's admission accepts, and a stale option simply never matches.
`--expected-model none` now pins a store without a revision, as the help
already documented.

* fix: resolve the Record intra-doc link the docs gate compiles

cargo doc --workspace -D warnings refused the CLI module's unqualified
[`Record`] link; the comment now links the engine path and says "admits"
where it described the type check the record read actually performs.

---------

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

* feat: fold and roll plates through owned bend records (A10) (#333)

* feat: fold and roll plates through owned bend records (A10)

Slice A10 of the bent-plate task (refs axisrow#190, milestone M1). A plate
may carry bend operation records (A-2): folds about lines in the blank plane
(inner radius, signed angle) or one roll to a cylinder or cone section about
an axis. The authored contour of a bent plate is its developed mid-surface
outline, so the unfolded outline is the authored contour and the analytic
volume stays outline area x thickness: one bend's annular-sector section area
is |angle| x t x (r + t/2), exactly the thickness times the developed length
it consumes.

The body is one G13 ruled loft of the derived cross-section and its
translation along the fold-line or roll-axis direction, so the blank must be
a rectangle with one side pair parallel to the folds (folds: regions fit and
do not overlap) or the developed annular sector about the axis start (cone:
one shared apex ratio, the axial span agreeing with the slant range); the
wrap stays below a full turn. Bend arcs facet to at most the 0.05 mm chord
rule and the kept deviation is reported; folds refuse treatments, holes,
cuts and a thickness side. Quantities run under the
kernel-a10-bent-plate-volume label; numbering distinguishes bent plates by
their hashed local surface.

Storage: seven parallel steel:plate:bend* attributes (the Change::Bends
whole-list replacement), shared to instances like cuts. CLI: bend-plate
with --kind fold|roll; the inspection reports the records and the unfold.
The ruled cap faces now share one face id per cap (one entry per logical
face), which the view snapshot's face-table rules require.

Checks: cargo fmt, clippy -D warnings, cargo nextest --workspace,
cargo test --workspace --doc; kernel fixtures compare the sealed folds and
rolls with independent closed forms (the L and U folds, the annular-sector
prism, the cone band and the development alone).

* fix: address the A10 review findings

Five review findings on axisrow#333:

* a cylinder roll now anchors its loft origin on the authored axis line (the
  ring stays centred on that line, not on the element origin);
* a cone roll with equal radii is refused in the model, the layer validation
  and the change path: a true cone cannot have them, and the stored encoding
  keeps one radius for a cylinder, so an equal-radius cone would read back as
  a cylinder and refuse its own sector blank on every replay;
* the seven steel:plate:bend properties are listed in the rebase property
  groups, so a published Change::Bends delta rebases instead of refusing as
  unsupported and accepted bend writes stay inside conflict scoping;
* Change::Bends joins the plate-only kind guard, so bend attributes can no
  longer land on a beam or column;
* the bent path refuses a clockwise or degenerate blank like the flat paths
  do, so a negative analytic area can no longer publish.

Checks: cargo fmt, clippy -D warnings, cargo nextest run --workspace
(1958 tests), cargo test --workspace --doc.

* fix: anchor a cylinder roll on the axis line at the blank along-min

Review round 2: origin = a anchored the along position on an arbitrary point
of the axis line, so two records for one axis line published bodies shifted
along it and a far-away endpoint pushed the body off the blank span. The
loft origin is now the line point at the blank's along minimum, so records
for the same line publish the same body wherever their endpoints sit on it;
a regression test compares the two blobs.

Checks: cargo fmt, clippy -D warnings, cargo nextest run --workspace.

---------

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

* feat: add non-destructive view cut planes (V15) (#334)

* feat: add non-destructive view cut planes (V15)

A cut hides part of the model in one window's image without changing the
model, its revisions or any measurement: the engine record holds up to
eight user half-spaces plus an optional work-area box, expands to at
most sixteen effective planes, and answers a conservative element mask
that keeps every partially intersecting body. The window applies the
cut as a fragment discard through a second shader uniform, so bodies
are cut through in the image while meshes and uploads stay untouched;
the View tab's Cut plane tool edits the per-window state.

The headless clip-view command reports the effective planes and visible
ids over the synthetic scene and re-builds the snapshot after masking
to assert the token is unchanged, and its token equals the uncut
snapshot-view token of the same pin: a cut is not revision data.

* test: name the storage and revision fixture dirs with the process id

The compaction and recovery fixtures already carry the process id in
their temp directory names, but storage and revision still used
test-{nanos}-{counter} with a process-local counter: under nextest's
process-per-test model two test processes starting within the same
nanosecond build the same name and one crashes with AlreadyExists, as
the macOS CI shard did on this branch. Same fix as the compaction
fixture got.

---------

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

* Merge upstream master into the fork: #748 and #749 (#331)

* feat: deliver I04 user attributes, the expanded Q01 alpha story and C04 acceptance fixes (#748)

* feat: deliver I04 user attributes, the expanded Q01 alpha story and C04 acceptance fixes

Typed user attributes (I04): attribute definitions and values on any element
across model, engine, layer engine and CLI, stored in USD, unknown attributes
never dropped. Expanded Q01 alpha story: a real-binary end-to-end scenario with
its coverage matrix. C03-C06 acceptance rows marked executable with the
corrected C04-09 cap; the document arrives in its fork state, overlapping the
open C03/C06 pull request that carries its new coordinator and compensation
tests.

* bug: reject control characters in an attribute-set name

AttrDef::validate checked only the length of a set name, the one free-text
field admitted without the label rule, so a raw newline or other control byte
could ride into the USDA string through bim:attr:set. The check now refuses
bytes up to U+001F and DEL, like unit labels, option strings and text values,
and the design card and the adversarial suite say so.

---------

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

* feat: rebase the workspace draft through the coordinator (C03) (#749)

The coordinator wire gains a `rebase` operation and the CLI a
`coordinator-rebase` client, so a client no longer needs a separate local
`rebase-workspace` process between publications: the same guarded local
rebase runs server-side with the token-derived actor as the only trusted
actor, 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 with real token files
(`apps/beamcraft-cli/tests/coordinator_rebase.rs`).

The transport needed a host-side conversion from the validated model id of
the authenticated actor. It deliberately takes the validated
`beamcraft_model::id::Uuid7` instead of raw bytes: a constructor from
unchecked bytes would hand out a `Uuid7` that `Uuid7::parse` rejects and
break the invariant every persisted id relies on (review finding on the
combined C03/C06 delivery, Apibim-com/BeamCraft#737). A round-trip unit
test pins the conversion.

Compensation (C06) is not part of this change: per that review,
compensating a published version is an administrator-only action that
needs the post-MVP roles-and-permissions model, so it stays deferred.

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

---------

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

* feat: loft a plate between two contours that need not be coplanar (A11) (#327)

* feat: loft a plate between two contours that need not be coplanar (A11)

A lofted plate stores its two face contours on the plate register
(steel:plate:form=lofted plus the two contour arrays) and evaluates
through the G13 ruled primitive: one ruling per vertex pair, wall quads
along the contour edges, G05 cap triangulations, sealed by the G09
certification. The sheet thickness is the distance between the contours,
so the command carries no thickness, holes, cuts or corner treatments.

Each contour is admitted by the shared F04 contour admission (winding,
planarity band, exact simplicity), which refuses self-intersecting input;
a renumbered (twisted) end contour is refused by the seal instead of
repaired, and a near-planar admitted contour is refused rather than
repaired into an inexact frame. The element keeps the plate kind token
and part path, so the instance and category registers stay untouched.

Engine: CreateLoftedPlate with its own content fingerprint, the authored
record, quantities with mass from the pinned material's density, and a
shape signature over the two contours. Layer engine: the Shape::LoftedPlate
variant with schema descriptor, encode and structural validation. CLI:
create-lofted-plate and inspect-lofted-plates through the same atomic
store and strict verifier as evaluated plates.

Fixes #191

* feat: show lofted plates in the committed view snapshot

A committed lofted plate decoded and verified, but the pinned view
snapshot only carried evaluated plates and members, so the element
stayed invisible to the viewport. Evaluate the committed lofted plates
on inspection, in the manner of every other part, and add their scene
elements: the certified mesh stays element-local under the authored
rigid pose, in element-id order with the rest of the scene.

An engine fixture builds the snapshot from a loft body; the end-to-end
suite asserts the element, its exact world bounds and the mesh triangle
count through the real binary.

---------

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

* feat: place drawing views on a document (D04 slice 1) (#335)

* feat: place drawing views on a document (D04 slice 1)

The D01-D03 contract admits exactly one view per document, so a drawing cannot
carry the front, top and right projections of its member side by side, and no
command can add one without rebuilding the whole intent from options.

Place views as document objects instead: `DrawingSpec.views` admits up to
MAX_VIEWS additional ViewSpecs with the same paper bounds as the primary view,
nothing is placed automatically, and `projection::resolve_view` resolves each
placement from the document's exact pinned snapshot, refusing a foreign view or
a stale pin before geometry. View UUIDs are distinct from every other document
object in both replacement directions, and the closed one-page intent now
budgets its page across placed views and drawn objects together
(MAX_INTENT_PAGE_ITEMS), because the reader page, not either collection cap,
is the real ceiling.

The wire format gains one optional `Scope "Views"` with canonical ordinal
children, the schema descriptor is bumped, and the CLI mirrors `edit-drafting`
with `edit-views`: no model store on the command line, idempotent replay by
request fingerprint, `placed_views` in the inspect output.

Part of #228 (view placement core); sections/details and titles/scales are the
next slices, sheet rendering stays with D05.

* docs: record placed views in the drawing help and contract (D04 review)

The edit-views command and its --placed-view syntax were absent from the
DOCUMENTS help line and from the drawing contract that its header binds to the
code, so the changed surface and its two mirrors disagreed about what the CLI
admits. The help now lists edit-views beside edit-drafting, including that
create/refresh accept --placed-view without using it and that a refresh drops
existing placements; the contract gains section 6b (placed views, their
identity rules and the shared one-page cap), admission bullets, and the
edit-views entry in the module map.

Review finding on #335.

---------

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

* feat: accept member targets on AddHole/UpdateHole/RemoveHole (A04.2) (#332)

A member hole is an owned MemberHoleRecord in the member frame (F06
member-hole amendment): axis point, a section direction, a through or
ranged extent and the authoritative round polygon. Records replay in
ordinal order after the plane cuts, through the kernel's certified
through-hole operation mapped by an exact signed axis permutation, so
an axis-aligned straight rectangle-profile member cuts, quantities and
undoes end to end today. Section profiles, polybeams, curved paths,
non-axis-aligned frames and ranged cutters refuse with
UNSUPPORTED_GEOMETRY naming the kernel slice G14.1 that lifts them; a
hole that no longer fits refuses with a diagnostic and changes nothing.

Storage adds the steel:mhole:* attribute family with its own monotone
counter and retired identities, so plate records (A04) and member
records never conflate under update-hole/remove-hole while record ids
stay one project-wide space. The CLI add-hole/update-hole take the
member flags, and the response names the plate or member as the target
decides. Refs axisrow/BeamCraft#315.

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

* feat: add copy, move, rotate, mirror and array transforms (T03) (#328)

* feat: add copy, move, rotate, mirror and array transforms (T03)

T03 wanted the five transform families by vector or two points, with one
undo step per operation and links and quantities remapped. A move stays
the placement patch (P08), so the two-point forms are relative fields on
the patch (`move_mm`, `rotate`, `mirror`) that the engine composes with
the current placement; their replay is decided from history (the head
must still be the revision the operation committed), because a relative
transform cannot recompute its would-be result from a head it already
moved.

A mirror is not rigid, so it is re-encoded through a symmetry of the
element's local body (`R' = A·R·L`): a plate flips its thickness side and
reflects its cuts, a straight member reflects its path and cuts and
trades the section offsets; polybeams refuse. The profile definition is
not mirrored (chiral sections keep their handedness) - documented as the
task's honesty limit. Arrays create independent P05-style duplicates in
one batch revision, so one undo step takes every copy back; the level
link follows every copy.

Gate: fmt, clippy -D warnings, nextest --workspace (1929), doctests.

* fix: review fixes for the T03 transform encoding

Three findings from the AO review of #328:

A mirror never shares a patch with a member path replacement: the mirror
re-encodes the body through the path…
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@Blogbotana Blogbotana Blogbotana approved these changes

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

PR #187: Fix picking of I-section members and plates with holes (#63)

Uh oh!

There was an error while loading. Please reload this page.

2 participants

@axisrow @Blogbotana
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.