Skip to content

feat: Raven gets a window, and the audit that came with it - #5

Merged
MotherSphere merged 39 commits into
mainfrom
feature/raven-gui
Sep 6, 2026
Merged

feat: Raven gets a window, and the audit that came with it#5
MotherSphere merged 39 commits into
mainfrom
feature/raven-gui

Conversation

@MotherSphere

Copy link
Copy Markdown
Member

Raven becomes a workspace of two crates: the library with its command line,
unchanged, and raven-gui — a window over the same library. The CLI is still
the primary interface and nothing here requires the window.

The window

Four screens, built on colony-ui over iced 0.14: environments with Start and
Stop on the card, a per-environment detail (session, Direct3D, devices,
registry), bases with a real deployment progress bar read from wimlib's own
output, and diagnostics. Errors with an obvious action become that action —
raven env stop games becomes a button rather than a command to retype.

Deliberately absent, each deferred to its own spec: a file dialog, a settings
screen, the block-device picker, and the launch/loading window.

Why now: the corpus only grows through users, and users who will not type
commands never arrive. Seven programs tested is too few to know anything, and
no feature widens that number — an interface does.

The audit

The branch was then audited whole: twelve lenses over the repository, every
finding put to two independent refuters (one on the fact, one on whether it was
worth acting on), and a completeness critic. 93 findings, 66 actionable, each
verified again by hand before anything changed. Three were refuted by
experiment rather than argument — CR/LF injection into a projected .reg
(tested against the real Windows base: 1 894 keys, zero malformed lines) and
tar path traversal from a user-supplied DXVK archive (GNU tar refuses both
the .. and the symlink variant, and Raven checks the exit status).

A warm launch costs 2 ms of Raven's own overhead, down from 32. Validating
the recorded session anchor asked whether one process still held the
environment, and answered it by reading /proc/<pid>/mountinfo for every
process on the machine. Reading the one process's file gives the same answer:
measured seven times before and after, raven run <env> -- /bin/true with a
session up went 33 35 32 32 32 33 30 ms → 2 2 2 2 2 2 2 ms.

Fixed along the way, each with a test that fails without it:

  • The library re-runs a Raven binary for two things — holding a session open,
    and mounting for a registry import — and only the first had been taught to
    the window. Reproject therefore launched raven-gui exec …, which opened a
    second window and then reported success having projected nothing.
  • A program given by a relative path was looked for in the wrong place, in
    every form: setns resets the working directory to the namespace root, so
    anything still relative by then resolved against /.
  • detach deleted drive-letter mappings made by hand in winecfg, which
    attach goes out of its way not to touch, and renumbered physicaldriveN
    by a rank mountmgr does not use.
  • An interrupted base deploy left half a Windows wearing a finished base's
    name — listed, offered to env create, refused by the next deploy and
    removable only by hand. It now applies into a hidden directory and renames.
  • A Direct3D install that failed after its copies left libraries Raven could
    neither see nor remove, and a failed upgrade deleted the working build it was
    replacing.
  • A rules file that existed but could not be read was silently replaced by the
    wider default rules; env create's rollback used the removal that cannot
    delete a mounted environment.
  • The window's Start mounted without warming Wine, so a card read "running"
    while the first launch still paid the two seconds the button exists to avoid.

Documentation

Fifteen files carried claims that had stopped being true — the shadow set's
size, sessions that the pages still called unbuilt, root-level src/ paths
after the workspace split. Two were wrong in a way worth naming: the projection
page said COM registrations carry over while HKLM\Software\Classes sits on
the deny list with a comment explaining why, and it listed four tests of which
two do not exist.

Before merging

The window has never been opened. It compiles, its tests pass, and it has
been read closely — but nobody has looked at it. That check is five minutes in
front of the screen and it is the one thing between this and a release.

The architecture reserved crates/raven-gui behind a trigger that is now met.
Nothing about the raven crate changes: the same sources, the same tests, the
same seven suites passing. The workspace root carries the shared version and
the release profile so the two crates cannot drift apart.
colony-ui supplies the palette and the type scale. Every size goes through
Typography::sz from the first line, because the layout has to survive the
0.7225x-1.68x the org's two font preferences produce when multiplied, and
retrofitting that later means touching every call site.

