Skip to content

feat: deliver the fork delta since the 1106 sync wave - #834

Merged
Blogbotana merged 518 commits into
Apibim-com:masterfrom
axisrow:chore/upstream-delta-1106
Nov 6, 2026
Merged

Blogbotana merged 518 commits into
Apibim-com:masterfrom
axisrow:chore/upstream-delta-1106

Conversation

@axisrow

@axisrow axisrow commented Nov 6, 2026

Copy link
Copy Markdown
Collaborator

What this changes

Delivers the fork delta accumulated since the 1105/1106 sync waves as one batch commit on top
of master (the single-batch delivery the fork sync policy prescribes). Nothing here changes
the recorded contracts; it is feature, fix, test and harness work that master predates.

  • V14 filter evaluator: one bounded evaluator for object queries, saved filters and view
    visibility (crates/engine/src/filter.rs). A rule is admitted against the schema before any
    element is evaluated — an unknown field, attribute or operator refuses before matching, and
    a missing value never satisfies a comparison, not even ne.
  • Drawing documents: the sheet editor lays, changes and removes document sheets from the
    window; per-version publish and export land in the window with the export binding tracked
    across republication and store removal; the two-point section view flow is pinned in the
    window; the drawing canvas work sits in apps/beamcraft-ui/src/drawing.rs.
  • V10: the accepted revision exports as IFC from the menu, and the export panel suggests the
    output path next to the model without clobbering a typed path.
  • Bolt groups: a placed group is editable from the bench (V19 slice 2), rotated part
    placements are bounded instead of refused, committed bolt groups draw in the exported view
    bundle, and the member-hole fit and refusal paths are pinned at the engine replay.
  • The side-panel camera preset persists across restarts, and the macOS viewport starts on the
    trackpad camera preset.
  • The DIN fastener sample library gains the 160 mm M20 length, and the full fastener stack is
    pinned in the viewport.
  • Q01 stories: renumbering staleness, storey assignment and instance bodies run in their own
    published stores so the numbered frame keeps its counts; the alpha scenario and matrix
    documents describe the split. V14 missing-value comparison and type-refusal admission are
    pinned, and the viewport GPU adapter is required structurally instead of by unreachable arms.

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 and workflows 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: 2643/2643 runnable tests executed nextest 0.9.149 on
    macOS aarch64, plus mandatory doctests and cargo doc -D warnings, all exit 0; the
    committed tree equals the tested tree, receipt in the runner report)

New behaviour carries tests in the same commit: the export, sheet, bolt-group, filter and
story suites exercise the new paths.

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
  • Every new asset has an entry in ATTRIBUTION.md (the DIN fastener library change extends
    the existing contributor-original synthetic library entry)

Anything a reviewer should look at first

Two UI tests in this delivery fail on Windows with path-separator assumptions. They were
written on macOS, and Windows runners only began actually executing the workspace tests this
week after the fork unblocked its Windows pool, so the failures surfaced there first:

  • app::tests::the_export_ifc_menu_row_writes_the_same_file_as_the_cli_by_hand panics with
    BACKEND_UNAVAILABLE ("store root is not usable") at apps/beamcraft-ui/src/app.rs:8176.
  • app::export_panel::tests::the_suggested_output_sits_next_to_the_model_and_never_clobbers_a_typed_path
    asserts "/m\\Nave 1.ifc" against "/m/Nave 1.ifc" at
    apps/beamcraft-ui/src/app/export_panel.rs:287.

Both are the same class — native path separators in the new export paths. The Linux and macOS
lanes are green. A follow-up fix can ride the next sync wave, or land directly here if
preferred.

axisrow and others added 30 commits October 27, 2026 16:15
#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)

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)

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

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

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]>
…12) (#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]>
…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]>
…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)

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: use the shared fixture in D07 drafting tests

