Repository navigation
Fulcrum v4.2.0
Release notes
IMPORTANT NOTE:
- macos dmg release asset removed since I forgot to notarise it (I didn't know that step was necessary after developer signing; the latest version has been notarised).
Two things run through this release. The Windows installer, which until now
sat outside every check the rest of the repository is held to, is now held to
all of them. And several places where the app quietly did the wrong thing
with your work are fixed: it reopens where you left it, it shows you where a
move landed, and it will no longer write over a saved organisation it cannot
read.
Experience
-
Dependency arrows land on the box they point at. An arrow head was
placed by backing off half a box width horizontally and half a box height
vertically at the same time, which is correct only when the approach is
exactly horizontal or exactly vertical. Every diagonal dependency
therefore had its head sitting somewhere off the target rather than on its
edge. Both maps now meet the border properly at any angle, and each run is
drawn from border to border rather than centre to centre, so no line is
painted across the inside of the box it leaves or the box it enters. A
dependency count above one is centred on its run instead of hanging off
the midpoint. -
The presentation's map no longer spills text out of its boxes. Every
node on the exported before-and-after map was drawn at one fixed width,
which was never checked against the text put inside it. A domain with
enough teams for a four-figure count ("1,076 teams, 177 contested teams")
needed half as much width again, so the line simply ran out past the
border. Nodes are now sized to their own longest line, sharing the widest
on the map so the grid stays a grid, and the map states the font it is
measured against rather than inheriting whatever the document supplies.
The one function producing a node's lines is now the one the sizing reads,
so a box cannot be measured from different text than it draws. -
Every button wears the same background. The icon buttons in the header
and the move-preview buttons beside each move declared a transparent
background of their own, so they read as bare glyphs sitting on the window
while every other button in the app sat on the raised grey. Those two
overrides are gone: there is now one rule deciding what a button looks like
at rest, and it governs the main window and every dialog alike. The
distinct darker fill on a disabled button is deliberate and stays, since
that is how unavailable is signalled alongside the red ring. -
The presentation is where you can see it, and it opens itself. Its
button sat out on the right-hand edge among the utilities, which is not
where the board's main output belongs; it now sits at the centre beside
the move record and the provenance mark. Creating a presentation still
writes it into your Downloads folder under a fresh name that never
overwrites an earlier one, but it now opens in your browser instead of
reporting a filename and leaving you to go and find it. The confirmation
box remains only for the case where nothing opens the file.
Fixes
-
The app reopens on the section you were working in. It always came
back at the whole organisation, however deep you had drilled, which on a
large structure meant finding your way back down on every launch. Two
things were wrong: the drilled section was never written to the autosave,
and the board discarded whatever focus a session arrived with before
showing it. Both are fixed, and the restored section is validated against
the organisation that came back, so a file naming a unit that no longer
exists or no longer holds teams opens at the whole organisation rather
than pointing at nothing. A file written by an earlier build simply has no
section recorded and opens as it always did. -
A move you play is now marked on the map. Playing from the move list
redrew the board in the new position and left no visible trace of what had
happened. At whole-org scope that is indistinguishable from nothing
happening at all: a repair inside one section moves the encoding by
hundredths, which no colour can show. The nodes a move acted on are now
ringed on both the complete picture and the drill map, and where the
picture summarises a section, the section holding those teams is ringed
instead. Taking a move back falls back to marking the one before it. The
ring already existed and was used by the move preview and the move record;
the board itself simply never asked for it. -
The uninstaller no longer offers to delete a directory the app has never
written. It looked for user state under LocalAppData while the
application saves its settings and its session to a directory in your home
folder. So "also remove my settings and saved games" removed nothing and
reported success, and a full uninstall left everything behind. The two now
agree, and a test pins them together by comparing the paths both sides
compute, since the installer is compiled separately and cannot import the
app to share a constant. -
A saved organisation that will not load is kept instead of overwritten.
The old behaviour was silent and total: any unreadable or incompatible file
produced a fresh session, and the first thing the fresh session did was
save itself over the file it could not read. One bad file and an
organisation with its whole move record was gone in seconds, with no
message anywhere. Such a file is now moved aside first, under a name that
cannot displace an earlier rescue, and if even that fails the store refuses
to write at all rather than destroy what it found. Either way the app now
says what happened and where the file went.
What's new
- The installer is gated like the application it installs. It was a
1,575-line PySide6 program with no tests, invisible to both the coverage
gate and the module-size gate, wearing a build script's exemption while
carrying real logic: registry writes, path resolution, payload extraction,
shortcut targets and the launcher resolution a onefile build needs. A
defect in any of those lands on a machine before Fulcrum ever starts. It
is now layered the way the app is:installer_logic.pydecides and is held
at 100% coverage by 49 new tests,installer_ops.pyacts on Windows,
installer_lifecycle.pycomposes the two and the Qt surface presents. No
installer behaviour changed. - The 400-line cap now covers the installer too. Every module under
installer/is inside it. The structural suite gained two invariants:
nothing underinstaller/may import from thefulcrumpackage, since
the two binaries are built and released separately; its decision layer may
not reach for the registry, a subprocess, the environment or Qt. - The analysis scripts are inside the suite.
calibrate.py,
sensitivity.pyandgenerate_matrixed_enterprise.pywere covered by
nothing, which mattered most for the calibrator: a silently broken one
reports success. The suite now fails if the sensitivity sweep loses a
qualitative conclusion, if a calibration case drifts outside its expected
band or if the committed matrixed-enterprise case stops matching what its
generator produces. - The matrixed-enterprise generator reads as a hierarchy. Its builder was
one 136-line function nested five deep; it is now an accumulator and four
named builders, at 350 lines rather than 391. The output is byte-identical
to the committed case, which the suite asserts. - The repository states its own outstanding debt.
TECH_DEBT.mdrecords
what is still open, what is deliberately left alone and what only looks
like debt, with the reasoning for each, and the README links to it. It
holds open items only: a resolved one is deleted rather than marked done,
because the history of a debt is not itself a debt. build_docs.pyis retired. It claimed in its own docstring to be the
authoritative source of the site while emitting a single page with no
navigation, so running it would have overwritten the published index and
orphanedwhy.html,model.html,tool.htmlanddownload.html. The
site is hand-maintained and now says so, with nothing left to contradict
it.