No assets/fonts/ directory exists yet, so the window relies on
JetBrainsMono Nerd Font being installed system-wide rather than bundling it;
a later task can add the bundled font deliberately.

Two deviations from the task brief's code, both required to compile/pass
lint under iced 0.14 and this repo's -D warnings clippy config:
- Column::spacing needs impl Into<Pixels>, which is not implemented for
  u16, only f32/u32 - pass t.sz(8) directly instead of casting to u16.
- theme.rs's doc comment used a Markdown-list-shaped '- X - Y' construction
  that clippy::doc_lazy_continuation flags as a malformed list; reworded
  without changing its meaning.
Reading one environment opens /proc for every process on the machine, so no
library call runs where it could stutter the window. The model is plain data
with its own tests - status_line included, because '1 processes' is the kind
of thing a widget tree test would never catch.
Starting is the most frequent action and env start is what makes launches
instant, so it does not hide behind a detail view.
Raven's messages are written for a terminal and several end with a command to
run. In a window that is an admission of failure, so the ones with an obvious
action become a button. The rest keep their own words - the text is good, and
a paraphrase would lose the part that explains why.
Install needs a file and this batch has no file dialog, so the button says
which command takes one rather than pretending. That is a dependency decision
worth making deliberately, not while building a screen.
…gress

The only long operation in Raven, and minutes of silence in the CLI. The GUI
spawns the CLI and parses wimlib's own output - no library change was needed,
because .status() inherits the parent's stdio and wimlib does not suppress
progress when piped. Both format strings are pinned by a test against output
captured from a real apply.
A review of the deployment progress bar flagged Command::new("raven") as
undocumented: under cargo run it silently runs the system's installed CLI
rather than the workspace build, with no hint that happened. PATH is still
the right choice - the package installs the GUI and CLI together at the same
version - so this documents the reasoning on pump() rather than changing the
resolution strategy.
raven doctor says 'absent - Wine falls back to wineserver' rather than 'no',
and a green tick would throw away the only useful half of that.
Two binaries, two desktop entries, one version. They are separate entries on
purpose: raven.desktop carries the .exe association, and a double-clicked
program must run rather than open the manager.
Eleven of the thirteen buttons carried no `.style()`, which does not mean
"no style" - it means iced's built-in theme, so they rendered iced's blue
on colony-ui's ground. `btn_default`, `btn_hover` and `btn_pressed` were
unused in the whole crate: the palette carried three button colours that
nothing asked for.

One helper in `theme.rs` rather than a copy per call site, so the next
button added cannot quietly opt out of the palette by saying nothing. It
covers `Disabled` too - the Deploy button spends minutes in that state
during a deployment, and the built-in theme's disabled fill was as
foreign as its active one.

The environment name on a card keeps its own style: it is the card's
heading and only behaves like a button. Its colour is now stated rather
than left to `button::Style::default()`, whose text is black.
Two halves of the same seam, written by different tasks and reviewed
apart.

`Acted(Err)` set the offer and then returned `load::environments()`, and
`Environments(Ok)` cleared the offer. So the flagship behaviour the
design asks for - an error that arrives with a *Stop the session* button
- appeared for the length of a `/proc` scan and then erased itself. The
clear is now narrowed to what a load raised: a successful read says the
read works again, and says nothing about the action that failed. `Offer`
carries a `Kind` to tell the two apart, which is also what lets guidance
stop being painted as a failure - pressing "Install…" was rendering
instructions in `error` ink on `error_bg`.

And the design's Data flow section asks for a refresh on actions plus a
two-second poll while the environments screen is open. None of it was
built: `Message::Refresh` was declared and never constructed, and
`Go(Environments)` returned no task, so the home screen showed whatever
was true at launch until an action happened to reload it. The poll is
`iced::time::every`, subscribed only while that screen or a detail is
showing - reading every process on the machine for a screen nobody is
looking at is the waste the design refuses - and `loading_envs` drops a
tick that arrives while the last scan is still running rather than
queueing a second one behind it.

Navigating now clears the banner too. An error raised on one screen said
nothing about another and made it look broken.
…binds