* fix: simplify the G13 finite sweep admission guard
#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]>
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]>
… 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]>
…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]>
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.
axisrow and others added 24 commits November 5, 2026 14:24
The expanded story approved the numbering once, so the staleness discipline —
the preview going stale exactly when an edit changes the numbering, and the
re-approval cycle — had no expanded-story coverage. The renumbering story now
runs as the file's thirteenth test in its own published store, keeping the
numbered frame's recorded counts and the per-test budget intact. It proves a
preview never writes the store and a fresh preview is not stale, that a rename
leaves the numbering fresh while a thickness edit that splits the group goes
stale with a new token, that the fresh token approves exactly the new marks and
that a copy stales the preview again for the second approval, and that the
published head is exactly the approved numbering. The matrix gains the executed
A08 row and the Number-it trace names it.

Co-authored-by: axisrow <[email protected]>
V19 slice 2: bolt-group editing from the bench.
…470)

Policy side of the CI acceleration pair (#239): complete-workspace tests move to the platform track.
…ough the 1104/1105 delta waves

Connect the fork's master to the upstream history it has tracked only by
content since the steel wave: every upstream commit from Apibim-com#758 through the
Apibim-com#830/Apibim-com#832 delta merges lands here with its own SHA and parents. The merged
tree equals the fork master tree byte for byte — the fork already carries
every upstream change by content (Apibim-com#758-Apibim-com#827 through the 11-03 absorb, the
delta waves through the deliveries themselves) — so this merge changes no
fork behaviour; it restores the shared ancestry. The fork-only form stays:
the recorded-gate ci.yml, the no-Python rule (the upstream ci tooling does
not ride along; card #385 decides its fate), the fork policy documents and
the fork's newer fixes that the upstream copies predate. The accumulated
fork delta leaves as the next single batch pull request toward upstream.

BeamCraft-Test-Gate: {"version":1,"commit":"e692e6a5749f02be7e21f0d64911e91a098494fa","tree":"80f09fd5052e4b62b864f6de3212f27e9bf52f2e","base":"e692e6a5749f02be7e21f0d64911e91a098494fa","merge_parent":"66ee62e2446f93eab7d5bf7d011dc7e39818ff44","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":2608,"executed_tests":2608,"ignored_tests":["beamcraft-test-gate::process::process_fixture"],"source_unchanged":true,"stages":[{"name":"format","exit_code":0,"elapsed_ms":4144,"timed_out":false},{"name":"clippy","exit_code":0,"elapsed_ms":18125,"timed_out":false},{"name":"prepare","exit_code":0,"elapsed_ms":52512,"timed_out":false},{"name":"tests","exit_code":0,"elapsed_ms":229789,"timed_out":false},{"name":"doctests","exit_code":0,"elapsed_ms":1802,"timed_out":false},{"name":"docs","exit_code":0,"elapsed_ms":7032,"timed_out":false}]}
The expanded story copied parts as independent copies but never exercised
copy-instance, so the delivered instancing feature had no expanded-story
coverage. The instance story now runs as the file's fourteenth test in its own
published store, keeping the numbered frame's recorded counts and the per-test
budget intact. It proves an instance shares its definition's body — a hole and
a plane cut applied to the definition ride the whole group with identical local
meshes, cuts, hole records and quantities (the exact kept half-space minus the
hole), while every member keeps its own name and placement, and publication
preserves the shared body state and both identities. The matrix gains the
executed U11 row and the model-frame trace names it.

Co-authored-by: axisrow <[email protected]>
…le arms (#502)

The gpu() helper has panicked on a missing adapter since the recorded-gate
delivery, so the `let Some(gpu) = gpu() else { return }` arms at its call
sites were dead code that made the suite look like it skips without a GPU -
the exact shape the Apibim-com#768 review follow-up quotes. Return Gpu directly and
drop the arms; the fail-closed behaviour is unchanged and every pixel
assertion still runs behind a real adapter.

Co-authored-by: axisrow <[email protected]>
The test-coverage follow-up mirrored in #500 (item 5) names the thin V14
unit coverage: the three existing filter tests pinned `ne` on one missing
attribute and one wrong literal type. Pin the rest of the comparison
contract: a missing name, class or attribute value satisfies no operator
on any field class, an absent target never matches, incompatible literal
types and field operators refuse rule admission, and Dataset::new refuses
a target value that disagrees with its definition.

Test-only: no product code, no contract change, no weakened check.

Co-authored-by: axisrow <[email protected]>
Issue Apibim-com#239 asks for a CI wall median of three minutes; the
measurement shows the windows test chain (plan -> nextest archive ->
slowest shard -> verdict) pins every run at 550-680 s, so the matrix
cannot stay inside the ci.yml wall at any tuning.

Carry the recorded-gate policy change over: ci.yml keeps plan, the two
quality lanes, WASM, licences, the cold release gate and the verdict;
ci-platform.yml runs the complete workspace on all three operating
systems on every integration push, nightly and manual dispatch with the
same archives, partitions, budgets and evidence. The verdict accepts the
platform hand-off and no longer requires the test-archive/test jobs.

The platform track also restores macOS partition 6/6, which the
five-shard tuning of the old wall silently dropped: the canonical
inventory is six partitions, so the sixth bucket of macOS tests never
executed in CI. The track has no wall-time pressure, so the queued
sixth shard costs minutes nightly, not coverage.

Local receipt discipline is unchanged: ci-plan still fails closed
without a current report on every pull request, push and dispatch.

Co-authored-by: axisrow <[email protected]>
* test: pin member-hole fit and refusal paths at the engine replay

The engine-level member-hole suite covered single-record shapes only, so
the replay's multi-record and refusal contracts stayed unpinned: nothing
proved that two records cut together in ordinal order with an exact
closed-form net, that a record meeting another hole refuses the
evaluation whole, that mixed record directions have no servable replay,
or that the member-side admission refusals (wrong target, counter
regression, the 65th record, an unknown update id, curved and polybeam
members) carry their typed errors. Independent closed-form expectations,
no production change and no weakened check.

Part of the member-hole slice of the Apibim-com#769 item 5 test-coverage gap
(mirror #500).

* chore: retrigger the platform-track checks after the cancelled first run

---------

Co-authored-by: axisrow <[email protected]>
…g them (#508)

* fix: bound rotated part placements for bolt groups instead of refusing them

create-bolt-group refused every group whose parts carry a rotated placement
(UNSUPPORTED_GEOMETRY), so an angled brace could never be bolted to a gusset
(#505). The admission now turns the conservative bounds with the placement
quaternion: a plate contributes its extruded contour box turned by the
placement, a member its axis points rotated before the section reach is
applied, so every bolt path through the shared world bounds accepts rotated
parts. Generated holes stay refused on a rotated plate, whose plate-local
frame the world box alone does not give; the hole decision reads full
placements now instead of bare translations.

* fix: read the plate identity through Placement::is_identity_rotation

The generated-hole refusal of a rotated plate re-inlined the xyz == 0
predicate; the documented model method exists for exactly this question and
keeps the identity rule defined in one place.

---------

Co-authored-by: axisrow <[email protected]>
* bug: start the macOS viewport on the trackpad camera preset

The camera preset default was platform-blind: every machine started on the
mouse mapping, so a Mac without a wheel button had to switch to the trackpad
preset by hand in the side panel after every launch. The default now follows
the platform (cfg(target_os = "macos") starts on Trackpad; Windows and Linux
keep Mouse), and an explicit side-panel choice still wins within a session.

* docs: reword the preset docs to the platform-aware start

The module and field docs still named `Mouse` as the default, which the
platform-aware start of this same PR made false on macOS; both now describe
the start preset the way `Preset::default` does (review finding on #507).

---------

Co-authored-by: axisrow <[email protected]>
The platform-aware start preset of #507 came back to the platform value on
every launch: an explicit side-panel choice lived only in the window state.
The chosen preset is now saved into the user's settings.json (a new optional
`camera` field on the session settings, read and written under the settings
write lock so it never loses a concurrent appearance edit) and read back when
a window opens. Nothing saved - a missing field, a null, or an unreadable
file - still starts on the platform default, so a fresh profile keeps the
Trackpad start on macOS and Mouse elsewhere.

A failed save still applies the choice to the open window and reports the
failure where the choice was made, matching how the saved-views write
reports into its panel.

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

The D03 suites covered version-ID reuse, single-version export determinism
and stale-source freshness, but never exported a historical version after a
newer one was published: every export test used exactly one version, and the
lifecycle suite never exported at all. The core #333 acceptance leg —
document-version-bound output that never silently rebinds — had no CLI-level
proof across two versions pinned to two different archived source revisions.

Pin it: publish V1 from a 3000 mm member, rebuild the member 4000 mm, expect
freshness `stale`, refresh onto the new revision, publish V2, and re-export
both. V1 must reopen from its own archived source with identical bytes and
its own source_revision_id; V2 must carry the new geometry and pin; the
journal must list each version with its own pin. Removing the live model
store entirely must report freshness `unavailable` and still export both
versions byte-identically from the archived sources alone.

Co-authored-by: axisrow <[email protected]>
ISO 4014 carries 160 mm for M20, but the sample list stopped at 150. The
engine's conservative-box grip for a knee-brace joint (a 12 mm gusset and a
100 mm member) needs ~154 mm, so auto selection refused every real bolt for
that joint; the captured repro lives in #505.

Co-authored-by: axisrow <[email protected]>
The icon acceptance job has not acquired a hosted macOS runner since
2026-11-05 14:48 UTC: every attempt after that (four PRs, including master's
platform track) waits out the queue budget and is cancelled with 'The job was
not acquired by Runner of type hosted even after multiple attempts', while the
status page keeps reporting Actions as operational. macos-latest now resolves
to the macOS 26 arm64 pool; this pins the same job to the separate macOS 15
arm64 pool and adds a dispatch trigger so the pin can be probed without
waiting for capacity on the saturated pool. No check is skipped: the job runs
the same build, packaging and screenshots on a hosted macOS image.

Co-authored-by: axisrow <[email protected]>
* fix: draw committed bolt groups in the exported view bundle

A committed A14.3 bolt group existed only in the store: the view snapshot
built its scene from plates, members, lofted plates and references alone,
so export-view bundled no fastener meshes and the native viewport showed
the joined parts with invisible bolts (issue #512).

Each committed group now joins the snapshot as one Connection-kind scene
element: the A14.2 prototype of its pinned fastener definition, placed at
every bearing point with the group's own frame and merged into one
world-space mesh at the identity pose. Every dimension is the pinned
definition's or the placement decision's, and the group's record is only
read - the view never rewrites the model. The frame-and-bearings
arithmetic of place_bolt_group is now shared with the read side
(BoltGroupPlaced::from_stored), so the snapshot re-derives exactly the
placement the creation decided, and a group the kernel cannot place or
build refuses the snapshot like any committed body that no longer
evaluates: never a guess and never a silent gap.

* docs: bolt-group placement notes follow the view drawing

The A14.3 and A14.4 design notes still said the prototype placements are
reported but not drawn; issue 512 made both claims false - the view
snapshot now merges the placed A14.2 prototypes into one read-only scene
mesh per committed group. The layer itself still stores no derived mesh
and no instancing (U11), and the generated-hole records still cut the
connected plates' own bodies; the notes now say exactly that.

* fix: drop an unused test import from the bolt scene suite

The import survived an assertion it no longer serves; the recorded gate
denies warnings, so the clippy step refused the head that all 2629 tests
otherwise passed.

* chore: retrigger the CI cycle after cancelled runs

The cancelled in-progress runs left tombstone stubs on the checks board
(a 0s quality shard and a pending macOS job from a superseded event);
a clean empty commit re-plans the whole cycle from the published receipt.

---------

Co-authored-by: axisrow <[email protected]>
* test: pin the two-point section view flow in the window (V09)

* test: make the two-point view restart leg rebuild the camera for real

---------

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

* feat: publish and export drawing document versions from the window

The native UI could move a loaded drawing document's placed views, but the
rest of the D03 document lifecycle stayed CLI-only: nothing in the window
published a version, listed the published ones or exported one, so a user
could not get a drawing out of the model without leaving the app. #333 names
this gap directly: the native UI must drive the headless commands.

Add a drawing document window beside the sheet editor, on the same pattern:
every action spawns the exact command a user types, and the window computes
no engineering geometry. Load reads `inspect-drawing` with the workspace as
the model store, so freshness is `current`, `stale` or `unavailable` instead
of an invented value. Publish commits the loaded head as a new immutable
version through `publish-drawing` with fresh operation, revision, actor and
version identities, then reloads so the next publish names the new base.
The version list comes from `inspect-drawing-versions`; export writes one
exact version's SVG or print-ready PDF through `export-drawing`, which
refuses an existing destination on its own. Creating and refreshing a
document stay CLI commands, and the window says so instead of hiding it.

Tests pin the reply parsing (whole mirror or nothing, versions list, absent
versions, missing model store reading as `unknown` freshness) and the exact
argument lists of all four commands. The full flow was verified against the
real CLI: draft load with `current` freshness, publication, version list,
SVG and PDF export, and a stale-head publish refusing with
BASE_REVISION_MISMATCH.

Refs Apibim-com#333

* fix: earn the drawing window's freshness in default store mode

The window named the personal workspace as the freshness model store, but
that field is only set when the app was opened with --workspace: in the
default --store mode the inspect reply carried freshness `unknown` forever,
although the live model on screen is known and comparable. Pass the source
store instead — main.rs sets it to the workspace directory in workspace
mode, so it names the live model on screen in both modes.

Spotted in review on #513.

---------

Co-authored-by: axisrow <[email protected]>
* feat: export the accepted revision as IFC from the menu (V10)

The menu's Export section shows its IFC row as a disabled placeholder; the
row now opens a form that commits through the same export-ifc command a user
types. The output path is suggested next to the model and stays editable, the
overwrite switch travels as --overwrite, and the reply is the CLI's: a refusal
is shown on the page and writes nothing, a success reports the command's own
counts, status and file digest. A V10 slice for the ribbon/menu acceptance:
every button runs the engine command. Unit tests pin the exact argv, the local
refusals and the report summary; an end-to-end test clicks the menu rows and
the form, runs the real CLI process and requires the written file to equal the
one the same command writes by hand, the refusal to reach the page without
touching the file, and the store to stay unchanged.

* fix: keep the IFC menu row on the Export page only

The enabled match keyed on the label alone, so the Import page's IFC row
turned clickable too: its click pre-opened the export form under a page that
never draws it and cleared the form's note for the next Export visit. The row
now enables on the Export section, and a test pins both pages: the Import
click neither opens the form nor leaves the page, the Export click does.

---------

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

* feat: lay, change and remove a drawing document sheet from the sheet editor

The sheet editor dead-ended on a sheetless document: it told the user to lay
a sheet on with the CLI's edit-sheet command, so the D05 sheet layout could
not start from the app. The window now carries the edit-sheet form instead —
a size, the two title-block fields and the frame inset — applied or removed
through the same command a user types, with fresh operation, revision and
actor identities and a reload that re-earns the head and mirrors the form
from the sheet the document carries afterwards.

The form introduces no defaults of its own: the initial size choice is the
command's own print default (a4), an empty inset is the command's own 10 mm
default, and empty title-block fields keep the button disabled instead of
guessing values for D05's still-open preset decisions.

Tests pin the exact argument lists of both edit-sheet forms, the frame field
reading empty as the default and refusing non-finite junk, and the whole
flow was verified against the built CLI: laying an a4 sheet with default
frame on the scratch document, and removing it again, both visible in the
inspect reply.

Refs Apibim-com#333

* fix: start the sheet form on the command's own print default size

The form's size choice came from a plain Default string, so it started
empty: the combo box showed a blank selection and, with the title-block
fields typed, Apply sheet ran edit-sheet without a size and was refused
with "sheet must be a4, a3, a2, a1 or a0" until the user opened the
dropdown. Seed the choice on a4 — the command's own print default — and
pin it with a test; the size can no longer be empty, because the only
writers are the size menu and a loaded document's own sheet.

Spotted in review on #518.

---------

Co-authored-by: axisrow <[email protected]>
Connect the fork's master to the upstream history delivered since the 1105
wave: upstream commit f00260e ("Deliver the fork delta since the 1105 sync
wave (Apibim-com#833)") carries the fork's own E04 IFC input study, contour grips,
weld panel, bolt panel, A10 bent plates, expanded alpha scenario and the
usd-sdk-differential fixtures back into the shared ancestry with their
upstream SHAs and parents. The merged tree equals the fork master tree byte
for byte - every upstream change is already present in the fork by content,
so this merge changes no fork behaviour; it restores the shared history.
The fork-only form stays: the recorded-gate ci.yml, the no-Python rule, the
fork policy documents and the fork's newer fixes that the upstream copies
predate. The fork delta accumulated since the Apibim-com#833 cut (V09/V10/V13 drawing
and sheet work) leaves as the next single batch pull request toward
upstream.

BeamCraft-Test-Gate: {"version":1,"commit":"f3b4ff252295c9d824e13633987f54aeae659d8f","tree":"087ce0ace2921f323399f2129ea841eae1b0503c","base":"f3b4ff252295c9d824e13633987f54aeae659d8f","merge_parent":"f00260ef8bf0b006b826e3b5c4ca6f247c40a853","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":2642,"executed_tests":2642,"ignored_tests":["beamcraft-test-gate::process::process_fixture"],"source_unchanged":true,"stages":[{"name":"format","exit_code":0,"elapsed_ms":1587,"timed_out":false},{"name":"clippy","exit_code":0,"elapsed_ms":379,"timed_out":false},{"name":"prepare","exit_code":0,"elapsed_ms":501,"timed_out":false},{"name":"tests","exit_code":0,"elapsed_ms":191471,"timed_out":false},{"name":"doctests","exit_code":0,"elapsed_ms":3016,"timed_out":false},{"name":"docs","exit_code":0,"elapsed_ms":386,"timed_out":false}]}
…520)

Issue 519's defect A captured the knee-brace demo drawing its committed
M20 group as bare shanks in the native viewport. On the current master
the merged-mesh path the window consumes (export-view, #516) already
builds the full stack - washer under head, hex head, shank, nut, washer
under nut - at the pinned definition's own dimensions, so the defect no
longer reproduces; what the acceptance still lacked is the viewport-side
proof on the demo joint with the shipped DIN library, pinned where the
owner saw the picture.

The new GPU test commits a DIN M20 group on the knee-brace demo joint,
reads the framed export the window reads and pins the group element's
exact bounds by hand from the library definition: the stack crosses the
plate faces, the head tip reaches 3.5 mm past the far face and the nut
stack tops out 102 mm past it - geometry a shank-only mesh cannot
produce - and the picture renders lit on the background. The length is
fixed at a standard 90 mm so the pin reads the stack, not the
box-conservative grip of defect B (tracked in 505, owner decision).

Co-authored-by: axisrow <[email protected]>
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]>
Drawing documents: publish and export per-version drawing documents from the
window with export binding tracked across republication and store removal, and
the sheet editor lays, changes and removes document sheets. The two-point
section view flow is pinned in the window, and the accepted revision exports
as IFC from the menu (V10). Committed bolt groups now draw in the exported
view bundle.

Bolt groups: a placed group is editable from the bench (V19 slice 2), rotated
part placements are bounded instead of refused, and the member-hole fit and
refusal paths are pinned at the engine replay. The side-panel camera preset
persists across restarts and the macOS viewport starts on it.

The DIN fastener sample library gains the 160 mm M20 length, and the full
fastener stack is pinned in the viewport. The Q01 stories for renumbering
staleness, storey assignment and instance bodies run in their own published
stores, V14 missing-value comparison and type-refusal admission are pinned,
and the viewport GPU adapter is required structurally instead of by
unreachable arms.
@axisrow
axisrow requested a review from Blogbotana as a code owner November 6, 2026 05:14
@Blogbotana
Blogbotana merged commit 25c4fd1 into Apibim-com:master Nov 6, 2026
27 checks passed
axisrow added a commit to axisrow/BeamCraft that referenced this pull request Nov 7, 2026
…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}]}
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