Skip to content

Fulcrum v4.2.0

Choose a tag to compare

@oernster oernster released this 05 Aug 01:39
· 50 commits to main since this release

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.py decides and is held
    at 100% coverage by 49 new tests, installer_ops.py acts on Windows,
    installer_lifecycle.py composes 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 under installer/ may import from the fulcrum package, 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.py and generate_matrixed_enterprise.py were 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.md records
    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.py is 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
    orphaned why.html, model.html, tool.html and download.html. The
    site is hand-maintained and now says so, with nothing left to contradict
    it.