iced defaults `application_id` to the empty string. A Wayland compositor
has no other way to tell which desktop file a running window came from -
it matches that id against a desktop file's basename - so nothing would
ever match `raven-gui.desktop`, and the `Icon=raven` that
docs/internals/packaging.md promises would never reach a taskbar. The
menu entry would carry the mark and the window it launched would show a
blank fallback, with nothing to say the two were one program.

assets/brand/README.md already writes down the agreement the icon needs;
this is a fourth term in it, so it is recorded there rather than left to
be rediscovered.
…ropped

Two faults in the same 40 lines, and both only bite on the one operation
that costs minutes.

stderr was inherited. A GUI started from an application menu has nowhere
to inherit it to, so the reason a deployment failed went to no terminal
at all and the window could only say "run the same command from a
terminal to see why" - the admission of failure the design's error
section forbids. It is piped now, drained on its own thread so a full
stderr pipe cannot deadlock a reader parked on stdout, and the last line
that says anything is what the banner shows. The child failing to spawn
at all now carries its reason too, instead of being a bare `false`.

And `DeployDone` was sent with `try_send` into a sixteen-slot channel,
like the progress updates around it. Losing a progress update is right -
they arrive faster than the window redraws and the next one is truer.
Losing the terminal message left `deploying` and `deploy_job` `Some` for
ever: a bar frozen at whatever percentage it reached, the fields and the
Deploy button disabled, and no cancel. It leaves the blocking worker as a
return value now and is awaited on the async side, so it waits for room
rather than giving up. The progress updates stay lossy on purpose.
The diagnostics test built a `Check` literal and then asserted against
the string it had just typed. No production code ran, so it would have
passed with `checks()` deleted. It calls `checks()` now: which
judgements pass differs between machines and cannot be asserted on, but
the shape can - four of them, each with a label, none reduced to the
bare verdict this screen exists to avoid.

The release workflow's build job was still called "Build raven-linux"
though it stages two binaries.

And `[profile.release]`'s comment still justified `lto = "fat"` with
"raven runs once per .exe launch". That is still true of `raven` and now
says nothing about `raven-gui`, which the same profile also builds. The
reasoning is rewritten to say what is actually true - the settings are
kept for the launcher and paid for in the window's compile time - as the
design asked. The values are unchanged.
…anifest

The workspace conversion moved the version into [workspace.package] and left
no [package] at the root. release-please's `rust` strategy assumes a Cargo
package lives at the package path and throws when it does not - so no release
pull request would have been opened at all, silently, and 0.2.0 would have
been the last version this project ever cut.

Found by the final whole-branch review, whose premise was that the strategy
would no-op; the fix investigation disproved that and found it throws, which
is worse and quieter.

The fix is not invented here. Eidos is the same shape - a virtual workspace
with a GUI crate, one inherited version, CI building with --locked - and
solved this already: `release-type: simple` naming the version line through
extra-files, plus a step that runs `cargo update --workspace` on the release
branch so the lock does not lag the manifest and break the --locked build on
merge. Both carry Eidos's comments, including the mistake it already made:
conditioning that step on `prs_created` silently skips it, and a step that
skips looks exactly like a step that had nothing to do.

One deliberate difference. Eidos names the release branch outright; this
matches by prefix, because the branch carried `--components--raven` under the
`rust` strategy and `simple` has no package name to derive a component from.
Guessing that suffix wrong would make the step a no-op in the one case it
exists for. More than one open release branch fails loudly rather than
picking one.
`Environment::stop()` signals every process holding the mount, SIGTERM then
SIGKILL, and does not ask who they are. The banner for `EnvironmentBusy` -
raised precisely when something other than Raven's own anchor holds the
environment, a game typically - offered that under the label "Stop the
session", in a window whose own vocabulary uses "session" for the anchor.
One click on a harmless-looking button ended the game.

`Action` now tells the two apart: `StopSession` for the anchor, `StopHolders`
for everything else, each with its own words, and the `EnvironmentBusy`
message says that stopping ends the programs it lists. Both still stop the
environment - that is the remedy in both cases - only the label's honesty
changed. The labels live on `Action` rather than in the view so the test
that pins the distinction can read them.

