Commit Graph

267 Commits

Author SHA1 Message Date
bherbruck 03b8f4138b New designs save as .sc; .solidcraft files still open (#7325)
* claim: new designs save as .sc

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* New designs save as .sc; .solidcraft files still open and keep their name on Save

DESIGN_EXT is now "sc"; DESIGN_EXTS lists sc, solidcraft and solvecraft and
every open path (dialogs, drag-drop, recent files, CLI, MCP, links, the web
picker) accepts all three. Save of an opened .solidcraft file writes that
file; Save As, autosave, recovery and crash reports use .sc. Windows, macOS
and Linux register .sc alongside the older extensions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Update UI tests for the .sc default extension

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 22:41:40 +00:00
bherbruck 7a6c884418 Change the app id to io.tangencylabs.solidcraft, with aliases for existing installs (#7681)
The app id moves from ai.storyteller.solidcraft to io.tangencylabs.solidcraft: macOS bundle id
(the CLI signs as io.tangencylabs.solidcraft.cli), Linux desktop/metainfo/MIME file names, icon
name, Wayland app_id / X11 class, Flatpak, Snap common-id, nfpm/FreeBSD/Nix/AppImage staging,
the web app's icon paths and the docs.

Existing installs keep working: the Flatpak repository marks both old ids
(ai.storyteller.solidcraft and ai.storyteller.solvecraft) end-of-life with a rebase straight to
the new id, re-marking the solvecraft ref that pointed at the 0.8 id; the manifest grants
read-only access to both old ~/.var/app folders and the first launch copies settings and
autosaves from the newest one; AppStream <replaces>/<provides> and X-Flatpak-RenamedFrom list
both; the AppImage removes menu entries it wrote under either old id. The data and settings
folders (solidcraft / SolidCraft) are unchanged. The MSI UpgradeCode and ProgIDs are unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 17:16:40 -04:00
raykholo d2f0627421 Follow a theme file and reload it when it changes (#7573) (#7574)
* claim: #7573

* Follow a theme file and reload it when it changes (#7573)

* Translate the follow-a-theme-file strings
2026-10-27 20:08:12 +00:00
semmelrinde 0d18757ed7 File dialogs open in the folder used last (#7756) (#7807)
* claim: #7756

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* File dialogs open in the folder used last (#7756)

The desktop dialogs never said where to open, and the Linux ones then
start in the home folder every time. Open, Insert, attach, Save As and
Export now open in the folder of the file last chosen in a dialog, or of
the open design when another design becomes the open one, whichever came
last. Also fixes #7735.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 19:19:10 +00:00
Mateusz Sych 9a7b657fdc Assistant: connect the last-used agent on startup (#7009) (#7124)
* claim: #7009

* Assistant: connect the last-used agent and open the panel at start (#7009)

Two Settings > AI options, off by default: when SolidCraft starts, connect
the agent started last (once the installed agents are known; a missing one
is not swapped for another) and open the Assistant panel. Not for runs
started with --control.

A connection that never gets ready (no agent, start error, the agent ends
while starting) is a new Failed state: the header reads Connection failed
and Connect reads Retry. The control channel reports it as "failed" and
takes connect_at_start / open_at_start.

* Assistant: keep the start-up agent when a design replaces the blank one (#7009)

Opening a design over the blank design SolidCraft starts with reset its
chat and stopped the agent connected at start. A chat not asked anything
yet now starts over in place and keeps its agent. Turning AI features
off also drops a start-up connection still waiting for discovery.

* i18n: Arabic, Hebrew and Vietnamese for the start-up options (#7009)

ar, he and vi are complete catalogs on main now, so every new English
message needs them.

---------

Co-authored-by: MateuszSychXpl <151755914+MateuszSychXpl@users.noreply.github.com>
2026-10-27 16:38:26 +00:00
semmelrinde 2346c1ae81 Web app: Report a bug saves its screenshot as a download (#7192) (#7197)
* claim: #7192

* Web app: Report a bug saves its screenshot as a download (#7192)

Save screenshot… wrote the PNG with image's save, that is std::fs, which
the browser doesn't have. The screenshot is now encoded in memory and
written through vfs, as every other save is, so the web app hands it out
as a download. Natively the same file is written and ui.screenshot
replies as before. A failed save says why in plain text instead of a
Rust debug value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Web app: a screenshot without a path no longer panics (#7192)

`ui.screenshot` without a `path` saved to `std::env::temp_dir()`, which panics on
wasm32-unknown-unknown (std has no file system there), and the web app's control
requests come through `window.solidcraft.request`. `vfs::temp_dir()` now returns
`vfs::BROWSER_TEMP`, a folder of the in-memory files, in the browser, and
`save_screenshot` and `vfs::design_folder_refusal` (reached when a request names
`allow_paths` while a design is open) use it. Natively nothing changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Web app: keymap and theme exports download (#7192)

Both exports wrote with `std::fs::write`, which always fails in the browser. They
now write through `vfs::write` like the other saves, so the web app hands the file
out as a download. Natively the file is the same; an agent's folder scope now
applies to them as to every other write.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Tests: say what the folder-scope checks of the saves show (#7192)

The screenshot test wraps the write in a folder scope by hand: that proves the write goes through vfs, not that ui.screenshot's write runs inside the request's scope. ui.screenshot checks the agent's folders when the request arrives; the PNG is written in a later frame, outside that scope, as on main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 12:41:21 +00:00
Liu XingJian 4ad285570c Control channel: retire a subscription when the events thread ends (#7226) (#7336)
The seat of a subscribed connection on the DesignWatch lived in the
connection's serve thread. It left only when that thread saw the end of
the stream, so one unbounded blocking read stood between a closed client
and the count. Under load (GPU backend probing and crash-dump children
on Windows) that read can sit for seconds; the test's 10 s deadline then
expired and `!WATCH.subscribed()` failed (#7226).

Move the seat onto the events thread. The thread ends on `stop`, or on
the first write a closed client refuses, so the next publish retires the
seat. The test now publishes while it waits, and its wait no longer
depends on the serve thread.
2026-10-27 12:40:41 +00:00
semmelrinde ae3d4faf53 macOS menu: disable design commands behind an open Report a bug draft (#7188) (#7193)
* claim: #7188

* macOS menu: disable design commands behind an open Report a bug draft (#7188)

The native menu bar kept running its actions while a Report a bug draft was
open, although the app's own toolbar and shortcuts wait behind it: Edit > Undo
or Cmd+Z removed the last feature, so the report described a design that no
longer existed. FileAction::enabled now refuses every action but Quit while a
draft is open. Settings, Keyboard Shortcuts and Agent Chat are refused too:
their windows open over the report but take no input until it closes. The
menu's Undo and Redo still go to a focused report field
(FileAction::menu_enabled), which no longer leans on Settings being enabled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Menus: Quit and Help's windows wait behind an open Report a bug draft

Quit switched to the first design with unsaved changes under the draft and
asked about it in a prompt that can't be answered while the report blocks
the app; with nothing unsaved it quit and lost the typed report. Like any
modal window, the draft now turns Quit off. Help's items that open a
window (About, Keyboard Shortcuts, Messages, Crash Reports, Connect AI
assistants) wait too, in the native menu and in the app's Help menu;
Documentation and Report an Issue… stay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 12:39:27 +00:00
yhamil04 6b7f1934b1 Startup tests: start the backends the app would, no OpenGL driver on Windows (#1156) (#7217)
* claim: #1156

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Startup tests: start the backends the app would, no OpenGL driver on Windows (#1156)

The software-adapter test enumerated every wgpu backend and the probe test
asked OpenGL on its own even where the app stops at D3D12, so both loaded the
GPU vendor's OpenGL driver (atio6axx.dll on AMD) into the test process, where
it once aborted the process at exit with c0000409. The adapter test now starts
what the software renderer starts (D3D12 alone on Windows) and the probe test
probes in the app's order, stopping at the first backend with a GPU.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Startup tests: read the software renderer's backends from wgpu_options

Review: the adapter test now takes the backends from the app's own
wgpu_options(true, None) instead of a copy of its choice, and the probe test
no longer re-checks the order choose_backends' own test covers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 12:39:01 +00:00
semmelrinde 9d01dcb358 Open from computer: let iOS and iPadOS pick .solidcraft files (#6977) (#7351)
* claim: #6977

* Open from computer: let iOS and iPadOS pick .solidcraft files (#6977)

The iOS file picker matches an input's accept list by system file type,
not by extension. No system type has the .solidcraft extension, so the
designs were greyed out. On an iPhone or iPad the file inputs now offer
every file; other devices keep the extension filter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 12:38:02 +00:00
DuBearte b47f82d406 Reopen the designs open at the last quit (#5542) (#6006)
* claim: #5542

* Reopen the designs that were open at the last quit (#5542)

A launch with no file to open reopens the saved designs that were open
when SolidCraft was last closed, in tab order. Designs never saved and
files that are gone are skipped. Preferences > General > Reopen designs
turns it off; a launch driven over --control or with --sample starts fresh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Move the new messages to a pending locale file

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
2026-10-27 03:34:04 +00:00
Victor Santos Oliveira a7c1909766 CLI: evaluate scripts on the input design (#6861) (#6872)
* claim: #6861 eval scripts on input designs

* fix(cli): evaluate scripts on the input design (#6861)

* fix(cli): validate eval options before opening inputs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 02:55:01 +00:00
DuBearte 3726959d82 Control channel: check the token before parsing the whole request (#6643)
Until a connection had shown the token, its first line (up to 16 MiB)
was parsed whole into a JSON value. A line of [0,0,...] that size is
hundreds of MB once parsed, so 16 connections without a token could
make the app run out of memory and lose unsaved work. The token is now
read from the request object without building the rest of it: other
fields are only skipped, and anything but an object with a string
token is refused as before.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 02:04:28 +00:00
yhamil04 ebb6cc53ab Crash reports: capture native crashes on macOS (#2113) (#6593)
* claim: #2113

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Crash reports: capture native crashes on macOS (#2113)

A signal handler for SIGSEGV, SIGBUS, SIGILL, SIGFPE, SIGTRAP and SIGABRT
writes the same <journal>.native.json as the Windows exception filter: the
signal, the faulting module and offset, the thread and a frame-pointer walk.
The next start reports it instead of "an unclean exit". The signal then goes
on to the previous handler or the default action, so macOS still writes its
own crash report.

native_crash_macos.rs joins the crash-safety skill's list of files that may
use unsafe (no safe API can catch these signals).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 00:14:00 +00:00
The Nomad afd6a5c584 Send to Slicer: send the selected bodies to a slicer (#2595) (#3600)
* Add Open in Slicer for the selected bodies (#2595)

One command saves the selected bodies as a 3MF in millimetres (an STL if the 3MF can not be written) and opens it in the last slicer used. It is on the File menu, the Mesh tab and the body right-click menu. The first time, or when that slicer is gone, it asks which one with the same list Save as mesh uses. The Save as mesh dialog is unchanged.

* Send slicer files to a private per-user folder (#2595)

Files sent to a slicer used to go to a shared folder in the temp directory. They now go to the user cache folder (mode 0700 on Unix), under a name that stays the same for the same design and bodies, so sending again replaces the file and the slicer can reload it. The file is written to a temporary name and renamed into place. If the name can not be replaced, as on Windows while the slicer holds the file open, the copy gets a number and the status line says so. Files older than seven days are deleted at startup. The first send tells the user that sending again replaces the open file, and so does the tooltip of Open in Slicer. Send to slicer in Save as mesh uses the same path.

* Open files in a running slicer (#2595)

PrusaSlicer, SuperSlicer, Bambu Studio and Cura are started with --single-instance, so a slicer that is already open loads the file instead of opening a second window. OrcaSlicer has no such flag and is left alone, and macOS bundles are single-instance already. PrusaSlicer 3.0 ships a launcher entry that already carries --single-instance-on-url; it keeps that one and never gets a second flag. It is a preference, on by default, and scripts can pass single_instance. Preferences also gets a button to forget the slicer Open in Slicer remembers.

* List slicers the desktop entries say open 3MF or STL (#2595)

On Linux the scan of desktop entries now also lists a program that claims 3MF or STL in MimeType, even when it is not in our table of known slicers. The table still supplies names, ids, icons and flags for the ones it knows. Entries marked NoDisplay or Hidden are skipped, and so are terminal programs and SolidCraft itself. The existing size and count limits stay. The Windows scan now looks inside a Prusa3D folder, where PrusaSlicer installs itself.

* Only start slicers the user can see or chose (#2595)

file.save_mesh took a slicer parameter that could be any executable path, so a script or an MCP client could run any program. The slicer now has to be one of the detected slicers (by id or name) or a program the user already added under Custom, which the app hands to the engine from its settings. The same rule holds for file.open_in_slicer. The agent chat already asked before any file command; its question now also names the slicer.

* Review fixes for Open in Slicer (#2595)

The file name hash keeps all 64 bits instead of 32. When Open in Slicer has to fall back to an STL, the status line now says so. The engine test that sets the chosen programs clears them again. The unused "could not make the folder" message is gone from the catalogs, and the docs say plainly that finding slicers by file type is Linux only.

* Keep unsaved designs apart and hide Open in Slicer in the browser (#2595)

Two unsaved designs with the same name and body names got the same file in the slicer folder. Every document now has a uid that is new for each New, Open and Recover and stays the same for the life of the document, and the file name uses it (with the process id) until the design has a path. At the first Save As the name changes to the path-based one, so the slicer gets one new file; the old one goes with the seven-day sweep.

The browser can not start a program, so Open in Slicer is now disabled there: the engine command reports why, the File menu item and the body and component right-click items are greyed, and starting it anyway says so on the status line instead of opening a dialog. The Save as mesh dialog keeps its browser guard for the open flow too.

* Put the Session doc comment back on Session (#2595)

The new NEXT_UID static had landed between the doc comment and the struct it describes.

* List every install of a slicer, and test the first Save As (#2595)

Detection kept one entry per known slicer, so a system PrusaSlicer 2.9 and a Flatpak 3.0 beta showed up as one. Every distinct install is listed now. The same program found twice, through a desktop entry and through PATH, is still one. When a slicer has several installs, each gets an id made from where it was found (prusa#comprusa3dprusaslicer) that stays the same on every run, and a name worked out from its command and location without running anything: PrusaSlicer (system), PrusaSlicer (Flatpak, beta), PrusaSlicer (AppImage). A lone install keeps its plain id and name. The plain id or name still picks the first install, so a remembered "prusa" keeps working. Single-instance flags and the other per-slicer behaviour go by the id without the install part.

Also a test that the first Save As changes the file name once and later sends keep the name that comes from the saved path.

* Start PrusaSlicer 3.0 first and send the file once it is ready (#2595)

PrusaSlicer 3.0 starts through slic3r-app-launcher, which ignores a file given at a cold start whatever the flag, and loads it over D-Bus when an instance is already running. A small table in slicers.rs names such slicers by their command; today it holds that launcher, so PrusaSlicer 2.9 and OrcaSlicer are untouched. Ready means a name starting with com.prusa3d.prusaslicer.BroadcastListener. is on the session bus, read from busctl --user list with no new dependency. If busctl is missing or fails, nobody waits and the file is sent as before.

When the slicer is not ready, Open in Slicer starts it without the file and looks every 200 ms for up to 10 seconds. The wait runs on another thread, so the window stays live, and the status line says Starting PrusaSlicer. Once the name appears the file is sent with the same arguments as before. If the limit passes, the status line says the slicer did not respond, that the file is already saved, and to send again. A second click during the wait only repeats the Starting line, so there is one launch and one send. Scripts calling file.open_in_slicer wait inside the command by default, or pass wait: return or no.

* Always show the slicer picker for Open in Slicer (#2595)

File > Open in Slicer, the Mesh tab entry and the right-click entry now always open the picker, with the slicer used last time pre-selected, so the usual case is one click or Enter. Scripts and file.open_in_slicer with a slicer still send without a dialog. The preference button that forgot the remembered slicer is gone: it could no longer make anything ask again, and it did not clear the pre-selection either. The guide says how it works now.

* Leave Bambu Studio out of the single-instance list (#2595)

Open files in a running slicer now adds --single-instance for PrusaSlicer, SuperSlicer and Cura only. Bambu Studio is started as it is, so it may open a second window. A test pins that, for the detected slicer and for one the user picked as a custom program. The guide and the preference tooltip no longer name Bambu Studio as getting the flag.

* Sort the English catalog and leave features.md to the generator (#2595)

cargo xtask lists wanted one message moved in en.json after the rebase. docs/features.md is written by the Generated files workflow on main, so the row I had added by hand is gone from this branch; the workflow adds Open in Slicer when it lands.

* Drop the unused folder message from the Vietnamese catalog (#2595)

Vietnamese arrived on main after I removed ui.save_mesh_temp_failed from the other catalogs, so the check that every translated id exists in English failed on it. Nothing uses the message any more.

* Say so when a started slicer can not be found again (#2595)

After Open in Slicer had started a slicer on its own, a failure to look it up again to send the file just returned, so the status line stayed on Starting and then nothing. It now says the slicer was started, that it can not be found to send the file, and where the file is saved. A test covers it.

* Say why a failed busctl ends the wait (#2595)

If busctl fails partway through a wait, the wait stops and the file is sent, the same as when busctl can not be run at all. That is on purpose: a file sent too early is dropped quietly and costs one more send. A comment in wait_for_ready says so.

* Keep the readiness wait inside its limit (#2595)

wait_until asked the probe before checking the deadline, including right after a sleep, so a yes could be accepted after the limit, and busctl ran with no timeout, so one look could outlast the whole wait. The deadline is checked first now, the probe is told how much time is left, and busctl is given at most two seconds (less near the end of a wait) and is killed after that. A hung busctl counts as not telling, which sends the file as before. This covers the first look on the UI thread too. Tests cover a slow probe, the shrinking time left, and a fake busctl that hangs, answers and fails.

* Send the file that was already written once the slicer is ready (#2595)

When the wait for a started slicer ended, the UI ran file.open_in_slicer a second time. That exported the design again on the UI thread, from whatever the session looked like by then, and could write a numbered copy while the timeout message still named the first file. The wait now keeps the slicer it found and the answer of the first call, and opens the file that was written. A test changes the design during the wait and shows the same file is sent, is not written again, no command runs, and no second file appears.

* Send with the single-instance flag after starting a slicer (#2595)

After Open in Slicer has started a launcher that needs a start first, the file has to reach the instance that is now running, so the send that follows a start carries --single-instance whatever the preference says. A normal send, to a slicer that was already up, still follows the preference. A comment in both places says why. The test uses a plain launcher command with the preference off: the normal send has no flag and the send after a start has it.

* Find the remembered slicer again after an uninstall (#2595)

With two installs the remembered key is prusa#prusaslicer. Remove the other install and the lone one is plain prusa, which matched neither the exact key nor the plain-key fallback, so the picker fell back to the first slicer in the list, maybe a different one. The key is now also matched by the part before the #, so the same slicer is pre-selected. A test covers the lone install, a plain key with two installs, an exact key, and that another slicer is never picked.

* Say a second click did not send anything while a slicer starts (#2595)

While a started slicer is being waited for, another click showed Starting <the first slicer> even when it had picked another slicer or other bodies. It now says it is still waiting for that slicer and that nothing else was sent. The test checks the wording and that it names the slicer being waited for.

* Label macOS installs by their application (#2595)

An open -a command resolved its program to /usr/bin/open, so every application on a Mac was labelled system. Labels for open commands now come from the .app path: /Applications is system and a folder under home is shown as ~/Applications. A test with two PrusaSlicer.app bundles and a made-up home checks both labels and that the ids differ.

* Pick the same place for a program found twice every time (#2595)

When two desktop files, or a desktop file and PATH, pointed at one program, the first one read was kept and its file name went into the id, so the ids could change with the order the folders were read in even though nothing was installed or removed. The place that sorts first now speaks for the program. A test with two aliases and a second install lists them with the folders in both orders and gets the same ids and commands.

* Treat flatpak run spelled two ways as one install (#2595)

flatpak run ... and /path/to/flatpak run ... with the same arguments had different install keys, so one invocation could be listed twice. The launcher is now compared by its file name and the arguments are kept, so both give the same key, while a different branch is still its own install. The test uses a real launcher file so the full-path entry is not skipped as stale.

* Say plainly that a late yes is accepted on purpose (#2595)

The comment on wait_until said a slow probe could not accept a yes that came too late. That was not what the code does: no probe is started after the limit, but one already running may finish and its yes is taken, because the file should still go. The comment now says so, with the bound: the limit plus that last probe, which in production is given the time left and so ends by the limit. A test has a probe that runs past the limit and says yes, and checks it is accepted and the wait stays within limit plus the probe time, and that a probe that says no is not followed by another.

* Give reading busctl the same deadline as running it (#2595)

After busctl exited, the output was read with a join that could wait forever if a process it left behind kept the pipe open. The reader now hands the text back over a channel and the wait uses the time that is left of the same limit, and gives up with nothing otherwise. The polling sleep no longer overshoots the time left either. A test uses a fake busctl that backgrounds a sleep with its output inherited and exits at once: the call returns within its limit.

* Tell two different flatpak launchers apart (#2595)

Comparing only the file name of the launcher made /opt/tool-a/flatpak run ... and /usr/bin/flatpak run ... with the same arguments one install even when they are different programs. The launcher is now found the way any program is, through PATH and with links followed, and its name stands in only where it can not be found. The test has flatpak on PATH and the same file by its path as one install, a different executable of the same name as a second, a different branch as a third, and the fallback when there is no PATH.

* Keep waiting when busctl is slow, send only when it is missing or fails (#2595)

Since the busctl timeout, a slow busctl came back as "can not tell", and that was read as send now. One slow look, likely just when a cold PrusaSlicer 3.0 is starting under load, ended the wait and sent the file early, which gets dropped. The look now says why it has nothing: slow means not up yet, so the wait goes on to its limit and then reports that the slicer did not respond; a missing or failing busctl still means it can not be told and the file is sent. The comments name only those two cases. Tests use fake busctl programs: one that hangs keeps the wait going to the limit and times out, a missing one sends at once, one that says not up and then fails sends, and one that names the slicer is ready.

* Do not open an empty path when the answer had no file (#2595)

If the answer kept for a started slicer had no path, the send ran open with an empty string. The tick now says there is no file to send and starts nothing. The test gives a wait with no path, checks the error line, and that the fake slicer was never run.

* Drop the unused folder message from the Danish catalog (#2595)

Danish arrived on main with ui.save_mesh_temp_failed, which this branch removed from English because nothing uses it any more, so the check that every translated id exists in English failed. The entry is gone from the Danish catalog too.

* Always start the 3.0 launcher with the single-instance flag (#2595)

A hand run showed that starting the PrusaSlicer 3.0 launcher with no file while a 3.0 is already running forwards to it and exits when the flag is there, but opens a second full window when it is not. A slow first look at the bus can start the launcher twice, and with the preference off the flag was stripped. A slicer that has to be started first now keeps the flag on that start whatever the preference says, the same reasoning as the send after a start; when nothing runs the flag does no harm. A normal send with the preference off still has none. The test covers the launcher entry, a bare launcher command, a normal send and a slicer that is not on the table.

* Swap --single-instance-on-url where it does not hand the file over (#2595)

A hand run on the PrusaSlicer 2.9.6 Flatpak showed that its entry's --single-instance-on-url does not forward a file to the running instance: every send opened a new one, three after a cold start and two sends. With plain --single-instance the second process finds the first, forwards, and exits. So when a command carries --single-instance-on-url and the slicer is not on the wait table, that is, not the 3.0 launcher, it is replaced by --single-instance, with the file markers handled as before, when the preference is on, and stripped when it is off. On the 3.0 launcher the flag does forward and stays. The comment on the rule says why. A test uses the real 2.9.6 and 3.0 Flatpak command lines.

* Cut long slicer names to fit the picker (#2595)

With installs listed on their own, a name like PrusaSlicer (Flatpak, master) is wider than its row. The list painted it with no limit, so it ran out of the popup, and the closed selector wrapped it onto a second line. The list now lays the name out on one line that ends in an ellipsis when it does not fit, the selector truncates, and the full name is in the tooltip when it was cut (for a program the user added, ahead of the delete hint). A test checks that a long name is cut to one line within the width and a short one is left alone.

* Keep the 3.0 launcher's flag on a send whatever the preference says (#2595)

A hand run with the single-instance preference off showed the 3.0 launcher needs its flag on a send too, not only on a start: a cold start with a file and no flag loaded nothing, and a send to a running 3.0 left a launcher process that never forwarded. A slicer on the wait table now gets the flag on every launch, as it already did when started alone. The doc comment says why. The preference text, in English, Japanese and Chinese, and the guide now say that PrusaSlicer 3.0 is the exception to turning this off. The tests that took the flag away with the preference off now expect it, for the launcher entry, a bare launcher command and the 3.0 Flatpak line; the test of the send in the UI checks the first send carries it.

* Keep the install-flag swap to PrusaSlicer, found by what it is (#2595)

The swap of --single-instance-on-url for --single-instance ran for any program whose command had the flag, so another program with that flag would have been rewritten. It is now a PrusaSlicer rule: it applies to a slicer recognised as PrusaSlicer by its identity, its id or, for a program the user picked, the name it goes by, never by a version number. The single-instance table uses the same identity check. A test keeps the flag of a program that is not PrusaSlicer, strips it when the preference is off, and swaps it for a picked program named PrusaSlicer.

* Do not promise a hard bound in the wait comment (#2595)

The comment on wait_until said the production probe ends by the limit. Its clock starts after the process is spawned, and killing and reaping the process and scheduling are not bounded by it, so it is a polling and read budget with process overhead on top. The comment says that. Comment only.

* Tell PrusaSlicer 2 from 3 by the installed version (#2595)

PrusaSlicer 2 and 3 need different handling, and the rules had to guess from the command line. Each install of PrusaSlicer now carries its major version, read when it is detected from the release at the top of its appstream metadata and never by running the program: a Flatpak's own files (the branch the command names, else the current one), or the metainfo file beside a desktop entry. PrusaSlicer older than 3 is sent the file with --single-instance in place of --single-instance-on-url, which does not hand a file over there, and is not waited for. PrusaSlicer 3 and newer keeps the launcher's flag, is started on its own, waited for, and sent the file. When the version can not be read, the command decides as before: the slic3r-app-launcher launcher counts as 3, and any other entry is left as it is. A program that is not PrusaSlicer is untouched whatever its flags. Tests use metadata fixtures, no Flatpak: 2.9.6, 3.0.0-alpha10, a branch that is not installed, a system package, an unreadable version with and without the launcher, and a non-PrusaSlicer program with the same flag.

* Make the busctl wait tests steady under load (#2595)

One of them failed once in a full run at its first assertion and passed alone. A script that has only just been written can fail to start (text file busy) while another test forks, and that reads as a busctl that is missing. The fake programs are now started once, patiently, with a flag they answer at once, before a test relies on them; a hanging one uses exec so it is killed cleanly. The time bounds that could trip on a busy machine are several seconds now, and the checks are on outcomes: not ready and keeps waiting, send now, and the number of looks. Thirty runs alone, thirty of the whole slicers filter and thirty of the busctl tests under eight busy loops all pass.

* Say what the busctl reader thread leaves behind (#2595)

The thread that reads busctl output is left to end when the pipe closes. A busctl that answered but left a process holding its stdout reads as slow, and every poll of such a bus can leave one blocked reader behind until that process lets go. A comment next to the thread says so. Comment only.

* Make Save as mesh a plain export (#2595)

Save as mesh asked for the bodies and the format, and could also send the file to a slicer with a checkbox and a slicer list. Sending now has its own command, so the checkbox, the list and the Send label are gone from this dialog: bodies, format, a file dialog, Save. The two settings that only the checkbox used, save_mesh_send and save_mesh_slicer, are no longer kept; a settings file that still has them loads, and they are ignored. The strings only they used are gone from the catalogs. The slicer list stays in the dialog of Open in Slicer. file.save_mesh keeps its slicer parameter for scripts. The guide describes the two as separate.

* Rename Open in Slicer to Send to Slicer (#2595)

The command sends a file to a slicer rather than opening one, and a later Send to App can share the name. File menu, Mesh tab, right-click menus, the dialog button, status lines, the guide and the English, Japanese and Chinese strings now say Send to Slicer, and the command id file.open_in_slicer is file.send_to_slicer. It is new in this change, so there is no alias. The remembered slicer is stored as send_slicer, and the question the agent chat asks says send to. Module and type names stay as they are.

* Put the send targets in one table and one send pipeline (#2595)

The known slicers, the list of slicers with a single-instance flag and the rule for slicers that must be started and waited for were three separate things. They are one table of targets now: id, label, kind, names and program files, accepted formats and the default, how the file is handed over, the single-instance flag, the version from which PrusaSlicer's on-url flag hands a file over, and the wait-for-ready rule. Programs that are not in the table are slicers with the same defaults. The kind is slicer, the only one so far; its comment says it is where a later Send to App would plug in. The send itself is one function: write the bodies, find the target, start it and wait if its rule says so, hand the file over, report. Detection and resolution take a kind. Nothing changes for what already worked.

file.save_mesh keeps its slicer parameter, now through the same function, with its export arguments and result unchanged: a cold PrusaSlicer 3 is started and waited for before the file goes, and it accepts the program path of a detected slicer as file.slicers lists it (file.slicers gives it as program). A path is accepted only when it is the program of exactly one detected target, and that target's own command and arguments run; a launcher several entries share, such as /usr/bin/flatpak, is refused as ambiguous, and any path nobody listed or chose is refused. The parameter and the code say it may be deprecated later in favour of file.send_to_slicer or a Send to App workflow, which is a possibility and not a plan.

Tests go through file.save_mesh itself for the two reported failures, a listed AppImage path and a cold PrusaSlicer 3 that is waited for, with the slicers and the busctl made up; and they check that the result and the file are what an export without a slicer gives, and that a path that is not listed, or is shared by two entries, writes nothing. The pipeline has its own tests: a cold launcher waited for, one already up, wait return, and one that never comes up. A test of save_mesh that shared a file name with another is given its own.

* Keep an AppImage's id through an upgrade (#2595)

When a slicer has several installs, each gets an id made from where it was found. For an AppImage that was its file name, which carries the version, so upgrading it changed the id and the remembered choice missed. The id is now made of the slicer's id, the fact that it is an AppImage and a short hash of its folder, so a new version that replaces the old one in the same folder keeps the id, and the same AppImage in another folder is its own install. Two versions kept side by side in one folder are two installs: while both are there, their file names are added to their ids to tell them apart.

A remembered id is found again only when it names exactly one install. An id that was one AppImage and is now two versions, or a plain id that is now several installs, is treated as not remembered, so the picker starts from the first entry as on a first run and does not quietly pick another install; a single install left of the same slicer is still found, as before. Installs that are not AppImages are told apart as before. An id from before this change no longer matches. Tests cover an upgrade, two versions side by side, a second folder, and the picker's side of each case.

* Add Open with EasyPrint to Send to Slicer

EasyPrint is Printables' slicer in the browser. It's a plain text row in
the Send to Slicer dialog, backed by a new file.open_in_easyprint
command. The command exports the bodies as a 3MF (or STL), hands them to
the host to upload to slicer.printables.com/import, and the app opens the
link that comes back. The desktop app uploads with ureq and the browser
build with fetch, so this is the one send target that works on the web.

The model leaves the machine, so the command refuses without
consent: true, the dialog asks once and remembers, and an AI agent's
request asks first. A model over 100 MB is refused before anything is
sent, and a failed upload shows as an error with the reason.

The chat's privacy note said SolidCraft sends nothing anywhere, which
isn't true any more, so it now says that about the chat. New strings are
in every catalog.

* Place Send to Slicer on the Insert Files panel
2026-10-26 19:59:35 -04:00
MAKAO 30f951ecf9 Navigation bridge: a device's own app moves the view (#6094)
A homemade 3D input device's driver app (MAKMOUSE) listens on a loopback
port; with Preferences > Navigation > "Navigate with a device app on this
computer" on (off by default), the desktop app connects, says hello and
reads JSON lines: axis motion (orbit, pan or zoom by mode), zoom steps and
standard views. The motion goes through navigation::device like a 3D
mouse's, only in the focused window; a HID device the app names is left
to it. Documented in docs/3d-mouse.md.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 22:14:52 +00:00
raykholo 66b872a749 Web app: open a design from a same-origin path (#6670)
?open= now also accepts an absolute path on the page's own origin, fetched
with the page's scheme, so a self-hosted setup that serves SolidCraft and
other tools under one origin can link a design on a LAN without a public
HTTPS address. Absolute URLs keep the HTTPS-only rule (a URL on the page's
own origin is also accepted), redirects must end on HTTPS or the same
origin, and protocol-relative, dot-segment, backslash and control-character
values are refused.
2026-10-26 12:23:52 +00:00
Mateusz Sych d895fef354 Control channel: a pushed design.changed event for clients that subscribe (#6985) (#6986)
* claim: #6646

* Control channel: document.revision, a design revision and a document id (#6646)

A program that follows the open design asks document.revision and exports
only when the answer moves: document_id names the open design (kept through
edits, saves and renames, another for each design started or opened, never
shared between two tabs or two runs) and revision moves whenever the design,
what is hidden or an uncaptured component position changes. It stays put on
reads, selections and view changes, unlike the session's view revision.

The revision is read lazily: the session remembers the design it last
reported (held weakly, so Arc::make_mut can never change it in place and its
address can't be reused) and takes a new number from one counter for the
whole run when the design is another one. The control method answers at once,
also while a slow command computes; engine.execute reaches the same command
for scripts and MCP.

* document.revision: hash the run's id prefix with the process's random keys

A clock set before 1970 gave every run the prefix 0, so a restarted app could
answer a new design with the id and revision of the old one. The docs no
longer promise an answer at once: a screenshot waiting for an opened design to
be meshed still holds the requests behind it.

* document.revision: its label in the complete catalogs (ja, zh)

* claim: #6985

* Control channel: control.subscribe and design.changed events (#6985)

A connection that sends control.subscribe gets the active design's id and
revision in the reply (as document.revision answers), then a
{"event":"design.changed","params":{document_id, revision}} line whenever
either changes, so a live link need not poll.

The window publishes the design at the end of each frame's logic and
drawing while a connection is subscribed (one atomic load otherwise). Each
subscribed connection waits on that from a thread of its own and writes
the latest after a 100 ms gap, so a burst of changes is one line. Replies
and events share one lock per connection, each written whole (#6390).

* control.subscribe: the reply before any event, a second one refused

The events thread started before the subscribe reply was written, so a
change right after the window's answer could reach the client before the
reply that named the older revision. The subscription now starts holding
the connection's line lock and writes the reply under it.

A second control.subscribe on a subscribed connection is refused on the
connection itself: its answer could arrive behind a newer event. When no
thread or socket is left for the events, the reply says it did not
subscribe instead of ok.

* control.subscribe: a second one does not lose a finished late reply

The already-subscribed answer came before the busy check, so a request that timed out and had finished since lost its reply: control.pending then said the app ended it without one. It now comes after the busy check and drops a finished late reply as every other request does.

* control_server: declare the subscribe guard at the top of the loop

Clear of the lines #6643 changes (its token check moves to where the declaration was).

* control_server: the subscription's items beside serve

Away from the lines next to send, where #6643 adds token_of.

* control test: New on a blank design reuses its tab, so redo the box first

---------

Co-authored-by: MateuszSychXpl <151755914+MateuszSychXpl@users.noreply.github.com>
2026-10-26 11:10:03 +00:00
Colin Faulkingham 03bf992bc1 Keep linked components valid through Undo after Save As (#6860) (#6877)
* claim: #6860

* fix: preserve linked component targets across Save As history (#6860)
2026-10-26 11:08:32 +00:00
raykholo c264c7a42a Startup falls back from a Direct3D 12 that can't build the UI pipeline (#6648) (#6682) 2026-10-26 10:44:13 +00:00
raykholo 189b47c750 Chamfer and Fillet refuse a point where several edges meet (#5173) (#5177) 2026-10-26 10:42:59 +00:00
Jesper We 0c4eb458df macOS 3D mouse: unregister from the driver when the app ends, so the next launch gets the mouse (#6114) (#6115)
* claim: #6114

* macOS 3D mouse: unregister from the driver when the app ends, so the next launch gets the mouse (#6114)
2026-10-26 10:42:25 +00:00
Victor Santos Oliveira b3cbbe7bd5 CLI: reject incomplete and unsupported run/eval options (#6918) (#6919)
* claim: #6918 validate run and eval options

* fix(cli): reject invalid run and eval options (#6918)
2026-10-26 10:38:56 +00:00
raykholo 36a60a70d9 Graphics: 1x anti-aliasing where the multisampled view draws nothing (#5819, #5870) (#5832) 2026-10-25 22:10:04 +00:00
raykholo 0344a41dc4 Open and insert a DXF outside a sketch (#2052) (#4635) 2026-10-25 21:03:31 +00:00
JK3D 492bba9242 Control channel: write each reply in one piece (#6389) (#6390)
* claim: #6389

* perf: write each control reply to the socket in one piece

The control server wrote each reply with writeln!(out, "{reply}") on the unbuffered TcpStream.
Formatting a serde_json::Value hands the writer every string, number and punctuation mark
separately, so each became its own write system call. A reply of a few megabytes (component.list
on a design with thousands of components) took about half a second to send.

Every reply is now serialized to one buffer and written at once. Writing one large Value to a
loopback TcpStream: 1.79 MB took 1,052 ms with writeln! and 4.9 ms with one write_all.

The test counts the write calls for a 2000-component reply (one) and reads it back.
2026-10-25 17:55:14 +00:00
raykholo b1da434458 Graphics: choose the graphics card, honour WGPU_ADAPTER_NAME (#5986) (#6010) 2026-10-25 16:49:57 +00:00
raykholo 4cd9a4f4d4 Web app: open a design from a link, and an embed mode (#6252) (#6253)
* claim: #6252

* Open hosted designs in the web app and add an embed layout

Fixes #6252.

Fetch HTTPS design links with bounded reads and open them as unsaved copies.
Keep model panels and joint controls in embeds without browser persistence,
application chrome, or unload prompts. Add a full-app link and iframe docs.

Cover query parsing, hostile URLs, size limits, and the compact layout.

* Translate the linked-design messages into Japanese and Chinese
2026-10-25 14:40:01 +00:00
bherbruck 093571cfe4 Connect AI apps at start-up, with a preference to turn it off (#6388)
At launch the app adds the solidcraft entry to each installed AI client off the window thread,
names newly connected clients once, and refreshes a stale path. Settings > Assistant has
"Make SolidCraft available to AI apps on this computer" (on by default); off removes the entries it
added. Entries the user changed are left alone; automation runs never touch the files.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 13:51:55 +00:00
Colin Faulkingham 16a5254e97 Fix macOS fullscreen application-bar interaction (#3643) (#3690)
* claim: #3643

* fix: keep macOS fullscreen application bar clickable (#3643)

* test: load bundled fonts for fullscreen regression
2026-10-25 12:49:02 +00:00
bherbruck dfde1e032d mcp setup: add SolidCraft to the AI clients on this computer; plain mcp finds a late app (#6379)
solidcraft-cli mcp setup (alias install) and Help > Connect AI assistants edit each installed
client's own configuration in place, with this program's absolute path: OpenCode, Claude Desktop,
Claude Code, Cursor, Codex, Gemini CLI, Copilot CLI, Windsurf, Zed and VS Code. Only the solidcraft
entries change; a stale path is refreshed and an entry the user changed is left alone.

Plain mcp keeps looking for the app while its headless session has no work, and says on every
tool result that it is headless when the app was not found.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 11:57:51 +00:00
raykholo cc1e654690 Warn when a design is open elsewhere or changed on disk (#5467) (#5468)
* claim: #5467

* Warn when a design is open elsewhere or changed on disk (#5467)

The desktop app keeps a lock file next to each open design
(.~<file>.lock: computer, user, version, opened and refreshed times),
refreshes it every minute and removes it on close; a lock not
refreshed for three minutes is stale and ignored. Opening a design
that another SolidCraft has open asks: Open Read-Only, Open Anyway or
Cancel. A read-only design saves only to another file.

A design remembers its file's size and modification time when it is
opened or saved. Save over a file another program or computer changed
since asks: Save a Copy (the default), Overwrite or Cancel.

Programmatic calls never ask: doc.open reports open_elsewhere and takes
read_only, and file.save over a changed file returns an error until it
is given overwrite: true. ui.documents answers both questions.
2026-10-25 03:34:20 +00:00
raykholo 138bb0261c Windows OpenGL: use the system title bar so the picture lines up with clicks (#2063) (#4403)
* claim: #2063

* Windows OpenGL: use the system title bar so the picture lines up with clicks (#2063)

On Windows with only OpenGL to start, the window opens with the system
title bar instead of using the application bar as its title bar. The
window without OS decorations keeps its caption style while having no
frame, and some OpenGL drivers (older Intel HD/UHD) show the picture a
caption's height lower than the window, so clicks land above what they
point at. Direct3D 12 and Vulkan keep the custom title bar.

The choice is made before the window opens, from the backends wgpu
starts, so the window never changes its frame while OpenGL draws into it.
2026-10-25 03:19:34 +00:00
bherbruck a150822d9b Put solidcraft-cli on PATH from the AppImage and the macOS app (#6347)
The AppImage writes a ~/.local/bin/solidcraft-cli launcher from its desktop-integration step
(same opt-outs, never over a file it did not write). The macOS app links the CLI into
/opt/homebrew/bin or /usr/local/bin at launch when one is writable, and Help > Install
solidcraft-cli command does it with an administrator prompt otherwise. Snap and Flatpak are documented.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 02:45:45 +00:00
ivisaf 765c5fd143 Pictures drawn off the window thread finish before the process exits (#6062) (#6063)
A Home picture still being drawn by the detached saved-thumbnail thread
could be on the GPU as the process exited, and the process died with
SIGSEGV inside the graphics driver. capture::close stops new pictures
and waits for those in flight (the app calls it in on_exit), and
dropping SolveApp waits for the Home picture threads it started.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 02:29:40 +00:00
neosandeep24 9ad96fb81b Home: keep a removed recent design removed (#4660) (#4718)
Removing a design from the Start page's recent list only changed memory, and on the web sync_recents() rebuilt the list from stored designs on each launch, adding it back. Track removed designs in a persisted 'forgotten' set that sync_recents honours; opening or saving a design, or deleting/renaming its file, clears it.
2026-10-25 01:26:31 +00:00
Matthew Knutson 05d43d3542 Windows: the taskbar button shows the mark when the first frames are slow (#6391)
The taskbar asks a window for its icon once, when the window appears. eframe
sets the icon while the window is still hidden and shows it after the first
frame; when that frame is slow (safe mode's software renderer) or startup kept
the UI thread busy long enough for Windows to mark the window as not
responding (building the viewport pipelines), the request isn't answered in
time, the taskbar falls back to the window class's icon, and winit's class has
none: the button showed Windows' generic icon for as long as the window was
open.

Give the class the exe's embedded icon (resource 1) before the window is shown.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 01:24:26 +00:00
Tyler Chiriboga 2ff17c0fcb 3D mouse: follow the device that moves, not the first one found (#3988) (#4339)
* claim: #3988 (3D mouse: follow the device that moves, not the first one found)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* 3D mouse: follow the device that moves, not the first one found (#3988)

On Windows and macOS the reader opened the first 3D-mouse interface it
could and read it until it failed. A 3Dconnexion Universal Receiver with
nothing paired opens fine, has a valid layout and never fails, so it kept
the reader for good. A SpaceMouse on a cable next to it was never read
("Not calibrated (hid 256f:c652)", every axis at zero).

The reader now opens every candidate and follows the one in use. The
first is followed from the start, as before. Another takes over once it
shows use (an axis off zero or a button down) while the followed one has
shown none for STALE_AFTER, so two devices moved together don't trade
the reading. A candidate that fails is dropped; if it was the followed
one the reading resets and the next is read. With one device the reader
behaves as before, down to the 100 ms read timeout. Several take 10 ms
turns, and reports queue in the system meanwhile.

The selection is `follow()`, written against a small `ReportSource`
trait, so it is tested with simulated devices on every platform: an idle
receiver next to a SpaceMouse, two devices in use at once, and devices
failing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* 3D mouse: a press that makes a device the one in use counts as a press

Since #3825 a HID device's first button report after it connects is its
baseline: a button down in it is held, not pressed. A device taking over
from another one was connected at that moment, so the press that took
over was swallowed. With an idle Universal Receiver found first, that is
the first button press on the SpaceMouse.

follow() now keeps the buttons each candidate last reported while another
was read, and the reader hands them to the reading as the baseline when
that candidate takes over. A device that reported no buttons before it
took over keeps the held-not-pressed rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Tyler Chiriboga <18270301+Tyler-Chiriboga@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 01:18:34 +00:00
raykholo 4cd1a51f48 Assistant: a request timeout per endpoint for slow models (#5009) (#5010)
* claim: #5009

* Assistant: a request timeout per endpoint for slow models (#5009)

An OpenAI-compatible endpoint now stores how long one model request may
take (request_timeout_s, 300 s by default, clamped to 30 s ..= 1 h). It
is set in the Add agent dialog and in the endpoint's entry under Agents
(a running conversation uses a change from its next request), through
ui.agent_chat add_agent, and reported in the chat's state. A request
that runs out of time says the limit and where to raise it instead of
"timeout: global". The desktop HTTP cap goes from 900 s to an hour.

The tool-call timeout stays 330 s: it covers SolidCraft's own tool work
and the user's Allow, never the model.

* Assistant settings: line up an endpoint's Request timeout with Permissions

The label column of an agent's entry is wide enough for "Request timeout",
so the label isn't cut and both rows share one column.
2026-10-24 22:24:42 +00:00
Nathalie Schönwetter b702ce8c7d fix: start the window on Intel Macs without wgpu's indirect-call validation (#3024) (#4808)
Co-authored-by: Nathalie Schönwetter <nathalie@cfw-ag.ch>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 21:52:55 +00:00
raykholo 0520910bfd Control: name the token file and spot a stale one when a call is unauthorized (#4664) (#4670)
* claim: #4664

* Control: name the token file and spot a stale one when a call is unauthorized (#4664)

When the app refuses a token that the MCP client read from a token file,
the error now names that file, when it was written and how long ago,
without printing the token. The app also writes `<port>.pid` beside the
token file (its process id and, on Linux, its start time) and removes
both at exit; when that process has exited, the error says the token
file is stale. The process check runs only after a refusal, and the
token file itself stays one line, so existing clients keep working.

Document force-quit leftovers and the MSIX AppData redirection.

* Bench assistant: read the token out of the new ClientToken

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 17:28:12 +00:00
cronos 4f4b1fc2d2 Sketch: select edge midpoints for horizontal and vertical constraints (#5159) 2026-10-24 16:15:45 +00:00
selune 9e6e0b8df1 Render workspace: complete experimental ribbon, resources and effects integration (#5493) (#5494)
* wip(render): reconcile completed material and image pipeline source with 0.20 (#5493)

Source-only integration reference for staged review, not a merge-ready aggregate.
Donor: 0xlillith/solidcraft@aea20b0115.
Upstream: 858e5995d6.

Retain original CPU/material contracts and explicit GPU donor provenance in
docs/render-materials-lab.md (#2067, #2191, #2780, #3306). Preserve released
format migrations and current navigation/startup/shell behavior. New saved
records use pending descriptors. The physical-card descriptor follows the
maintainer's material-engineering-cards naming request, with a legacy-lab
format-29 regression. Replace raw new panel hosts with shared containers.

Repository checks, complete workspace/all-target Clippy, and the independent
physical/resource authoring checks pass for this source. The fresh full local
CI invocation has reached test compilation and is not yet complete. Do not
transfer donor native output or prior test totals to this integration.

Generated review screenshots, video, package ZIP and machine logs remain at
the immutable personal evidence pin. Uncommitted geometric displacement is
backed up separately, not silently included. No kernel/vendor change, direct
main write, release or independent contributor-branch rewrite.

Co-authored-by: Christian Svensson <blue@cmd.nu>

* test(cli): pin the Compact camera-sample admission diagnostic (#5493)

The bounded request still refuses 1024x1024x64 samples before host initialization and writes no output. Update only the stale expected diagnostic to the existing Compact budget wording; production limits and code are unchanged. Exact-tree CLI regression suite: 14 passed, 0 failed. The full repository gate has been invoked separately and is not yet complete.

* Normalize UI container violation paths across platforms

Report repository-relative paths with forward slashes so diagnostics and the existing nested-package regression assertions agree on Windows and Unix. Keep the container detection rules unchanged.

Validation: official ui-containers gate and all seven focused xtask tests passed; rustfmt and diff checks passed. Only xtask/dependencies were compiled; no application build or heavy tests.
(cherry picked from commit 44b58d8e10)

* fix(render): preserve camera contracts and correct acceptance regressions (#5493)

Preserve Design view.apply semantics with a separate strict Render camera command. Retain existing translations when English-only aliases collide; add material-axis entries and camera-specific pitch labels. Compare complete validated engineering cards and retain job handles before cancellation.

Focused and native acceptance is pinned to tree f8a423b21f. Full CI has been invoked separately and is not claimed green. Landing follows the render integration policy in #5493.

* fix(render): resolve showcase licence and upstream resource hygiene (#5493)

Record SychPL's explicit MIT OR Apache-2.0 grant, retaining source/model bytes and the Ubuntu lettering notice. Remove 80 absent attribution rows, personal fork records, dead delivery attributes and three fork/resource-only workflows. Existing xtask assets now performs bounded offline hash checks and rejects orphan render attribution; no parallel draft CI or package installation. Original authoring reproduction remains explicit. Scoped xtask tests, assets, Clippy, formatting and prepared SELENE validation pass; no application or renderer-wide result is inferred.

* fix(render): align saved bounds and progressive sample resolution (#5493)

Address review r4364211669/r4364211677/r4364211681/r4364211691/r4364211695. Persisted light/frame domains respect the existing 1e9 document bound; related LED/rectangle/direction cases use the same bound. Workstation primary resolve shares the 4096 admission cap and film counters use checked conversions. Exact candidate: 485 CPU render tests, 21 render document tests and strict scoped Clippy pass, including positive boundary round trips and 2048/4096 end-to-end checkpoints. No released format/migration edits, relaxed test limits or full-stack acceptance claim.

* wip(render): isolate optional resources and harden sample handling (#5493)

Review work in progress: opt-in encoded render library with reproducible runtime catalog exports; native-only robust dependency through the workspace; checked bloom slices; per-sample point-light diagnostics; independent area-light spatial/film keys. Resource-module binaries in both configurations, generation, Cargo production feature graphs and attribution checks pass. Full engine/render regression and combined acceptance are pending: do not merge this donor as a constituent or treat scoped resource proof as runtime acceptance.

* fix(gpu): preserve donor allocations through the scope-safety repair (#5722)

Port the native-validated #5723 scope-cleanup correction into the experimental donor, retaining its adaptive, volume, aging, photographic and bloom allocations. Same reverse-drain helper and regression cases; the donor combination still needs its own compile/test gate and is not covered retroactively by render-base results.

* claim: #1369

* Reject unknown pending concepts after format folding (#1369)

* test(render): close bounded sampling and bloom review regressions (#5493)

Validate the optional-resource/lighting/dependency donor at its exact tree. Required Vulkan GPU 127/0/3; CPU renderer 487/0; engine Render 72/0; document render 21/0; lean library 7/0; strict scoped Clippy, assets/layers/lists/containers and lean-engine WASM pass. Preserve original failed controller/typing attempts. This is the draft donor/disposition source, not a replacement for constituent render PRs or final main acceptance.

* wip(render): preserve stable aging target validation candidate

Exact staged source from prior review work on donor 3b892eab5e. This is a recovery/review checkpoint, NOT accepted delivery. An actual command-path regression currently fails because the selected executor refuses ordered surface aging. No assertion, capability or admission limit is weakened. Native/UI/full combination acceptance remains pending. Saved/runtime split adds one pending descriptor and retains original file scope; no main/render branch is changed.

* test(render): preserve numeric setup intent at the admitted coordinate bound

Follow up jyviko review on #5494 without weakening the corrected saved-coordinate contract. The positive clicked fixture uses 1e9 instead of inadmissible 2e9; explicit out-of-range cases still refuse, and the full saved setup round-trips through JSON and the native container. Exact-source Render workspace 95/0 and strict all-target UI Clippy pass. This does not incorporate the separately failing stable-aging candidate or claim full current-render acceptance.

* test(render): use the real GPU executor for saved aging

Move successful native cold-reopen Check/Start coverage from the CPU-backed protocol fixture into the production GPU executor tests. Verify capture-index changes preserve the stable target and that enabling aging changes both PNG and raw EXR output. Retain explicit, allocation-free refusal coverage for executors without aging support. Production capabilities and admission checks are unchanged.

* fix(render): persist stable identities for targeted aging (#5493)

Separate saved RenderBodyTarget identities from captured mesh ordinals. Reject legacy ambiguous targets, resolve Check/Start after visibility changes and preserve native reopening, copies and undo. Unsupported executors still refuse; real AMD Vulkan regression completes saved aging after reordered capture.

Source tree 6b84c8ad passes document 23, engine Render 75, GPU effects 5 and Render UI 95 with no failures; scoped strict Clippy, WASM and repository gates pass. Fresh CLI/native reopen and clean/aged GPU exports pass with identical physical totals and inspected actual images. Whole current-render integration is separate.

* fix(render): reconcile combined CPU/GPU contracts on current upstream

Unify captured resources/status and CPU-vs-GPU request schemas after the real two-parent integration; remove obsolete duplicate Vulkan selector and retain combined platform-filter regression. Deduplicate only identical or superseded test definitions, retaining the stronger card metadata and reserved-executor assertions. No assertions, admission limits or feature refusals are weakened.

All targets of render dependencies, GPU, engine, UI, CLI and native app pass cargo check with raytrace-cpu and bundled-render-library at tree c70d00d1. Native executable tests and full current-source acceptance remain pending. This is a private integration rehearsal for subsequent constituent PRs, not an aggregate official render/main merge.

* test(render): retain one complete host lifetime regression set

Deduplicate byte-identical DropProbe/task-lifetime tests and retain the adapted environment/texture ownership regressions with every assertion and complete memory estimate. Keep the registered-geometry admission regression once; only struct field order differed. Replace a one-element diagnostic loop with its equivalent block, preserving all wrap/width assertions.

Test-only integration repair. Formatting and diff checks pass; refreshed executable validation is required. No production behavior, limits, assertions or ignores changed.

* fix(doc): recognize the proven format-29 card descriptor alias

Normalize only the historical format-29 physical-material-cards spelling, and only when material-engineering-cards is registered. Admission and migration de-duplication use the same identity. Unknown concepts, wrong versions and incapable readers still refuse before mutation. Six controlled-history Rust regressions and actual CLI migration/no-clobber acceptance pass. No version, released migration or CI changes. Refs #5793, #1369, #5493.

* test(render): preserve grounding refusal before explicit instance moves

Update the two Render binding fixtures for upstream first-occurrence grounding. Verify grounded movement refuses without changing document/history, then explicitly unground through the public command before the intended move. Retain every target, resource, copy and persistence assertion. Refs #5493.

---------

Co-authored-by: Christian Svensson <blue@cmd.nu>
Co-authored-by: Victor Santos Oliveira <56417388+Vittuu@users.noreply.github.com>
Co-authored-by: cfaulkingham <colin.faulkingham@gmail.com>
2026-10-24 14:21:59 +00:00
selune b7b25f9e2b Render workspace: first basic delivery to main (#6050)
* Normalize UI container violation paths across platforms (#5599)

Report repository-relative paths with forward slashes so diagnostics and the existing nested-package regression assertions agree on Windows and Unix. Keep the container detection rules unchanged.

Validation: official ui-containers gate and all seven focused xtask tests passed; rustfmt and diff checks passed. Only xtask/dependencies were compiled; no application build or heavy tests.

(cherry picked from commit 44b58d8e10)

Scoped render-branch prerequisite from #4507, coordinated under #5493. No unrelated translation/kernel changes are included. Fresh Windows fmt, seven ui_containers regressions and the production container gate pass on render base 3a6153d18f. Full CI is reserved for final render-to-main acceptance under the maintainer policy.

Co-authored-by: Victor Santos Oliveira <56417388+Vittuu@users.noreply.github.com>

* Preserve translated source-label aliases (#5619)

Render integration prerequisite for #5493. Fixes #5618. Retain stable-ID fallback and translated legacy captions; include controlled regression coverage.

* Persist validated engineering cards independently of mass and optics (#1060)

Render constituent under #5493. Fixes #1048. One pending material-engineering-cards descriptor, immutable released migrations, independent assigned density and source conditions. Nine persistence and ten physical-card regressions plus the fresh quick gate pass. Additional native follow-up is reported separately; no full-stack acceptance is implied.

* Render: progressive GPU path-tracing executor (#2067)

Land the unchanged contributor GPU foundation into render under the maintainer policy in #5493. Fresh quick gate 39993339605 passed. Original author and co-author attribution retained; no workflow file changes. Final render-to-main CI and jyviko sign-off remain separate.

Co-Authored-By: Christian Svensson <blue@cmd.nu>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Render: asynchronous GPU setup and completed-image panel (#2780)

Integrate the original asynchronous setup, progress and completed-image UI after #2067. Refreshed source passes quick gate 40009901424. Original attribution retained. Final render-to-main full CI and jyviko sign-off remain separate.

Co-Authored-By: Christian Svensson <blue@cmd.nu>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Render: studio lighting, clear coat and glass on GPU (#2191)

Integrate the original studio renderer after the GPU executor and async UI foundations, under #5493. Fresh render-target quick run 40011408339 passes. Preserve attribution, bounded resource admission and documented glass/denoising limits. Historical native benchmarks retain their source pins. Full renderer-to-main CI and jyviko sign-off remain separate.

Co-Authored-By: Christian Svensson <blue@cmd.nu>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex <noreply@openai.com>

* GPU path tracing: use compatible Vulkan adapters (#3306)

Land the final Vulkan-only path-executor policy after #2191 under the #5493 constituent route. Fixes #3217. Fresh quick run 40012565783 passed. Preserve adapter/software policy and CPU fallback; no DXC dependency or workflow changes. Full native/hardware acceptance and final jyviko sign-off remain separate.

Co-Authored-By: Christian Svensson <blue@cmd.nu>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(material): initialize optional cards in upstream density regression

Fix the E0063 found by quick run 40015318093 while combining main #4785 with render #1060. The new upstream fixture explicitly has no engineering card. Keep every density/name/default-material assertion and production implementation unchanged.

* feat(render): expose independent anisotropic GGX core (#1694) (#1699)

Extract the original numerical source and analytic tests from published #1698 at 463a3e0d into its existing #1699 constituent. Keep current GPU/shared jobs, format and kernel untouched. Add scoped numerical-contract documentation. Full render library 158 passed, strict scoped Clippy and wasm pass, as do assets/layers/lists/format; source tree pinned in the acceptance record. No runtime consumer or final combined renderer acceptance is claimed.

* Sync current main into render, preserving release and viewport changes (#5705)

* Regenerate THIRD-PARTY-LICENSES.txt and docs/features.md [skip ci]

* Viewport: whole-body highlights reuse the model's own triangles (#4554) (#5109)

Hovering or selecting whole bodies -- a browser row, a History group, a body
under the pointer in body-pick mode -- built a CPU copy of every body's
triangles into the highlight scene. On a 40-body 4.3 MB STEP assembly that was
147 bodies, 70 MB of triangle copies and a 519 ms frame.

The model already has the same geometry on the GPU. The highlight now names it
and the renderer draws it again in the highlight colour:

- bodies drawn from a display plan: the model's evaluated plan chunks, with a
  hover/selection tint uniform (the chunks stay resident in the model scene);
- bodies drawn as placements of shared triangles: the same placements of the
  model's shared triangles, coloured per instance;
- bodies the model builds on the CPU: their vertex range in the model scene's
  block.

See-through and faded bodies keep the CPU feedback, which colours them; faces
and edges highlighted on their own are unchanged. The highlight looks the same
(verified side by side).

On the assembly the highlight build drops from 519 ms to 9 ms and no longer
copies any triangles; selecting the whole assembly takes 9-20 ms.

Co-authored-by: Claude Code <noreply@anthropic.com>

* Offer to stand up SolidWorks files instead of turning them silently; add Turn Up Axis to the component menu (#5655)

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>

* Sketch on a picked origin plane uses the plane shown after standing an import up (#5659)

* Sketch on a picked origin plane uses the plane shown, also in a moved component

After a STEP file opens its component stays the active one, and standing it up
turns that component a quarter about X. Starting a sketch on XY picked in the
viewport then sent `{"plane": "XY"}`, which names the active component's own
XY: upright after the stand-up. The sketch landed on the world XZ, Look At
turned to face Y and the extrude went along Y.

The viewport and browser only show the design's origin planes, so a picked
origin plane now goes to sketch.create written out as that world plane while
the active component is moved; sketch.create maps it into the component's
frame once. The flip toward the viewer uses the same plane.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Stand-up axes tests: stand the file up through the offer when one is waiting

#5655 offers to stand up SolidWorks files instead of turning them silently.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* gpu tests: give the two test frames the new hover and selection tints (#5698)

#5109 added Frame::hover_tint and selection_tint; two test frames that landed alongside it didn't set them, so solidcraft-gpu's tests stopped compiling on main.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Release 0.21.0-rc.2 (#5691)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jackson Oursland <jacob.oursland@gmail.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>

* GPU: preserve error scopes on cancelled preparation (#5722) (#5723)

* claim: #5722

* fix(gpu): unwind preparation error scopes on cancellation (#5722)

Move fallible CPU HDR packing before GPU scopes and drain the remaining scope stack in reverse order before propagating denoiser/resource allocation failures. Native regressions cover cancellation and allocation-error cleanup plus subsequent scope reuse. Exact candidate Vulkan-required GPU suite: 101 passed, 0 failed, 3 existing ignored; strict GPU all-target Clippy and repository assets/layers/lists/container checks pass. No driver exception, shader transport or test limit is suppressed.

* library: transport PBR and engineering cards without lossy copies (#1086)

* wip(library): reconcile exact rich-catalog replacement semantics (#1062)

Source-only scoped extraction from original 0f801962 / #1086 on the refreshed #1060 card prerequisite. Preserve current asynchronous picker guards and source-ID handles. Scalar v1 and rich-v2-without-PBR clear existing PBR; supplied rich PBR is retained. No new saved concept or duplicate card descriptor. Formatting and source review completed; compile/native acceptance is still pending.

* test(library): preserve explicit downgrade and fallible UI regressions (#1062)

Request scalar v1 explicitly in the card downgrade refusal regression; rich v2 remains the default lossless writer. Propagate fallible harness actions with ? in the shared clicked-catalog helper, preserving all assertions. No production code, limits or ignore changes. Fresh grouped IO/engine/UI tests, scoped strict Clippy and WASM pass on this exact tree.

* Render: defer path-host startup until first use (#5742) (#5743)

* claim: #5742

* fix(render): defer GPU path initialization until first use (#5742)

Retain the selected adapter factory during Design/safe-mode startup; instantiate once on first Render entry, including late viewport installation. Preserve the deferred/preparing/ready/failed state across document changes and retain selected fallback adapter policy. Exact tree passes 32 render-workspace and 1966 full UI tests (0 failures; 7 existing ignored), strict native all-target Clippy, wasm check and repository gates. Three wasm warnings remain in untouched upstream files. Fresh native desktop acceptance is separate and in progress; no whole-renderer/main sign-off implied.

* GPU: adapt dispatches and retire lost devices (#5754) (#5755)

* claim: #5754

* fix(gpu): adapt dispatch size and retire lost path devices (#5754)

Start with a 64-item quantum and adapt bounded work toward 20 ms from observed completion, including denoiser tiles. Never exceed 16384 work items; hardware-specific shader cost means this is not a universal TDR guarantee. Publish device loss/uncaptured failure/poll failure through live executor availability without reinitializing automatically. Source-pinned required-Vulkan GPU suite: 105 passed, zero failed, three existing ignores, including actual device.destroy retirement and session availability. Strict GPU/engine all-target Clippy passes. Broader engine and repository/WASM continuation remains separately recorded. No geometry baseline, CI rule or published format history change.

* fix(render): wrap complete backend diagnostics (#5714) (#5715)

Preserve full backend/job/decoded-image diagnostics in the existing shared panel without ellipsis. The painted-galley regression covers Floating/Compact and light/dark, complete text, wrapping and fixed panel width. Exact-tree Render UI 33/0, strict UI Clippy, WASM and repository gates pass. Real safe-mode window captures, camera restore, CPU export and normal exit are recorded; no backend behavior or unrelated UI policy changes.

* Sync pinned main into render (#5869)

* Viewport: show reference geometry through bodies (Preferences, off by default) (#2697)

A new Graphics preference, "Show reference geometry through bodies", draws
inactive sketch contours, origin and construction planes and their axes over
shaded bodies. It is off by default, so the viewport looks as before: reference
geometry is depth-tested and a section leaves it whole (#1229); Create Sketch
and dialogs picking a plane still draw the planes through the model.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* ui-egui tests: offscreen GPU tests start their devices one at a time (#3989) (#3990)

* claim: #3989

* ui-egui tests: offscreen GPU tests start their devices one at a time

Every offscreen test started a wgpu instance of its own, and starting one
makes the Vulkan loader scan its drivers. With several test threads two
tests doing that at once crashed the test process with SIGSEGV inside the
loader (loader_icd_scan). offscreen_device now holds a process-wide lock
while it starts the instance, adapter and device; each test keeps its own
device and the rendering still runs side by side.

* DXF: write and read a Bézier at its own degree (#5269) (#5281)

* claim: #5269

* DXF: write and read a Bézier at its own degree (#5269)

The DXF writer and reader held a spline's degree to 7, the value MAX_BEZIER_DEGREE had before
#1642 raised it to 9. A G4 blend (one Bézier of degree 9) was written as a degree-7 SPLINE over
its first 8 control points, so a flat pattern's cut-out stopped 4.444 mm short in the file.

- seg_shape takes its limit from MAX_BEZIER_DEGREE: a Bézier is written whole, at its own degree.
- read_dxf reads SPLINE degrees 1 to MAX_BEZIER_DEGREE, the degrees a sketch spline takes.
- Tests: every Bézier degree 4 to 9, a degree-9 round trip, the reader's degrees, hostile SPLINE
  degrees and too few control points, the G4 cut-out exported and read back with its areas, and a
  degree-9 SPLINE with 2 to 10 points inserted into a sketch.

Fixes #5269

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Renaming a plane keeps its projections, canvases and hidden state (#4353) (#4354)

* claim: #4353

* Renaming a plane keeps its projections, canvases and hidden state (#4353)

timeline.rename only renamed the feature in other features' references. A sketch's
projection of a construction plane, a canvas placed on it and the hidden list keep it
by name too, so they lost it: the sketch warned that the plane no longer exists, the
canvas left the view and a hidden plane showed again. Document::rename_feature_keys
renames it in sketch links and canvases, as rename_body_keys does for bodies; the
selection follows in the command, and the hidden list in Session::refresh_from, so a
hidden plane stays hidden through undo and redo too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Browser: add Rename and Delete to feature menus (#3839, #4343) (#4342)

* claim: #3839

* fix(ui): add Rename and Delete to browser feature menus

* Browser: a second click on a Document Settings entry closes its window (#4150) (#4152)

* claim: #4150

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Browser: a second click on a Document Settings entry closes its window (#4150)

Each entry under Document Settings only opened its window. A second click on
Units closed the menu on the press (the row is outside it) and opened it again
on the release; Precision left Preferences open; Parameters and Design
Properties came back as a fresh copy. Now a click on an entry whose window is
open closes it the way the window's own x does:
- Units: context_menu::toggle_for with the row as the opener, the fix #1522
  made for the workspace button's menu. A right-click still opens it at the
  pointer.
- Precision: prefs::close(app, true), the Cancel/x path that puts back what
  changed since the window opened (now pub(crate)).
- Parameters and Design Properties: the window is dropped, as its x does. A
  Parameters value typed but not applied yet is dropped with it, as today's
  second click already drops it.

Fixes #4150

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Preserve expression-positioned sketch fillet corners (#4097) (#4106)

* claim: #4097

* Sketch fillet keeps a corner placed by an expression (#4097)

Filleting a corner whose position comes from parameter expressions deleted
the point and its expressions, so the sketch gained two degrees of freedom
and later parameter changes distorted the outline. keep_corner now also
keeps fixed or expression-positioned points as virtual sharp corners.

* Fix feature editor picks after upstream sketch resizing (#4083) (#4084)

* claim: #4083

* Feature editors refill named edges and faces where they are now (#4083)

After an upstream sketch change, Fillet, Chamfer, Shell and Draft evaluated
correctly, but their editors refilled the originally clicked points and
could then fail with "no edge at ...". Resolve the stored names through the
document's naming helpers first, keep the resolved index, and map the pick
into the displayed body's frame; the point lookup stays as the fallback.

* ui-egui tests: clear the texture deltas of the frames they run (#4039)

Nine tests dropped an egui frame's output with its texture delta still
unapplied (the expression completion editor's frames, the slot preview's
three, the browser's feature rows). epaint asserts against that in debug
builds, so they failed under the dev profile and passed only where debug
assertions are off. They clear the delta as the other tests do; one more
hover test that dropped its output the same way does too.

* Frames: profile library, members along frame sketch lines, and the Frame Member dialog (#4003) (#4004)

* claim: #4003

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Frames: the profile library (#4003)

The first slice of frames and weldments: the cross-sections structural members
will be made of, each SolidCraft's own parametric definition built in code
(no vendor or standard size tables are shipped).

- solidcraft_doc::frame: bar, round bar, rectangular and round tube, angle,
  channel, I-beam and a generic T-slot extrusion as exact line/arc regions,
  with defaults worked out from the profile's size, refusals that name the
  dimension that does not fit, anchors and placement, and exact section
  properties (area, centroid, Ixx/Iyy/Ixy by Green's theorem).
- frame.profile.list, frame.profile.properties (with mass per metre for a
  density) and frame.profile.insert (draws a profile into a sketch, locked by
  default, at a point, anchor and angle).
- Tests against hand-computed values (rectangles, circles, composite
  rectangles by the parallel-axis theorem, fillet spandrels), hostile input,
  extrusion volume, undo; catalog entries in every locale; docs/frames.md;
  ROADMAP M15.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Frames: drop the built-in T-slot profile (#4003)

Aluminium extrusions and other vendor sections come from the user's own
sketch (drawn or inserted from the vendor's DXF), so the library keeps the
plain steel shapes only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Frames: members along frame sketch lines (#4003)

frame.member lays one body per straight line of a frame sketch (2D lines or
3D sketch lines), its cross-section square to the line from end to end:

- profile: a built-in profile, or profile_sketch, a sketch whose material
  (nested loops alternating, so two circles make a tube) is the section in
  its own x and y around its origin; profiles picks override it.
- anchor (origin, centre, centroid or a bounding-box corner or edge middle),
  angle (an angle expression, so a parameter), mirror.
- Orientation: the profile's y as near world up as the line allows, x
  square to both; a vertical member's x is world X (frame::member_axes).
- Members follow sketch and parameter edits, undo as one step, save and
  reopen (format: pending/frame-members.rs), and are named after the profile
  (Rect Tube 40x40x3, (2)…), numbering on past names already taken.

Tests against hand-computed volumes and bounding boxes (rectangle, 3D
tower lines, parameter edit, sketch tube profile and picks, anchor/angle/
mirror), refusals, undo/redo and reopen; walk fixtures and goldens; docs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Frames: fixes from the adversarial review (#4003)

- Default member lines are what was drawn: projected and other linked curves
  and 3D wires (a face sketch's edges) and lines of no length are left out.
- A profile sketch's material starts from its drawn profiles, as
  solid.extrude's "all" does, so a section sketched on a face works; the
  even-odd test uses outlines and boxes worked out once, capped at 256.
- Member names are pinned to their lines (`names`, by line id; `body_name`
  is the base): deleting or adding a line, or deleting another feature, no
  longer renames other members, and frame.member stores every name it gives.
  body.rename renames a member on its line (also for later member features);
  a copy whose body_names were set still names its members by position.
- Built-in profile dimensions are expressions (ProfileSpec): they follow
  parameters, bare numbers take the design's units, and they are feature
  inputs. frame.profile.properties/insert read them the same way.
- Default root/toe radii shrink to the room a thick section leaves; radii
  given are checked by name; a radius must be 0 or at least 0.01 mm; error
  messages report the wall or flange value given, not twice it.
- A line within 1e-6 rad of vertical orients as vertical, so rounding never
  turns a post a quarter turn.
- A 2D frame sketch in a differently placed component is mapped into the
  member's frame, and member features recompute when placements change.
- Caps before lookups for a design's `lines`; a line listed twice is
  refused; a 3D wire that runs back on itself is not a line.

Regression tests for each, from the review's scenarios.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Frames: the Frame Member dialog on the ribbon (#4003)

- Frame Member (MODEL > Create) opens a dialog: the frame sketch's lines
  picked in the view (or All Lines), a built-in profile and its dimensions or
  a section picked in another sketch, anchor, angle, mirror and name, with a
  live preview. Edit Feature reopens it filled in.
- Lines picked in the view keep their sketch (a SKETCH_LINES input picks
  ExternalSketchCurve): line ids are each sketch's own, so two 3D sketches
  both have a `w2`, and the members went on the newer sketch's lines.
- timeline.redefine takes frame.member. Members keep their names through an
  edit unless it gives them a new body_name; frame.member takes `names`.
- A member whose section is a whole profile sketch keeps it when edited
  (the dialog shows the sketch instead of picking every region, which would
  fill a tube's hole).
- An original frame_member icon, open for review, on the ribbon, timeline
  and browser.
- The angle profile is listed as L-Angle, and web and flange as Web/Flange
  thickness, so they translate apart from the existing Angle, Web and Flange;
  the dialog's labels are in en, ja and zh. en-GB no longer repeats the frame
  command labels it spells the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Rectangular and circular pattern: copies that miss the body are reported (#3042) (#3919)

* claim: #3042

* Warn when cut pattern copies remove no material

A rectangular or circular pattern whose copies miss the body succeeded silently and those copies did nothing. The pattern now reports how many of its cut copies removed no material, and keeps the copies that land.

* Delete in a rolled-back history warns about the features after the marker (#3471) (#3698)

* claim: #3471

* Delete in a rolled-back history warns about the features after the marker (#3471)

With the timeline rolled back, the checks a delete runs first (the
confirmation's "these features will fail", timeline.dependents and
selection.delete's would_fail) compared models evaluated only up to the
marker. Features after it, broken by the delete, were not listed, so their
error marks turned up only once the timeline moved to the end.

newly_broken_in_history evaluates both sides over the whole history when
either has a marker (on copies; the model and document are unchanged) and is
used by the delete's report and by timeline.dependents.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* macOS: Fusion navigation preset does not support Shift + two-finger orbit (#3446)

* Macos: keep touchpad/key modifiers true to fusion

* fix: remove obsolete orbit snap reset from trackpad navigation

Upstream removed ViewportState::orbit_snap. The new trackpad orbit path retained one reference, causing E0609 in the PR merge build.

Validation: all seven trackpad tests pass; cargo check -p solidcraft-ui-egui --all-targets passes on the exact failing CI merge 66a2791b with this fix applied; rustfmt and diff checks pass.

Co-Authored-By: GitHub Copilot <noreply@github.com>

* fix: retain Fusion trackpad help in refactored preferences

Preserve upstream's new preferences pages and apply the translated Fusion trackpad tooltip in prefs/navigation.rs.

Validation: cargo build -p solidcraft passed. Tests and full CI were not run, as requested.

Co-Authored-By: GitHub Copilot <noreply@github.com>

* style: format Fusion trackpad preferences tooltip

Apply rustfmt's required wrapping for the relocated tooltip. No behavior changes.

Validation: cargo fmt --all -- --check and git diff --check passed.

Co-Authored-By: GitHub Copilot <noreply@github.com>

---------

Co-authored-by: GitHub Copilot <noreply@github.com>

* 2-point circle takes a typed diameter (#3278) (#3335)

* claim: #3278

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* 2-point circle takes a typed diameter (#3278)

After its first point the 2-point circle tool showed no value box, so the
circle could only be sized with the cursor. It now shows a Diameter box,
like the centre circle: the typed value puts the second point that far
along the cursor's direction, and Enter draws the circle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Phanoob <phanoob@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Reorder design tabs by dragging them (#3187) (#3188)

* claim: #3187

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Reorder design tabs by dragging them (#3187)

A tab dragged along the strip follows the pointer while the others make
room, and lands where it is released; Escape, or releasing down the
window, leaves the order as it was. With the custom title bar a drag that
starts mostly up or down (or on the only tab) still moves the window.
The control channel's ui.documents gains a move action (from, to).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Phanoob <phanoob@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Renaming a body keeps the evaluated timeline, off the window thread (#2467) (#2468)

* Renaming a body that later features change evaluates nothing (#2467)

Model::rename_body now keeps the results when later features change the
body (a pattern, a chamfer, a join). It still evaluates when a body of
that name comes and goes or isn't the same body throughout, when a later
body's name is worked out from it (X (2)), or when a message quotes it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Rename box: rename off the window thread, and locked bodies follow a rename (#2467)

The rename box commits body.rename through run_heavy, so a rename that
still evaluates later features (one names the body in its targets)
computes beside the window instead of freezing it. Locked bodies now
follow a body.rename from anywhere (the rename box, the control channel,
scripts), not only the rename box.

---------

Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Toolbar: a constraint dragged out of the small box becomes a full button (#5254) (#5456)

* claim: #5254

* Toolbar: a constraint dragged out of the small box becomes a full button (#5254)

The Constrain panel's small box of constraint icons was a fixed list, so
dragging one onto the full-size buttons saved a new order but it was still
drawn in the box. The toolbar settings now remember, per tab, which box
commands were moved out; those are drawn as full buttons, the box closes
up, and dropping one back on a box icon returns it. Reset Toolbar restores
the default box, and settings saved before this load unchanged.

* Sketch offset keeps its source lines free to turn (#4464) (#4466)

* claim: #4464

* Sketch offset keeps its source lines free to turn (#4464)

An offset adds a Parallel between each source line and its offset line.
Since offsets became associative, the offset's points are held during a
solve, so each Parallel pinned its source line to the offset line's
current direction: a closed chain lost its turning freedom (8 degrees of
freedom became 6), and Horizontal or an angle edit on a source line was
refused as conflicting.

The solver and the constructive reading now leave out a Parallel that
ties an offset line to the source it follows while the association lives;
the association already keeps them parallel. After Break the stored
Parallel drives again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Flat Pattern view draws perforations (#4440) (#4441)

* claim: #4440

* Flat Pattern view: draw perforation holes (#4440)

The flat view drew the outline, cut-outs, pockets and bend lines but not
perforation fields, though the flat-pattern data and the DXF export list
their holes. The view now reads the hole centres from the sheet through
SheetBody::perforation_holes, caches them per model state and body, and
draws them as circles in the cut-out style. Holes outside the viewport or
smaller than one pixel are skipped, and the cache is capped at one million
holes.

* "Don't Save" looks like a button in the Save changes prompt (#4380) (#4400)

* claim: #4380

* "Don't Save" looks like a button in the Save changes prompt (#4380)

The prompt's third action used the kit's low-emphasis text style, so it
didn't read as clickable and people thought Cancel was the only way out.
Add an opt-in secondary_left style to the confirm footer (a framed
secondary button from the theme tokens) and use it for Don't Save.

* Save prompt test: clear each frame's texture delta once it is applied

egui 0.36 panics when a TexturesDelta with unapplied deltas is dropped;
the test kept only the last frame's output and dropped the earlier ones.

* 3D mouse test: clear the frame's texture changes, so it passes in debug builds (#4382)

each_frame_hands_the_view_its_motion_through_the_drivers_mapping dropped
the output of a frame it ran (`let _ = ctx.run_ui(...)`). epaint panics
in debug builds when a TexturesDelta is dropped with deltas left ("Dropped
TexturesDelta with 1 unapplied deltas"), so the test failed under a plain
`cargo test` on every platform while the ci profile passed. It now clears
them, as the other UI tests do since #60.

Co-authored-by: Tyler Chiriboga <18270301+Tyler-Chiriboga@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Spanish (es-ES): catch up with en.json (#4292)

Co-authored-by: carlymx <24634598+carlymx@users.noreply.github.com>

* Esc ends the navigation bar's Pan, Orbit and Zoom modes (#4226) (#4233)

* claim: #4226

* Esc ends the navigation bar's Pan, Orbit and Zoom modes (#4226)

The nav bar toggled a mode on, but nothing cleared it on Esc, so a left drag
kept panning, orbiting or zooming. Esc now ends an active nav mode first;
the next Esc reaches the tool or dialog as before.

* Keep long Select Other labels inside the popup (#4209) (#4225)

* claim: #4209

* fix(ui): bound Select Other labels and expose full names (#4209)

* Deleting an inserted file removes its components, placements and joints (#4100) (#4113)

* claim: #4100

* Deleting an inserted file removes its components, placements and joints (#4100)

A STEP or mesh file inserted into a design makes a component tree. Deleting
its feature left that tree behind: the components, their occurrences, the
rigid joints that hold them and the bodies' component assignments. A later
body that took an imported body's name was then placed in the stale component.

Document::remove_component_tree removes a component subtree and everything
that refers to it: the features made inside it, its occurrences, the joints
on them with their motion links, joint origins, contact set members, motion
study keys and exploded moves, body component and offset entries, standard
part and link records, and configuration columns. Deleting an inserted
file's feature and component.delete both use it; component.delete now also
removes joints left pointing at a deleted occurrence. The body transfer
refusal moves with it, unchanged.

* Only an inserted file's feature takes its component tree when deleted

Mark the feature a STEP, IGES or mesh insert makes (`inserted`), and remove
the component tree on delete only for those and for a linked design's
feature. Deleting a copied component's independent base deletes just that
feature, as before. Older designs get the mark as they open (pending format
change), except copied components' independent bases.

* Rigid joint saved with the wrong occurrence when two occurrences share a component (#3857) (#3939)

* claim: #3857

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Joint dialog: a body of a further instance picks that occurrence, not the component's first (#3857)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>

* Cancel Sketch: leave a sketch without keeping its changes (#3838) (#3938)

* claim: #3838

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBN24y2TcqRcm2zn8Gkj27

* Cancel Sketch: leave a sketch without keeping its changes (#3838)

sketch.cancel (Cancel Sketch, ribbon Exit group and the sketch's
right-click menu) puts the design back as it was when the open sketch
was created or opened for editing: a new sketch is removed with its
dimension parameters, an edited one returns to how it was. It is one
undo step, so Undo goes back into the sketch.

The session remembers the design and the undo history at the point a
sketch is entered. Cancel uses that only while the history still runs
through it (the same steps before, every step since taken in this
sketch); otherwise it refuses and points to Undo, so a stale entry can
never throw away later work.

The icon is the approved close glyph in the sketch accent (alias
sketch_cancel); ja and zh carry the new label.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBN24y2TcqRcm2zn8Gkj27

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* 3MF import: name a Bambu Studio project's bodies after its objects (#3858) (#3859)

* claim: #3858 (3MF import: names from a Bambu Studio project)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* 3MF import: name a Bambu Studio project's bodies after its objects (#3858)

Bambu Studio leaves the name off the root model's objects and keeps it in
Metadata/model_settings.config (<config><object id="N"><metadata key="name">),
so every object of a project came in as Mesh1, Mesh2, ... A build item whose
root object has no name of its own now takes the object's name from those
settings. A name in the model still comes first, and part names, other
settings and objects of other model parts are left alone.

The settings are read only when a body needs a name, within what the model
parts left of the 1 GB budget. Settings that can't be read (malformed, nested
too deeply, too large) never fail the import: the bodies keep the generic
names, with a warning saying why.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Tyler Chiriboga <18270301+Tyler-Chiriboga@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Viewport: right-click keeps a multi-selection of faces and edges (#3733) (#3788)

* claim: #3733

* Viewport: right-click keeps a multi-selection of faces and edges (#3733)

---------

Co-authored-by: AhmetV <344941595+ahmetv10@users.noreply.github.com>

* Parameter import and image pickers offer their own file types (#3216) (#3314)

* claim: #3216

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Parameter import and canvas image pickers offer their file types (#3216)

Both opened the picker with no file type of their own, so it listed only
designs, CAD files and meshes (and All files). macOS lets you pick only
the types a filter names: CSV parameters, and PNG or JPEG images for a
canvas, were greyed out. They now ask for csv/json and png/jpg/jpeg,
which the picker lists first (#1801).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Phanoob <phanoob@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Return to the originating tab after finishing a sketch (#3250) (#3264)

* claim: #3250

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Return to the originating toolbar tab after finishing a sketch (#3250)

A sketch started from SHEET METAL or PLASTIC always ended on the SOLID
tab. Remember the tab that was active when the sketch began
(UiState::sketch_return_tab, not saved) and go back to it when the
sketch ends. Falls back to SOLID when that is not one of the normal
toolbar tabs. A sketch started from SOLID behaves as before.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Test: no one-element loop in the return-tab test (clippy single_element_loop)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>

* STEP import: let a cancelled import release the import slot (#3171) (#3189)

* claim: #3171

* Release cancelled file import slots (#3171)

* Dragging a part along its joint no longer highlights what's under the pointer (#5137) (#5140)

* claim: #5137

* Dragging a part along its joint no longer highlights what's under the pointer (#5137)

The viewport's hover pick was skipped during a box selection and camera
moves, but not while a jointed part was dragged, so faces and edges under
the pointer lit up on every frame of the drag. The pick now waits until
the joint drag ends.

* Browser: a body picked in the viewport is opened to and scrolled to (#3005) (#3081)

* claim: #3005

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Browser: a body picked in the viewport is shown in the Browser (#3005)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Merge main; viewport::select takes a toggle flag now

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Phanoob <phanoob@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Line tool snaps a diameter through the circle centre (#4326) (#4327)

A line drawn across a circle only snapped its end onto the curve, so a
diameter could miss the centre at any angle. Near the centre the line now
locks onto the radial line: on the rim it stays on the circle and the centre
is kept on the line, and on the centre point the end lands there.

* Flatpak: find the host's fonts for CJK text and the font list (#5124) (#5127)

* claim: #5124

* Flatpak: find the host's fonts for CJK text and the font list (#5124)

Inside the Flatpak sandbox the host's fonts are mounted at /run/host/fonts
and /run/host/user-fonts, which no font search looked at. Japanese and
Chinese text fell back to boxes, and the font list missed the host's
families.

- Search both folders for the CJK and Chinese fallbacks and for the
  installed-font list.
- The CJK fallback reads SOLIDCRAFT_FONT_DIRS (a path list, as the font
  list already does), with CRAFT_FONTS_DIR kept as a fallback.

* Crash reports: keep component.activate in the journal (#5079) (#5121)

* claim: #5079

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Crash journal keeps component.activate (#5079)

Activating a component changes only the session's active component and
occurrence path, not the design, so the journal dropped the command and a
crash report's replay built later parts under the previously active
component. The journal now also keeps a command that changes either.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Control channel and MCP on by default, with a setting to turn it off (#5561)

The app opens its localhost control channel on port 27878 (or the next free
one) unless Preferences > Assistant turns it off; --control and
SOLIDCRAFT_CONTROL_PORT still win. A current.port discovery file lets
solidcraft-cli mcp find the running app with no arguments.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>

* Tests: moving an occurrence recomputes only the cuts and joins it touches (#4965) (#4982)

* claim: #4965

* Joins and cuts follow only the placements they depend on (#4965)

Tests that turning a joint between parts cuts no hole again, and that a
tool cutting another part cuts again when that part moves relative to it,
while an unrelated part moving keeps the result.

Booleans and Combine now hash only the placements of the bodies they take
as inputs, relative to the frame that evaluates them (#3397), so these
pin that behaviour for joins and cuts with named targets across parts.

* fix(ui): preview shortcut chords and wait for complete modifier input (#1810)

Keep physical modifier events out of the ordinary-key slot, preview held keys immediately, and confirm after release and a one-second settling interval. Preserve legacy control-channel modifier behavior while allowing explicit physical Control/Command input. Add regression coverage, a UI scenario, localized guidance and documentation.

Fixes #1809.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>

* Move: scope box selection to the active component (#4459) (#4527)

* claim: #4459

* fix: scope Move box selection to active component (#4459)

* Delete removes a selected construction axis (#4692) (#4695)

* claim: #4692

* Delete removes a selected construction axis (#4692)

Delete on a construction axis skipped it with "the origin axes can't be
deleted". It now deletes the construction axis feature, like construction
planes; the origin axes X, Y and Z are still refused.

* Agent chat: end the agent's process group where `kill` is busybox' (#4587) (#4588)

* claim: #4587

* Agent chat: end the agent's process group with the system call (#4587)

The group was signalled with a `kill` program: `kill -s TERM -- -<pgid>`.
That works with procps' `kill` and with BSD's, but busybox' `kill` takes no
`--`: it prints its usage, exits 1 and signals nothing. The agent's helpers
(the CLI an adapter wraps, the MCP servers it starts) then stay alive after
the agent is ended.

The group is signalled with the system call instead, so no `kill` program and
no shell are needed and the behaviour is the same everywhere. `nix` (MIT) is
already in the tree through zip, and only on Unix: the browser build does not
take it.

The two tests that cover this now pass where they failed before:
`agent_chat::process::tests::dropping_the_process_ends_its_group` and
`agent_chat::process::tests::stopping_the_agent_leaves_no_helpers_running`.

* Extrude: picking a face, vertex or parallel plane off the profile switches the extent to To Object (#2855)

* Extrude: picking a face, vertex or parallel plane off the profile turns the extent to To Object

With profiles picked, a face off their plane (or curved), a vertex off it, or a
construction plane parallel to it is taken as the To Object target instead of
another profile: the extent switches without the Extent Type choice, and the
distance shows how far the target is. Coplanar faces still join the profiles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Extrude: automatic To Object is a Preferences setting, off by default

Preferences > Design > "Extrude to a picked target" (`extrude_auto_to_object`, default
false) turns the automatic To Object on. Off, picks in the Extrude dialog only add
profiles and the distance is not synced to a target, as before this change.

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>

* Regenerate THIRD-PARTY-LICENSES.txt and docs/features.md [skip ci]

* FreeCAD/Rhino import: decode caps with room for detailed parts (#1783) (#3209)

* claim: #1783

* FreeCAD/Rhino import: decode caps with room for detailed parts (#1783)

cadmpeg decoded FreeCAD and Rhino files under its service profile, whose
million collection items and 2.56e8 work units a detailed part (a sprocket,
a threaded rod, a part with a few hundred faces) runs past: the import was
refused with "resource limit on CollectionItems: BudgetExceeded". Allow
8 million items and 2e9 work units, half of cadmpeg's desktop profile; the
input-size cap stays SolidCraft's STEP limit and every other cap stays at
the service profile's.

decoding_is_capped checks the two raised caps (and that they stay below the
desktop profile's) and that every other cap is still the service one.

Fixes #1783

* A spherical shell revolves into a solid with a void, and cuts and joins with hollow tools work (#451) (#4182)

* Assembly: ground the first part; drag free joint chains (#2804) (#2805)

* assembly(feat): ground first parts and drag free joint chains

Ground the first top-level part when it is created and let face dragging reposition free occurrences or any member of a floating joint chain in one undo step. Preserve exact repeated-placement frames, joint values, modifier selection and Escape cancellation. Keep the accumulated fixture, viewport layout and documentation integration fixes from the existing PR.

Signed-off-by: Karl Von Richter<karl@embeint.com>

* assembly(fix): update movable fixtures for first-part grounding

Accept the documented grounded override in component creation, explicitly free movement fixtures, and keep linked clipboard pastes movable even in an empty document. Cover first and reused linked pastes, default insertion grounding, and single-step undo in the existing link tests.

Signed-off-by: Karl Von Richter<karl@embeint.com>

* assembly(test): keep the motion-cost crank free

Unground the first component in the document-level joint-face measurement fixture so its motion samples can rotate after first-part grounding. Preserve the rotation and shared-measurement assertions that caught the Linux CI failure.

Signed-off-by: Karl Von Richter<karl@embeint.com>

* assembly(test): free the frame placement fixture

Create the frame-member placement fixture ungrounded before moving it above its root sketch. Keep its world-space member assertions while respecting first-part grounding in the frame tests added on main.

Signed-off-by: Karl Von Richter<karl@embeint.com>

---------

Signed-off-by: Karl Von Richter<karl@embeint.com>

* UI: integrate theme radii, borders, palettes and viewport refresh (#5045) (#5046)

* claim: #4922

* UI: let themes own corner radii

* UI: use four-point panel and window radii in SolidCraft 95

* UI: align the 95 radius assertion with four-point panels

* claim: #4953

* UI: let themes own structural and control borders

* UI: single Blueprint outlines and dotted secondary rules

* UI: separate container outlines and add theme-owned 95 bevels

* claim: #5007

* UI: apply theme palettes to icons, workspace tabs and Gruvbox settings

* claim: #5045

* Viewport: invalidate cached geometry colours when the theme palette changes

* UI: preserve legacy theme logo colours with the borderless mark

* Section Analysis: OK with no plane picked turns the section off (#2410) (#2411)

While a section is on, Section Analysis opened with no plane picked now
says "No plane picked: OK turns the section off." and OK runs
inspect.section {"clear": true}. With no section on, OK with no plane
still asks for a plane.

Fixes #2410.

* Regenerate THIRD-PARTY-LICENSES.txt and docs/features.md [skip ci]

* Close Change Parameters with Escape (#3372) (#3399)

* claim: #3372

* Fix Escape cancellation in Change Parameters

Consume Escape before parameter editors can commit pending text on focus loss, while preserving autocomplete and shortcut capture priority. Add 11 UI regressions covering draft cancellation, preview cleanup, document and history preservation, and reopening saved values.

* Preserve construction references when axes and points are renamed (#4419) (#4449)

* claim: #4419

* fix: preserve renamed construction references (#4419)

* Browser: route body picks into open command dialogs (#2560) (#4371)

* claim: #2560

* fix(ui): route Browser body picks into active dialogs

* Fix midpoint picks in sketch dimensions (#4872)

Retain MID snaps across both picks and materialize reusable midpoint
constraints for point-to-line and point-to-point dimensions. Let midpoint
pairs choose horizontal or vertical spans with pointer placement before
entering a value.

Cover projected model edges, overlapping geometry, helper reuse,
undo/cancel/reopen, and existing angular and line-circle workflows.

* Sketch slots: provide typed dimension inputs for every variant (#2423) (#4318)

* claim: #2423

* fix(sketch): add typed dimensions to every slot tool (#2423)

* Version 0.21.0 on main (released from rc.2's code) (#5774)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* docs(mcp): Windows setup for Claude Desktop (#3794)

* docs(mcp): Windows setup for Claude Desktop (paths, two servers, Store config file)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(mcp): say the Store build can keep the config elsewhere, not that the other path fails

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Regenerate THIRD-PARTY-LICENSES.txt and docs/features.md [skip ci]

* MCP: wait out long requests, honest batch outcomes, batch tool aliases (#2901)

Rebased onto main as one commit (was 120d26f7, df488f9a, db9d1073 plus
merges of main).

- A request the app reports as still running (#1618) is waited for on the
  same connection through `control.pending`; past the wait the error says it
  was not cancelled and will still apply (or only write its file), later calls
  are not sent behind it, and the new read-only `pending` tool returns its
  result once it lands.
- A failed batch says what really happened: `rolled_back` only when the
  design is back where the batch started (as #3288 / #3242 on main: the app's
  `rolled_back` is believed; for an older app, every completed step undone;
  only a rollback asked for is reported as one), plus `applied`, `not_run` and
  `failed.still_running`.
- A batch step may name a tool (set_parameter, list_commands,
  describe_command, execute, measure, inspect_design, undo, redo, export, save,
  open, new_design, crash_reports); screenshot, body_topology, batch and
  pending are refused before anything is sent.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* 3D mouse: driver choice, guided calibration, button mapping and a picture of the device (#1463) (#3825)

* Add a small test boat to steer around with the 3D mouse

A little hull in its own camera, drawn by the UI itself and not the CAD
viewport, so a 3D mouse can be tried out without opening a design. It
brings the model (generated by assets/models/make_test_boat.py) and the
tests for how the camera moves it.

Nothing uses it yet; Preferences picks it up with the rest of the 3D mouse
work.

* UI kit: keep a choice row from flipping with the scrollbar

A choice row picks segments or a dropdown by whether its labels fit. A page
scrollbar that comes and goes narrows the row by its own width, so the row
could flip back and forth between the two on a page that sits right at the
edge. It is now judged on the whole row and through a small helper,
fits_steadily, that only changes its answer once the miss is wider than the
scrollbar.

Also tests for that, for typed numbers in a slider's value box, and a
test hook to put up the issue-report input barrier by hand.

* 3D mouse: device model, guided calibration, buttons and a picture

The 3D mouse becomes a device we know something about instead of six raw
numbers.

The reader picks between spacenavd and the direct hidraw driver, falls back
from one to the other, and says so when it can't open the device. Guided
calibration asks whether the device is a spring cap or a trackball, adds a
touch step, and uses a settle filter so a ball let go of stops reading as
motion. Buttons get jobs (keys, held modifiers, commands), saved per
device, with defaults for the SpaceMouse Pro.

Preferences gets a picture of the device that moves as it does, next to the
test boat, and the same picture can sit in the 3D view at Left Viewport,
Right Viewport or Left Window, above the floating History card like the
legend and readouts.

These ride together because the crate's calibration types, the Preferences
page, the Linux reader and the button hook all use each other.

* 3D mouse: offer to calibrate an uncalibrated device

When a 3D mouse shows up that has not been calibrated, a card in the 3D
view offers to calibrate it, to swap drivers when the other one would work
better, to close, or to never ask again. It is made once per device, goes
away after 30 seconds in view, and gives way to a status or computing
toast instead of stacking on it.

The card is the shared toast, which now takes buttons: they group by
purpose, wrap inside the card at narrow widths, and do nothing while an
issue report holds CAD input. The picture of the device in the 3D view
keeps clear of the card.

* 3D mouse: translations and attribution

The 3D mouse strings (ui.3d_mouse_*, the calibration steps and the offer
card) in every shipped language, the Japanese catalog note, and the credit
for the test boat model in ATTRIBUTION.md.

This is last because the code before it only looks keys up: it builds and
runs without these, falling back to English until they land.

* 3D mouse: draw its preferences with the settings kit like the rest of Navigation

The 3D mouse section was a folded header with its own layout. It now
uses the same kit as the sections above it: a settings_section card,
settings_form rows with the shared label column, dividers between the
device, axes, picture and buttons groups, Text::Hint for the helper
lines, and settings_control_width for the dropdowns.

The section is open like every other one (the header still folds it),
so the device badge on the folded header is gone; the calibration row
already names the device. Every setting, string and key is unchanged.
Tests that unfolded the section no longer click it, and the hint lines
are looked up as hints instead of notes.

* 3D mouse: show each axis's live tilt in Preferences, like main's axis rows

Each view axis row is main's axis row again: a meter that follows the tilt of the motion driving it, with the dead zone marked, next to Invert, Speed and the "driven by" menu. The meter reads the calibrated travel when the device has a calibration and the raw reading over full scale when it does not, and asks for a frame whenever the reading changes. The helpers are back as they are on main, and the calibration state is a badge by the enable toggle.

* 3D mouse: line its preferences up with the rest of Navigation

- The two checkboxes sit side by side in the same two-column grid as Wheel and trackpad.
- Row labels stop repeating "3D mouse" and use sentence case: Calibration, Driver, Restore defaults, Reset view, Left viewport, Right viewport, Left window.
- Hints line up under the control column instead of the left edge.
- The six view axes are named Pan X, Pan Y, Zoom, Orbit X, Orbit Y and Roll so they fit the name column.
- Buttons are a divider and rows in a form like the other groups, without the bold sub-heading.
- The driver, dead zone, axes, picture and buttons sit behind a collapsed Advanced section, since most people never touch them.

* 3D mouse: tidy the Advanced test for clippy

* Timeline: suppressing a feature keeps later features' body names (#4302) (#4311)

* claim: #4302

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Timeline: suppressing a feature keeps later features' body names (#4302)

Bodies numbered by the counter (`BodyN`) take the next free number on every
recompute, so suppressing a feature that made one moved every later counted
body down one (`Body2` became `Body1`) and broke what named it, such as a
Move of `Body2`. timeline.suppress now stores the current names of later
features' counted bodies in their `body_names` first, as body.rename already
does for a renamed counted body (#1109). Engine only: no evaluation or
design-format change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Timeline: a body made while a feature is suppressed keeps its name (#4302)

Suppressing a feature freed its counted name (`Body1`), so a body made
meanwhile took it and became `Body3` once the feature was back. Suppress
now also stores the suppressed feature's own counted names, and the
evaluator adds the names stored on earlier suppressed features to the
state's `reserved_body_names` (the names a rename freed, #4957), which the
`BodyN` counter passes by. They are taken from the design before each
feature is computed and are part of the state digest, so a cached result
is only reused when they match.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* cadmpeg 0.6.3: FreeCAD and Rhino import in the browser too (#4482)

cadmpeg 0.6.3 is pure Rust (ruzstd instead of the zstd C library, zip 9), so
solidcraft-io depends on it on every target and the wasm build reads .FCStd
and .3dm files like the desktop app. The browser's file pickers offer them.

0.6.3 replaces DecodeOptions::container_only with a decode depth; ask for
Geometry, what its STEP writer reads. zip 8 leaves the tree, and with it the
three cargo-deny advisory exceptions kept for it.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Pattern dialogs: Quantity fields get up/down step arrows (#4260) (#4264)

A new kit control, `ui_kit::count_field` / `count_row`, is an expression field for a
whole-number count with stacked step arrows at its right edge. An arrow steps the
evaluated count by one within the field's limits: a plain number is replaced, an
expression keeps its parameters and gains or adjusts a trailing `+ n`. The text
changes as typing changes it, so the live preview follows.

Used by the Quantity rows of the sketch Rectangular (1-500, both directions together
at most 500) and Circular (2-500) patterns, and the solid Rectangular, Circular and
Path patterns (1-1000).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Regenerate THIRD-PARTY-LICENSES.txt and docs/features.md [skip ci]

* Join: say so when the joined piece doesn't touch the body (#4434) (#4436)

* claim: #4434

* Join: say so when the joined piece doesn't touch the body (#4434)

A Join (extrude, revolve, sweep, loft, pipe, coil, primitives, thicken,
boundary fill, surface network) whose material meets none of its targets
still joins it to the first target as a separate piece. The result stays
as it was; the feature now warns: "the joined piece doesn't touch `Body1`,
so `Body1` is now in 2 separate pieces (New Body makes it a body of its
own)". History marks it and the command reply carries it.

The check compares pieces (outer shells, not voids) of the bodies the
feature changed or absorbed before and after it, so it holds for the
batched multi-profile path too, stays quiet for a join that bridges
pieces or closes a cavity, and leaves Combine (which joins the bodies it
is given) alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Hole: snap to face and round-edge centres, drag placed holes, section in the dialog (#2171) (#2912)

Placing a Single hole put it exactly where the click landed, with nothing to line it up by and no
way to move it but taking it out and picking again; the dialog was a list of size fields.

- Preferences › Design › Create Hole has two settings, both off by default, so a new installation
  places holes as before:
  - Snap to face and edge centres (`hole_snaps`): the planar face under the cursor offers its
    middle and the centres of its round edges (a boss, a rounded corner, a hole already there) as
    marks; a pick near one, or on or beside such an edge, takes that centre and the mark is named.
    Sketch points on the face still come first; Alt or Ctrl held places it where clicked.
  - Move holes by clicking or dragging (`hole_moves`): each placed hole has a handle that drags it
    (snapping when snaps are on) and takes it out when clicked, and a pick elsewhere on its face
    moves it there. Edit Feature shows the handle too, so a hole can be moved later (#1047).
- The Hole dialog draws a section of the hole as its options make it, with each size in a field
  beside the dimension it sets; a hovered or focused field lights its dimension. The layout is a
  new kit element, `ui_kit::figure_rows` (a caller-painted figure beside captioned fields).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Hole: place a Single hole by distances from reference edges (#37) (#2931)

A Single hole can take up to two straight edges of its face as references,
each with the distance of the hole's centre from that edge's line, measured
in the face's plane. `solid.hole` takes them as `references: [{edge, distance}]`;
the edges are found again by their persistent names, else their points, so
the hole follows them when the part changes, and each distance is a
parameter. A bad reference (no edge there, a curved edge, one square to the
face, parallel edges, a negative distance, a centre off the face) is refused.

In the Hole dialog, Reference 1 and 2 follow the face, each with its Distance
row; the canvas shows each distance as a dimension from its edge to the hole,
with a box just past the edge to type it in. Picking an edge measures its
distance (and its box takes the keyboard), typing one moves the hole, and
moving the hole measures them again. Edit Feature picks the edges again with
their distances.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

---------

Signed-off-by: Karl Von Richter<karl@embeint.com>
Co-authored-by: Frank <58932224+plexi32@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: raykholo <raykholo@users.noreply.github.com>
Co-authored-by: CamWight <91156642+CamWight@users.noreply.github.com>
Co-authored-by: SS-EN <84559909+SS-EN@users.noreply.github.com>
Co-authored-by: Colin Faulkingham <colin.faulkingham@gmail.com>
Co-authored-by: Karl Von Richter <karl@von-richter.net>
Co-authored-by: HiroKenEz <59478073+HiroKenEz@users.noreply.github.com>
Co-authored-by: Peppe Bergqvist <p@bergqvi.st>
Co-authored-by: GitHub Copilot <noreply@github.com>
Co-authored-by: phanoob <lesur.Stephane@wanadoo.fr>
Co-authored-by: Phanoob <phanoob@gmail.com>
Co-authored-by: Kai Evans <nemshilov@gmail.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Tyler Chiriboga <tylerchiriboga.apps@gmail.com>
Co-authored-by: Tyler Chiriboga <18270301+Tyler-Chiriboga@users.noreply.github.com>
Co-authored-by: CaRLymx <carlymx@gmail.com>
Co-authored-by: carlymx <24634598+carlymx@users.noreply.github.com>
Co-authored-by: Loohi <78334654+Loohi@users.noreply.github.com>
Co-authored-by: leValdemar19 <valdemarnord@hotmail.com>
Co-authored-by: AhmetV <mihendiz10@gmail.com>
Co-authored-by: AhmetV <344941595+ahmetv10@users.noreply.github.com>
Co-authored-by: rlartot <94693724+rlartot@users.noreply.github.com>
Co-authored-by: Christian Svensson <blue@cmd.nu>
Co-authored-by: Thodoris Tsagkogeorgas <thodoristsagko@gmail.com>
Co-authored-by: ahmetalpcelik <80778880+ahmetalpcelik@users.noreply.github.com>
Co-authored-by: ealitt <ealitt@gmail.com>
Co-authored-by: Homero Thompson <vdirienzo@gmail.com>
Co-authored-by: Diogo Soares Rodrigues <dsrodrigues20@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Artem Sushchev <Suschev.Artem+github@gmail.com>
Co-authored-by: Nathan Dane <nathandane.dev@gmail.com>
Co-authored-by: Kai Evans <hi@kaievans.co>
Co-authored-by: Victor Santos Oliveira <56417388+Vittuu@users.noreply.github.com>
Co-authored-by: cronos <crono2250@users.noreply.github.com>
Co-authored-by: rbousigues88 <rbousigues@gmail.com>
Co-authored-by: The Nomad <nomad@positron3d.com>
Co-authored-by: Jackson Oursland <jacob.oursland@gmail.com>
Co-authored-by: Jacques <59315319+Jacquesvdberg92@users.noreply.github.com>

* feat(render): reconcile definition-local visual mapping (#1214) (#1223)

Retain the original mapping implementation, all eight analytic/hostile-input tests and mapping contract byte-for-byte from #1223. Reconcile only module registration and the current roadmap on render. Actual current render library 166/0; strict all-target render Clippy, wasm32 and repository gates pass. No persistence, sampler, new capability, kernel/vendor change or external code is added. Historical mixed-base full-CI failures are not relabelled.

* feat(render): add bounded linear film accumulation (#1696) (#1697)

Extract the independent FilmAccumulator from owned donor 3b892eab5e. Keep the existing job cancellation and caller-owned admission/work contracts. PNG resolution, primary camera coverage and progressive consumer wiring remain separate. Add analytic statistics, atomic refusal, allocation/range boundaries and real 4096/1048576-count tests. No kernel, document schema, workflow or capability change.

* Sync main into render and preserve GPU CLI help (#5976)

* CLI: --help on each subcommand prints its usage and exits 0 (#4538) (#4545)

* claim: #4538

* CLI: --help on each subcommand prints its usage and exits 0 (#4538)

Every command now answers `--help` and `-h` with its own usage on stdout and
exit 0, the way the top level has since #1828. Before, each subcommand
answered differently: `fuzz --help` started a fuzz run with the default seeds
and ran until it was stopped, `recipe --help` took the flag for a file name,
and `snapshot`, `render`, `oracle`, `run`, `eval`, `script` and `crashes`
answered with an error on stderr and exit 1.

The help is read before the command runs, so `--help` never starts work: no
fuzz run, no MCP server, no file read.

`fuzz` also refuses an argument it doesn't take, as `snapshot` already did: a
flag it doesn't know and a stray word are errors, not arguments to ignore
while a hundred seeds run.

The usage lives in one place now: each command's lines are a constant, and the
top-level usage is composed from them, so the two cannot drift apart. The
top-level usage is unchanged, byte for byte.

# Conflicts:
#	apps/solidcraft-cli/src/main.rs

* Renaming a parameter follows it into sheet-metal and plastic rules and canvases (#4420) (#4423)

* claim: #4420

* Renaming a parameter follows it into sheet-metal and plastic rules and canvases (#4420)

Document::rename_param rewrote other parameters, feature inputs, dimensions,
configurations and joints, but not the expressions of sheet metal and plastic rules or
the plane a canvas lies on, so after a rename the sheet metal and plastic features
using them failed with "unknown parameter" and the canvas left the view. It renames
them there now, and param_users lists them (a rule's field, a canvas's input), so a
parameter they use can't be deleted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Parameters: rules' features in drives and the graph; shared rule field lists (#4420)

A sheet metal or plastic rule that uses a parameter makes the sheet metal (or plastic)
features rebuild when it changes, so parameters.users lists them in drives and
parameters.graph links them. SheetRule::fields/fields_mut and PlasticRule::fields_mut
replace the hand-written copies of the rules' fields in params.rs, eval.rs and
sheet.manage_rules. Suggested by @CamWight.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fmt (#4420)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Mirror and pattern: a pattern or mirror feature as the source (#395) (#3998)

* claim: #395

* Mirror and pattern: a pattern or mirror feature as the source (#395)

A pattern or mirror of features refused another pattern or mirror as a
source ("Pat cannot be patterned or mirrored yet"). Such a source now
stands for what it copied: its own sources are replayed at each of its
transforms moved by each of ours, to any depth up to eight. Copies of
a feature picked directly keep the instance numbers they had, so
existing designs keep their face names; copies made through a nested
pattern or mirror are numbered after them, so one feature reached two
ways never names two copies alike. A pattern or mirror of bodies as
the source is still refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Expression completion: keep longer names visible when the typed word is a unit (#3663) (#3664)

* claim: #3663

* Expression completion: keep longer names listed when the typed word is a unit (#3663)

Typing `rad` into an expression box showed nothing because `rad` is the radians unit and
a word that is itself a known token closed the popup. The popup now stays open on such a
word. A complete name (`width` with `width_extra` defined) opens it unarmed: the longer
names are listed with no row selected, Enter submits what was typed and Tab moves on; Up
or Down picks a row, after which Enter or Tab complete it as before. A unit means
something only after a number, so a unit typed anywhere else (`rad` on the way to
`radius`) is a prefix like any other: the popup is armed and Enter completes `radius`
instead of submitting one radian.

A complete token is extended only by names that start with it (`in` lists `inner`, not
`rounding`), a typed unit offers no other units (`in` does not list `inch`), and a unit
right after a number (`2 in`, the form every dialog starts its sizes in) opens no popup at
all, so nothing sits over a dialog's OK button.

Fixes #3663

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Homero Thompson <vdirienzo@gmail.com>
Co-authored-by: SS-EN <84559909+SS-EN@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Martin Pålsson <martin.palsson@outlook.com>
Co-authored-by: Daniel <daniel.owsianski@gmail.com>

* Render: isolate matched GGX visible-normal sampling (#1654)

Independent numerical constituent of #5493. Part of #1654; the CPU reflection consumer remains #1698, so this does not close its image acceptance. Original sampler, matched PDF and analytic/hostile-input tests are preserved from the reviewed donor. 189 render tests, strict Clippy, wasm32 and repository gates pass; fresh quick run 40086624771 passes. No CI configuration, saved-data or kernel change.

* GPU: match index-matched Fresnel in path and viewport (#6036)

Fixes #6035. Preserve actual GPU/f64 material-oracle checks in both translations. Scoped Vulkan, render, Clippy, WASM and fresh quick checks pass. No CI configuration or gate changes.

* feat(render): expose bounded original appearance recipes (#1722)

* claim: resolve the main/render synchronization conflict for #6050

* Settle appearance panel scrolling before targeting Search (#6079)

* claim: #6076

* test(render): settle appearance scroll before resolving Search (#6076)

Expanded starter swatches moved under the scenario click while the existing scroll animation was active. Advance 20 deterministic harness frames before resolving the Search handle. No wall-clock sleep, timeout change, assertion deletion or production change. Original native reproduction fails at the empty-state assertion; all 124 scenarios now pass with one pre-existing ignored adhoc entry.

---------

Signed-off-by: Karl Von Richter<karl@embeint.com>
Co-authored-by: Victor Santos Oliveira <56417388+Vittuu@users.noreply.github.com>
Co-authored-by: Christian Svensson <blue@cmd.nu>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jackson Oursland <jacob.oursland@gmail.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Frank <58932224+plexi32@users.noreply.github.com>
Co-authored-by: raykholo <raykholo@users.noreply.github.com>
Co-authored-by: CamWight <91156642+CamWight@users.noreply.github.com>
Co-authored-by: SS-EN <84559909+SS-EN@users.noreply.github.com>
Co-authored-by: Colin Faulkingham <colin.faulkingham@gmail.com>
Co-authored-by: Karl Von Richter <karl@von-richter.net>
Co-authored-by: HiroKenEz <59478073+HiroKenEz@users.noreply.github.com>
Co-authored-by: Peppe Bergqvist <p@bergqvi.st>
Co-authored-by: GitHub Copilot <noreply@github.com>
Co-authored-by: phanoob <lesur.Stephane@wanadoo.fr>
Co-authored-by: Phanoob <phanoob@gmail.com>
Co-authored-by: Kai Evans <nemshilov@gmail.com>
Co-authored-by: Tyler Chiriboga <tylerchiriboga.apps@gmail.com>
Co-authored-by: Tyler Chiriboga <18270301+Tyler-Chiriboga@users.noreply.github.com>
Co-authored-by: CaRLymx <carlymx@gmail.com>
Co-authored-by: carlymx <24634598+carlymx@users.noreply.github.com>
Co-authored-by: Loohi <78334654+Loohi@users.noreply.github.com>
Co-authored-by: leValdemar19 <valdemarnord@hotmail.com>
Co-authored-by: AhmetV <mihendiz10@gmail.com>
Co-authored-by: AhmetV <344941595+ahmetv10@users.noreply.github.com>
Co-authored-by: rlartot <94693724+rlartot@users.noreply.github.com>
Co-authored-by: Thodoris Tsagkogeorgas <thodoristsagko@gmail.com>
Co-authored-by: ahmetalpcelik <80778880+ahmetalpcelik@users.noreply.github.com>
Co-authored-by: ealitt <ealitt@gmail.com>
Co-authored-by: Homero Thompson <vdirienzo@gmail.com>
Co-authored-by: Diogo Soares Rodrigues <dsrodrigues20@gmail.com>
Co-authored-by: Artem Sushchev <Suschev.Artem+github@gmail.com>
Co-authored-by: Nathan Dane <nathandane.dev@gmail.com>
Co-authored-by: Kai Evans <hi@kaievans.co>
Co-authored-by: cronos <crono2250@users.noreply.github.com>
Co-authored-by: rbousigues88 <rbousigues@gmail.com>
Co-authored-by: The Nomad <nomad@positron3d.com>
Co-authored-by: Jacques <59315319+Jacquesvdberg92@users.noreply.github.com>
Co-authored-by: Martin Pålsson <martin.palsson@outlook.com>
Co-authored-by: Daniel <daniel.owsianski@gmail.com>
2026-10-24 14:21:58 +00:00
fugazister a633a67e63 Preferences: choose the window system on Linux (Wayland or X11) (#3083) (#3084)
* claim: #3083

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* WIP: choose the window system on Linux in Preferences (#3083)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Preferences: a window system set over the control channel is saved for the next start again

The merge kept main's check in ui.prefs, which only looked at the anti-aliasing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Preferences: the window system section drops the restart badge its note already states

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 12:12:38 +00:00
Homero Thompson 5e0e7a4c6a CLI: --help on each subcommand prints its usage and exits 0 (#4538) (#4545)
* claim: #4538

* CLI: --help on each subcommand prints its usage and exits 0 (#4538)

Every command now answers `--help` and `-h` with its own usage on stdout and
exit 0, the way the top level has since #1828. Before, each subcommand
answered differently: `fuzz --help` started a fuzz run with the default seeds
and ran until it was stopped, `recipe --help` took the flag for a file name,
and `snapshot`, `render`, `oracle`, `run`, `eval`, `script` and `crashes`
answered with an error on stderr and exit 1.

The help is read before the command runs, so `--help` never starts work: no
fuzz run, no MCP server, no file read.

`fuzz` also refuses an argument it doesn't take, as `snapshot` already did: a
flag it doesn't know and a stray word are errors, not arguments to ignore
while a hundred seeds run.

The usage lives in one place now: each command's lines are a constant, and the
top-level usage is composed from them, so the two cannot drift apart. The
top-level usage is unchanged, byte for byte.

# Conflicts:
#	apps/solidcraft-cli/src/main.rs
2026-10-24 07:35:25 +00:00
Jackson Oursland 612d9d08dd cadmpeg 0.6.3: FreeCAD and Rhino import in the browser too (#4482)
cadmpeg 0.6.3 is pure Rust (ruzstd instead of the zstd C library, zip 9), so
solidcraft-io depends on it on every target and the wasm build reads .FCStd
and .3dm files like the desktop app. The browser's file pickers offer them.

0.6.3 replaces DecodeOptions::container_only with a decode depth; ask for
Geometry, what its STEP writer reads. zip 8 leaves the tree, and with it the
three cargo-deny advisory exceptions kept for it.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-24 03:47:00 +00:00
The Nomad 1f6a2d4daf 3D mouse: driver choice, guided calibration, button mapping and a picture of the device (#1463) (#3825)
* Add a small test boat to steer around with the 3D mouse

A little hull in its own camera, drawn by the UI itself and not the CAD
viewport, so a 3D mouse can be tried out without opening a design. It
brings the model (generated by assets/models/make_test_boat.py) and the
tests for how the camera moves it.

Nothing uses it yet; Preferences picks it up with the rest of the 3D mouse
work.

* UI kit: keep a choice row from flipping with the scrollbar

A choice row picks segments or a dropdown by whether its labels fit. A page
scrollbar that comes and goes narrows the row by its own width, so the row
could flip back and forth between the two on a page that sits right at the
edge. It is now judged on the whole row and through a small helper,
fits_steadily, that only changes its answer once the miss is wider than the
scrollbar.

Also tests for that, for typed numbers in a slider's value box, and a
test hook to put up the issue-report input barrier by hand.

* 3D mouse: device model, guided calibration, buttons and a picture

The 3D mouse becomes a device we know something about instead of six raw
numbers.

The reader picks between spacenavd and the direct hidraw driver, falls back
from one to the other, and says so when it can't open the device. Guided
calibration asks whether the device is a spring cap or a trackball, adds a
touch step, and uses a settle filter so a ball let go of stops reading as
motion. Buttons get jobs (keys, held modifiers, commands), saved per
device, with defaults for the SpaceMouse Pro.

Preferences gets a picture of the device that moves as it does, next to the
test boat, and the same picture can sit in the 3D view at Left Viewport,
Right Viewport or Left Window, above the floating History card like the
legend and readouts.

These ride together because the crate's calibration types, the Preferences
page, the Linux reader and the button hook all use each other.

* 3D mouse: offer to calibrate an uncalibrated device

When a 3D mouse shows up that has not been calibrated, a card in the 3D
view offers to calibrate it, to swap drivers when the other one would work
better, to close, or to never ask again. It is made once per device, goes
away after 30 seconds in view, and gives way to a status or computing
toast instead of stacking on it.

The card is the shared toast, which now takes buttons: they group by
purpose, wrap inside the card at narrow widths, and do nothing while an
issue report holds CAD input. The picture of the device in the 3D view
keeps clear of the card.

* 3D mouse: translations and attribution

The 3D mouse strings (ui.3d_mouse_*, the calibration steps and the offer
card) in every shipped language, the Japanese catalog note, and the credit
for the test boat model in ATTRIBUTION.md.

This is last because the code before it only looks keys up: it builds and
runs without these, falling back to English until they land.

* 3D mouse: draw its preferences with the settings kit like the rest of Navigation

The 3D mouse section was a folded header with its own layout. It now
uses the same kit as the sections above it: a settings_section card,
settings_form rows with the shared label column, dividers between the
device, axes, picture and buttons groups, Text::Hint for the helper
lines, and settings_control_width for the dropdowns.

The section is open like every other one (the header still folds it),
so the device badge on the folded header is gone; the calibration row
already names the device. Every setting, string and key is unchanged.
Tests that unfolded the section no longer click it, and the hint lines
are looked up as hints instead of notes.

* 3D mouse: show each axis's live tilt in Preferences, like main's axis rows

Each view axis row is main's axis row again: a meter that follows the tilt of the motion driving it, with the dead zone marked, next to Invert, Speed and the "driven by" menu. The meter reads the calibrated travel when the device has a calibration and the raw reading over full scale when it does not, and asks for a frame whenever the reading changes. The helpers are back as they are on main, and the calibration state is a badge by the enable toggle.

* 3D mouse: line its preferences up with the rest of Navigation

- The two checkboxes sit side by side in the same two-column grid as Wheel and trackpad.
- Row labels stop repeating "3D mouse" and use sentence case: Calibration, Driver, Restore defaults, Reset view, Left viewport, Right viewport, Left window.
- Hints line up under the control column instead of the left edge.
- The six view axes are named Pan X, Pan Y, Zoom, Orbit X, Orbit Y and Roll so they fit the name column.
- Buttons are a divider and rows in a form like the other groups, without the bold sub-heading.
- The driver, dead zone, axes, picture and buttons sit behind a collapsed Advanced section, since most people never touch them.

* 3D mouse: tidy the Advanced test for clippy
2026-10-24 03:34:21 +00:00
SS-EN 6c6ffe4802 MCP: wait out long requests, honest batch outcomes, batch tool aliases (#2901)
Rebased onto main as one commit (was 120d26f7, df488f9a, db9d1073 plus
merges of main).

- A request the app reports as still running (#1618) is waited for on the
  same connection through `control.pending`; past the wait the error says it
  was not cancelled and will still apply (or only write its file), later calls
  are not sent behind it, and the new read-only `pending` tool returns its
  result once it lands.
- A failed batch says what really happened: `rolled_back` only when the
  design is back where the batch started (as #3288 / #3242 on main: the app's
  `rolled_back` is believed; for an older app, every completed step undone;
  only a rollback asked for is reported as one), plus `applied`, `not_run` and
  `failed.still_running`.
- A batch step may name a tool (set_parameter, list_commands,
  describe_command, execute, measure, inspect_design, undo, redo, export, save,
  open, new_design, crash_reports); screenshot, body_topology, batch and
  pending are refused before anything is sent.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 02:41:41 +00:00
Karl Von Richter 0dffb206b1 Assembly: ground the first part; drag free joint chains (#2804) (#2805)
* assembly(feat): ground first parts and drag free joint chains

Ground the first top-level part when it is created and let face dragging reposition free occurrences or any member of a floating joint chain in one undo step. Preserve exact repeated-placement frames, joint values, modifier selection and Escape cancellation. Keep the accumulated fixture, viewport layout and documentation integration fixes from the existing PR.

Signed-off-by: Karl Von Richter<karl@embeint.com>

* assembly(fix): update movable fixtures for first-part grounding

Accept the documented grounded override in component creation, explicitly free movement fixtures, and keep linked clipboard pastes movable even in an empty document. Cover first and reused linked pastes, default insertion grounding, and single-step undo in the existing link tests.

Signed-off-by: Karl Von Richter<karl@embeint.com>

* assembly(test): keep the motion-cost crank free

Unground the first component in the document-level joint-face measurement fixture so its motion samples can rotate after first-part grounding. Preserve the rotation and shared-measurement assertions that caught the Linux CI failure.

Signed-off-by: Karl Von Richter<karl@embeint.com>

* assembly(test): free the frame placement fixture

Create the frame-member placement fixture ungrounded before moving it above its root sketch. Keep its world-space member assertions while respecting first-part grounding in the frame tests added on main.

Signed-off-by: Karl Von Richter<karl@embeint.com>

---------

Signed-off-by: Karl Von Richter<karl@embeint.com>
2026-10-23 23:15:34 +00:00