The card's own Stop is unchanged: a count cannot tell a game from Wine's
services, and listing the holders there is the spec's own item for the
Session section, left for after the visual check.
The sidebar highlighted an entry only when the screen equalled it, and
`Screen::Detail` equals none of the three, so opening a card - the most
common navigation in the window - turned the whole sidebar dark. Everywhere
else the detail is already Environments one level down: `Open` is
`Go(Detail)`, the back button returns there, the poll runs for both. The
sidebar now agrees.
To count environments per base, `load::bases` built the full environment
rows, and each of those reads the session, the holders, the Direct3D
manifests and the attachments - where reading the holders walks
`/proc/<pid>/mountinfo` for every process on the machine, and the session
read does it a second time. Measured at 60 to 70 ms per environment on a
435-process machine, all of it for a comparison of base ids the manifests
already hold.

`base_rows` now takes those ids and nothing else. `load::bases` no longer
touches `/proc`, and the note in `model.rs` that `load::environments` is
`env_rows`' only caller has become true.
Start, Stop, Remove, Detach and Reproject each spelled out the same wrapper
- open the environment on a blocking thread, one library call, the `Arc`
bridge for an error that cannot be `Clone` - five times over: 65 lines for
five calls. `act` says it once, so a sixth action cannot quietly do any part
of it differently. No behaviour changes; the five closures keep exactly the
calls the arms made.

`load`'s module doc claimed every library call went through it, which was
already untrue of those five arms. It now says what is true: every read
does, and actions take the same route through `act`.
The test kept a `.last()` under a clippy allow, with a comment citing "the
brief" as the reason - a build-time document no reader of the repository
can find. The code under test already uses `.next_back()`, the same
operation on a double-ended iterator. The allow was the only one in the
repository, and it guarded nothing worth keeping.
`Environment::ensure_session` starts the anchor as `current_exe()
session-anchor <name>` - whichever binary asked. Only the launcher answered
to that argument, so Start from the window spawned a second `raven-gui`,
which opened a window, said nothing on the pipe, and was killed thirty
seconds later with a banner blaming the kernel. The GUI's headline action
could never have succeeded, and the visual check would have met an
unexpected window first.

The anchor routine moves from the launcher into the library, beside the
code that spawns it, and both binaries call it. The window checks its
arguments before iced or tokio exist: the anchor's `unshare(CLONE_NEWUSER)`
is refused to a process that already has threads. One behaviour of the
routine changed in the move - a failure to record the session file now
leaves through stdout like every other failure, instead of through an
`anyhow` context on a stderr the anchor does not have.

The test runs the window binary with its environment cleared, so no
display exists for it to open anything on, and expects the anchor
protocol's answer on stdout. Before the fix it printed nothing and died
trying to create an event loop.
Two places decided whether an environment was held by Raven's own session
or by something else with `comm == "raven"`. That held while only the
launcher could start a session. The window can now, and its anchor is the
window's binary - `current_exe()` - so it is called `raven-gui`, and both
tests classified the window's own session as a foreign program: "still in
use by 1234 (raven-gui)", with the button that ends the user's programs
offered to end it.

A holder is now an anchor when its `/proc/<pid>/cmdline` says so - the
first argument is `session-anchor`, whichever binary it is - and `env
status` names it among the holders. The rule lives once, on `Holder`, and
a test pins it to both binary names and to the things that are not it.
The library re-runs a Raven binary twice: `session-anchor` to hold a session
open, and `exec` to mount for a registry import. Both ran `current_exe()`,
which is right for the launcher and wrong for the window. The anchor was
taught to the window last time; `exec` was not, so the Reproject button
launched `raven-gui exec ...`, which fell through to iced and opened a
second, unexplained window on the user's screen. Closing that window exits
zero, so the import would then report success having projected nothing.

Both re-invocations now run the `raven` beside us when there is one - the
package installs both binaries into /usr/bin and a cargo build puts both in
target/<profile>, so there always is - and the window answers a command line
instead of opening for it. An argument it does not know is refused on
stdout, the stream the library reads, so a fallback can only ever produce a
loud failure rather than a window and a false success.

The tests run the window binary with its environment cleared, so no display
exists to open anything on. Before this, both of them reached iced.
`session` answers "is the anchor I recorded still holding this environment?"
- a question about one pid - and answered it by reading
`/proc/<pid>/mountinfo` for every process on the machine. That scan was
measured at 37 ms on a 454-process machine, out of a 32 ms warm launch:
Raven's whole cost on the path every double-clicked program takes was the
scan, and `run` pays it before it can join anything.

Reading the one process's `mountinfo` gives the same answer - the scan's
only filter is that needle - so `holds` asks about one pid and `session`
uses it. Measured on the same machine and environment, `raven run games --
/bin/true` with a session up:

  before  33 35 32 32 32 33 30 ms
  after    2  2  2  2  2  2  2 ms

The check that made a recorded pid trustworthy is unchanged, and the
integration test now pins both halves of it against a real mount: the cheap
question must agree with the scan that found the holder, and a recorded pid
that holds nothing must still be refused.
`run` joins the session's mount namespace before it execs, and
`setns(CLONE_NEWNS)` resets the process's working directory to that
namespace's root. Everything still relative by then was therefore resolved
against `/`: `raven launch demo/game.exe` had wine look under `/demo`, and a
bare `game.exe` - whose directory was deliberately left unset - under `/`.
Only an absolute path worked, which is what a file manager passes and why
this survived a corpus.

`launch::target` settles both the program and its directory while the
caller's own directory is still ours. It is lexical rather than
`canonicalize`, because a program inside a live environment lives under a
mount point that exists only in the anchor's namespace - resolving through
the filesystem would fail for exactly the paths that do work.

A working directory that is not there is now skipped instead of fatal, and
tested where the answer is true - inside the namespace. Setting it anyway
made `exec` fail with an ENOENT that the message rendered as "could not run
wine", which sent the reader after a Wine that was installed and fine.
Two ways the same asymmetry showed. `attach` refuses a letter that already
carries any mapping, on the grounds that a `"d:"="cdrom"` made in winecfg is
not Raven's to overwrite - but `detach` accepted any entry at all and
deleted it, so `raven env detach d` silently destroyed a mapping Raven never
made. It now recognises its own entries by the form it writes, and says the
letter is not attached otherwise.

And the renumbering counted the wrong set. mountmgr numbers a drive by its
rank in the Drives section whether or not anything is wired to it, which is
what `attach` and `attachments` both count; `detach` renumbered by rank
among the entries that had a device behind them. With any floppy entry that
has no raw link - a half-finished attach, or one made by hand - every
survivor was then wired to a `physicaldriveN` naming a different disk from
the one `attachments` reports.
`base deploy` applied a Windows straight into the directory that names it,
and cleaned up only when `wimlib-imagex` exited non-zero. That is the one
failure it cannot count on: ten minutes over 143 886 files is long enough to
be interrupted, and an interruption usually kills Raven too, so the cleanup
never runs. What was left was half a Windows wearing a finished base's name
- listed by `base list`, offered to `env create`, refused by a second
`deploy` as already existing, and removable only by hand, since there is no
command that deletes a base.

It now applies into a hidden partial directory and renames only once the
image is whole and its junctions repointed. A rename within one directory is
atomic, so the name appears on a finished base or not at all, and a leftover
partial is neither listed nor in the way: the next deploy clears it.
Installing a runtime copies libraries into the upper layer, edits the
prefix's registry, and only then records what it did. Everything between
the first copy and that record could fail - and did, in a full disk or a
read-only prefix - leaving libraries Raven had put there and did not list.
`dxvk` reported nothing installed, `--remove` removed nothing while saying
the real Windows was uncovered, and the next install refused each of those
files as somebody else's, telling the user to move aside what Raven had
written itself.

The record now goes down as soon as the files exist and describes a
superset of them until the install narrows it at the end. `d3d` already
skips entries that are not files, so a superset costs nothing and always
leaves something that `--remove` can act on.

The rollback also stopped short of the previous install. An upgrade copies
over the libraries already there, and undoing it deleted them too, so a
failed upgrade uninstalled the build that was working. It now removes only
the files this call created.

Both are pinned by tests that fail without the change: one makes the
registry unwritable after the copies, the other fails an upgrade half way
and requires every library of the working build to survive it.
A rules file that exists and cannot be read was treated as one that is not
there, and "not there" means the defaults. The defaults are the *wider*
set, so a file someone had narrowed by hand - saved by an editor in Latin-1
with one accented character, or left unreadable - was replaced by rules that
project more of the base's registry than its author allowed, without a word.
Only a genuinely absent file falls back now; anything else is reported with
the path.

And `create`'s rollback used `remove_dir_all`, which this module documents
as unable to delete an environment that has ever been mounted: overlayfs
leaves a `work/work` with no permissions and the removal stops there, having
already deleted the upper layer. Creating an environment mounts it, so the
rollback was exactly the case that cannot work; it now uses the same
`remove_tree` that `destroy` does.
…ngs failed

Three things the window got wrong about telling the truth.

Its Diagnostics screen showed four judgements and left out the one `raven
doctor` leads with: who the kernel hands a double-clicked `.exe` to. Wine
registers a handler for the same magic, the kernel silently picks the most
recently registered, and losing that race looks exactly like Raven losing
its prefix - an hour, once. Someone using a window rather than a terminal is
precisely who would never find that out, so the screen now carries it.

A failed deployment showed the last line of the child's stderr, which for
Raven's own failures is the errno anyhow prints under the sentence: "No such
file or directory", true of a thousand failures and a description of none.
The sentence wins when there is one, and wimlib's own last-line diagnostics
still work as before.

And the banner's "Stop the session" could outlive the judgement behind its
words. It is raised when only Raven's anchor holds the environment and is
deliberately exempt from the poll that clears other banners, so a game
started in between would have been killed by a button that promised it would
not be. It asks again before acting, and the banner relabels itself to "Stop
everything using it" if the answer has changed.

`raven doctor`'s media playback line also lines up with the others now.
Becoming a workspace left `examples/` at the repository root, where it is
part of no package: `cargo run --example project_real_base` answered "no
example target named project_real_base in default-run packages" and the one
tool the project has for checking a projection against a real Windows had
been unbuildable since. It moves in beside the library it exercises, and
runs: 1894 keys in 82 ms against the deployed base, with all six of its
refusals holding.
…ease does

`pkgver()` printed a literal 0.1.0 that stayed put through two releases, so
a package built from a 0.3.0 tree called itself 0.1.0 and pacman read an
upgrade as a downgrade. It reads the workspace version now, from the line
release-please writes and both crates inherit.

CI ran `cargo test` and nothing locked, while the release workflow and the
PKGBUILD both build with `--locked`. A Cargo.lock that no longer agreed with
Cargo.toml therefore passed every check and failed at the release, which is
the expensive and public place to discover it. CI does that build too.

And `conf_path` was labelled "where the packaged registration belongs" while
naming `/etc/binfmt.d`, which is where an *admin* file goes and which takes
precedence over the `/usr/lib` file the package installs - the same
mechanism the Wine mask relies on. The path is right for what it is used for,
`raven binfmt` printing what to register when nothing has; only its name for
itself was wrong, in the source and in the shipped conf file's own header.
The branch forked one commit before main's release commit, so its version
files still said 0.2.0 while v0.3.0 was tagged and published. Merging it now
rather than at the end settles the one conflict here, where it can be built
and tested, instead of in the merge that lands the window.

Cargo.toml keeps the workspace layout and takes the released version -
release-please owns that line, and hand-writing it anywhere else is how the
two get out of step. The manifest and the changelog were never touched by
this branch and come across unchanged.
The card's Start mounted the environment and stopped there. Mounting is the
cheap half - hundredths of a second - and the seconds a first launch pays
are Wine's services coming up, which the CLI's `env start` deliberately
waits for and the window did not. So the card flipped to "running", read as
warm, and the next double-clicked program still paid the two seconds the
button was pressed to avoid. The warm-up moves into the library and both
callers use it.

A launcher that lost the start race waited the full thirty seconds even
when the winner had already given up: it watched only for a session, never
for the lock going away. The lock disappearing is the holder saying it is no
longer starting one - and it is the same signal that clears a lock left
behind by a launcher that was killed, which cost the next launch the same
thirty seconds.

An anchor's failure reached the user with its reason removed: `Error::Layer`
reads "preparing the layer failed at <path>" and keeps the errno as a source
that anyhow prints for the CLI and this path drops. The anchor's one line is
all a launcher ever shows, and "permission denied" is the difference between
a bug and a chmod.

And the commonest `--from` mistake said "does not look like a  build",
having been given an empty runtime name to print. It names the runtime it
wanted.
Fifteen files carried claims that had stopped being true. The audit that
found them checked each against the source; each correction below was
re-checked before it was written.

The shadow set says one entry, not two: `Windows/Fonts` was withdrawn on
correctness grounds and `reconcile` un-masks it at every session mount, so
every measurement taken under that mask - the 135 ms spawn, the 1.19x ratio,
the ~20 ms launch overhead - is labelled as history rather than quoted as
current.

Sessions exist, so the pages that said concurrent launches into one
environment are unsupported, refuse, or are an open item now say they join,
and the lifecycle table describes an anchor rather than a mount that dies
with the program.

The repository is a workspace, so the trees and paths name crates/raven/src
and crates/raven-gui/src, and the architecture page records that the first of
its four planned splits has happened.

Two claims were wrong in a way worth naming. The projection page said COM
registrations "genuinely carry over" while `HKLM\Software\Classes` is on the
deny list with a comment explaining why - the opposite of what the page said,
and the corpus's own `com` category depends on the refusal. And it listed
four tests, two of which do not exist; the list is now the eight that do. The
`\Device\` rewrite it promised is stated as known and unhandled, because it
is.

The packaging page said the registration is installed to /etc/binfmt.d; it
goes to /usr/lib/binfmt.d, and /etc is what a hand-registration uses to
override it. It also said both binary names report as `raven` - `rvn --help`
prints `Usage: rvn`, checked.

The README, the status page and the corpus record what the project has
gained: a second game rendering through DXVK on the GPU, vkd3d-proton
installed and still unproven, twenty leaf commands counted from --help, and
the window - named, with its screens listed and nothing said about how it
looks, since it has never been opened.

Today's measurement is in performance.md: Raven's own share of a warm launch,
32 ms down to 2.
The atomic deploy hid every directory beginning with a dot, which is more
than it needed: a base a user had named with a leading dot was listed before
that change and would have silently disappeared. The rule is now the exact
prefix Raven writes, so nothing that was visible stops being visible.
The re-check added to the stop button refused whenever something other than
Raven's own anchor held the environment - which is the ordinary state from
the moment Start warms Wine, since `wineserver` and the services hold the
mount too. So every Stop the user can see went dead: the card's, the detail
screen's, and the banner's own "Stop everything using it", which is offered
only for that case and therefore could never do the thing it named. Pressing
it re-raised its own banner, and the environment could only be released from
a terminal.

The judgement and the button are separated now. `Action::message` says which
stop each banner asks for, so the mapping is one function with a test on it,
and only the stop offered as "the session" - whose label promises nothing of
the user's is running, on a banner that outlives the poll - looks again
before acting.

Two fixes claimed by the previous commit were not in it: a Python script
aborted on its first substitution and the rest never ran. They are here. A
launcher waiting on the start lock now stops the moment the lock goes away
rather than sitting out the full thirty seconds after the holder has given
up, and an anchor's failure carries its cause, so "preparing the layer
failed at <path>" arrives with the errno that says whether it is a bug or a
chmod.

Also from the review: an interrupted Direct3D upgrade no longer reports
itself as the build it half-replaced; `raven doctor` names an interrupted
deploy, which is several gigabytes under a name `base list` hides on
purpose, and `Base::find` refuses that name; `unpack`'s doc comment is back
on `unpack`; CI asserts the lock through `cargo test --locked` rather than a
second fat-LTO release build; and two comments and two documentation
sentences that were wrong about their own subject are right.
`launch::target` asked for the caller's directory before deciding anything,
so a launch by absolute path - which is what a file manager passes - failed
outright when that directory had been deleted underneath the shell. An
absolute path is already what it means, so it is resolved without asking.
@MotherSphere
MotherSphere merged commit 958f38e into main Sep 6, 2026
4 checks passed
@MotherSphere
MotherSphere deleted the feature/raven-gui branch September 6, 2026 14:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant