Skip to content

Releases: denislov/fingerprint-browser

FpBrowser 0.6.0

Choose a tag to compare

@github-actions github-actions released this 17 Oct 09:17

Added

  • A started profile shows what it is (Runtime/Window): a profile now opens a
    local page first, served by this program on a loopback port for as long as the
    profile runs. It says which profile the session is - the name, the core's
    version and major, the proxy's name, the ports, when it started, and any
    fingerprint switch this core cannot honour - and it answers where one request
    leaves from: one request through that profile's engine to the configured
    endpoint, the same reading the Proxies page's Test takes, reported as an
    address or as the class of fault that stopped it. That is the engine's exit
    address and the page says so; whether the browser itself was pointed at the
    engine is still the window's Verify question, and a profile with no proxy is
    told that nothing was asked rather than shown a number it did not earn.

    The page talks to nothing but this program: every number reaches it over
    loopback, and the policy it is served under (default-src 'none' with
    connect-src 'self') is what keeps that true. It is read-only - no endpoint
    changes anything, none takes a parameter, and a request that names another
    Host is refused, so no other page in the browser can read it and none can
    make the machine ask about an address of its choosing. Nothing the request
    carried is echoed back and the proxy appears only as its name.

    The page holds the port it was reserved rather than releasing it, which is the
    one place in this program with no release-to-bind window: the browser is sent
    to an address something is already answering on. It ends with the session - a
    stop, a crash and a reclaim all drop it - and a session adopted from a previous
    run has none, because that run's page died with it.

    Identity page on the Settings page turns it off and on; it is on unless the
    reader says otherwise, and turning it off is enough to leave it out of the next
    launch. See identity page.

    The key is written only in the off state: while the page is on (the default),
    config.json has no status_page entry, so a configuration written by this
    build stays readable by a build that predates the switch. Once the page has
    been turned off, the file carries status_page, which an older build refuses
    as an unknown field - it reports the file as unreadable, falls back to
    defaults and refuses to save. To go back to an older build after using the
    switch, remove that line from config.json; the page is then on again.

  • A core newer than the newest measured one is named as unmeasured
    (Runtime/Window): CoreCapabilities::NEWEST_MEASURED_MAJOR is 148, and a
    profile whose core is above it is warned at start that its fingerprint
    switches were emitted from the measured rule without having been read back on
    that build. The Download Core dialog names the published versions above the
    line. fingerprint-chromium 150 is the first: on its Windows asset,
    getImageData on a canvas that has been drawn on with a seed hangs for a
    region of 4x4 pixels or more (1x1 and 2x2 return), so the fingerprint suite
    could not certify it; the Windows 148 control passes the same suite. The
    evidence and the reproduction are in
    fingerprint-matrix.md.

  • A failure matrix (Docs): one page maps the failures each subsystem is
    built to survive to the behavior that keeps data, processes and inputs safe,
    the test that proves it, and the opt-in acceptance runs that need a real
    browser, engine or network. See
    failure-matrix.md.

Changed

  • The minimum Rust version is declared (Build): the workspace's Cargo.toml
    carries rust-version = "1.98.1", inherited by every crate - the same
    compiler rust-toolchain.toml pins and the gate and releases use. An older
    compiler is refused by cargo with a clear message instead of failing
    somewhere inside a dependency; crates/app/src/version.rs fails the gate if
    the two declarations drift.

  • The architecture page points at the references instead of restating them
    (Docs): docs/architecture.md keeps the positioning, the architecture diagram,
    the dependency direction and the principles; the design prose that duplicated
    the subsystem references became links, and the reference pages gained the
    rationale that previously lived only on this page (the appearance palette
    rules, the profile filter, the Xray inbound, the argument order, and more).

  • The sidebar folds instead of jumping (Window): the rail's width is
    sampled from a spring rather than taken from the layout, so collapsing and
    opening it is something the reader watches happen. The spring is the design
    system's own (Theme::motion, the same place this window's colours come from),
    and it carries velocity: a toggle pressed twice redirects the rail from where
    the first press got to instead of restarting it. A settled sidebar asks for no
    frames at all, and a reader whose system asks for less motion gets the fold
    instantly - the same switch that the component library's dialogs and
    notifications already answer to.

Removed

  • The implementation plan is drained (Docs): docs/implementation-plan.md
    is deleted. The facts that existed only there moved into the docs that own
    them - exit modes, releases, UI shell, settings, diagnostics, backup and
    restore, proxies and Xray, traffic verification - and its one open item,
    reproducing remote upstream forwarding on Windows, is tracked as
    issue #5.

Fixed

  • A stranger's port can no longer pass for the engine (Runtime): the
    SOCKS port is reserved and released just before Xray is spawned, so
    another process can hold it in between; readiness accepted any listener
    there, which let an engine that never bound pass on the strength of a
    stranger's socket. The launch path now waits for the SOCKS5 greeting the
    inbound is built to answer - the rule CDP readiness already applies by
    reading /json/version instead of accepting any listener. What readiness
    proves is otherwise unchanged: not that traffic flows, which remains the
    diagnostic's question.

FpBrowser 0.5.0

Choose a tag to compare

@github-actions github-actions released this 13 Oct 02:03

Changed

  • The database journals ahead of the file (Storage): writes - a saved
    profile, an imported row, a re-probed core - now commit in WAL mode with
    synchronous = NORMAL instead of creating, syncing and deleting a rollback
    journal around each one. What the trade costs is named where it is made: after
    a power loss the last few commits can be gone, where the default would have
    kept them; the database itself stays consistent. The two sidecar files this
    adds (app.db-wal, app.db-shm) are listed in --diagnostics, and a
    filesystem that cannot support WAL keeps the old mode. The profile table also
    gains indexes on its core and proxy columns, which the delete paths and the
    "in use" lookups scan.

  • A first frame that reads less and waits less (Storage/Window): starting no
    longer reads every table twice, every frame queries only the page on screen
    rather than every page's rows, and the activity log keeps its file open
    instead of re-opening it per line. A machine with several registered browser
    cores no longer waits for each core's --version in turn before the window
    opens - the probes run together, so the wait is the slowest core's rather than
    their sum.

  • A core is registered without stopping the window (Window): the Add Core
    form, an edit that re-points a core, and a browser a finished download
    unpacked all read the binary's --version before storing it - a process the
    window does not control, started on the thread that draws it. That read runs
    on a worker now, so a slow or hung binary no longer freezes the window: the
    form says it is checking, a second press waits for the answer instead of
    asking again, a refusal is written on the form where the path that was
    refused still is, and a save that worked closes the form once its answer has
    landed. The banner notices are unchanged, so a downloaded core is still
    announced, and refused, exactly like one added by hand.

  • The window redraws when something changed (Window): the tick used to
    redraw the whole window once a second whether or not anything had moved. The
    runtime rows are compared with what is on screen first now, and a frame is
    asked for only when the answer is that something is different. The one
    redraw the clock still owns belongs to the two places that read the clock
    themselves - a log line's age, and how long a running profile has been up.

  • The two downloads share one implementation (Window): the browser-core and
    Xray-engine downloads had their own copy each of the same machinery - the
    GitHub API request, the reading of the release list, the streaming fetch with
    its .part rename and progress reports, the download's state in the window,
    the progress row and the footer's two buttons. That machinery is one
    implementation now (releases.rs, its sentences in text/releases.rs, the
    dialogs' common pieces in ui/downloads.rs), and each feature keeps what it
    actually differs in: which assets a release offers, what a finished download
    becomes, and what each dialog says. Nothing a reader sees, downloads or finds
    on disk has changed.

Fixed

  • A database that will not open says why (Startup): an installation whose
    app.db cannot be opened - an unreadable file, a directory without
    permission, another program holding it - used to end in a panic message and
    nothing else. It now says what failed, on the terminal, in the configured
    language, writes the report --diagnostics writes beside it (the question it
    answers is "why will it not start"), and exits with code 1.

FpBrowser 0.4.0

Choose a tag to compare

@github-actions github-actions released this 04 Oct 15:04

Added

  • Use system proxy on the Settings page (Runtime): a machine that reaches the
    internet through a proxy of its own now gets both hops. With the switch on, the
    engine dials the profile's proxy through this machine's proxy instead of
    connecting to it directly, which is what a profile whose proxy is only
    reachable that way needs. The reading is taken from the process's environment
    first (all_proxy, then https_proxy, then http_proxy, either case), and
    then from the platform's own setting - GNOME through gsettings, macOS through
    scutil --proxy, Windows through reg query - and turning the switch on
    reports, once, as a toast, which of the three answers this machine gave: the
    address that was found, that a proxy is chosen by a script and cannot be read,
    or that nothing was found. A machine with nothing to find is not an error; the
    switch stays on for a machine whose proxy appears later, and the reading is
    taken once per click rather than while the page is drawn, because on three of
    the four platforms it is a process to spawn.
  • The switch is baked in at start, like the Xray executable: the row says next
    start
    , and a config is built with the value the run began with, while the
    machine's proxy itself is read afresh for every launch, so changing the proxy
    on the machine needs no restart. A proxy test uses the same hop a launch would,
    so a test cannot pass where the launch fails. FP_BROWSER_SYSTEM_PROXY (on,
    off, and the words a switch is otherwise spelled with) decides the switch
    ahead of the config file for a machine whose proxy is stored somewhere this
    does not read - KDE's setting, for one - and while it is set a click on the
    switch is refused by name rather than storing a value that would move back
    under the reader's hand. What is deliberately not read is a proxy chosen by a
    script: a PAC file or WPAD is reported as itself, and a machine with nothing
    readable goes on dialling the profile's proxy directly, which is what the toast
    says when the switch is turned on. See
    through this machine's proxy for that
    and for the two consequences worth knowing first - an HTTP hop resolves the
    profile's host name rather than this machine doing it, and an HTTP hop cannot
    carry UDP, so a profile that tunnels UDP itself fails the connection instead of
    leaking around the proxy.

Changed

  • The program is renamed from fingerprint-browser / Fingerprint Browser to
    fp-browser / FpBrowser. The executable, the desktop entry and the
    Start-menu entry now carry the short name - the installed command is
    fp-browser - and the title bar, the tray tooltip, a diagnostics report and
    --version show FpBrowser. Nothing a user has on disk moves: the data
    directory is still FpBrowser, the configuration directory is still
    fp-browser, and the FP_BROWSER_* variables are unchanged, so no setting is
    re-entered and no file is migrated. A command line that named
    fingerprint-browser has to name fp-browser, and on Linux the Debian package
    is now fp-browser - a name of its own, so the old fingerprint-browser
    package is removed separately after the new one is installed. On Windows the
    installer's application id is unchanged, so an upgrade replaces the installed
    copy in place.

Fingerprint Browser 0.3.0

Choose a tag to compare

@github-actions github-actions released this 28 Sep 17:01

Added

  • The Browser Cores page can fetch a core instead of only being told where one
    is. Download Core opens a dialog listing what
    adryfish/fingerprint-chromium has published, newest first: one row per
    version, with the publication date and one button for each system that version
    was built for - the one this window runs on filled in and named as such, the
    other two beside it for a machine that is not this one. The asset offered is
    chosen by what the release actually carries rather than by what the tag says:
    the Linux tar.xz and the Windows portable .zip are preferred over the
    AppImage and the installer, because those are the two this program can unpack
    by itself.
  • Downloading is one click and runs on a worker: the file is streamed to a
    .part name under <data directory>/cores/<tag>, renamed once it is complete,
    unpacked beside it, and searched for the browser binary - by exact name, so
    chrome_sandbox, chrome-wrapper and chromedriver are not mistaken for it,
    and by the one directory a macOS bundle keeps its executable in. The binary is
    then registered through the same probe the Add Core form uses, so a core's
    major always comes from the version the binary reports and never from the
    release tag. Progress is reported on the row it belongs to, one download runs
    at a time, and a second click is refused with the name of the one already
    running rather than queued behind it.
  • A download that did not produce a core is not reported as one. An installer or
    a disk image is saved and the reader is told where it is and what to do with
    it; an archive with no browser this program recognises is named by the
    directory it was unpacked into; a binary that cannot be probed is named by its
    path as well as by the probe's own reason. The cores folder is reachable from
    the dialog, and is created when it does not exist yet.
  • The HTTP client is ureq - the one the runtime already uses - with tar and
    xz2 for the Linux archive and zip for the Windows one. Both unpackers
    refuse to write outside the directory they were given, which is why a release
    archive is unpacked with a library rather than by running the platform's tar.
  • Two opt-in acceptance tests cover what the fakes cannot:
    core_releases::tests::the_real_repository_answers_with_releases_this_can_read
    and core_releases::tests::a_real_release_downloads_and_unpacks_into_a_browser
    dial GitHub and move a real core. Both are ignored by default; run them with
    --ignored when the request shape, the parsing or the unpacking changes.
  • The Settings page can fetch Xray too, from a card under the Xray executable
    row - the same door the core page opens, and open the same way: always, because
    the reader decides which engine to run, and never without a click. It lists what
    XTLS/Xray-core
    has published, newest first, and offers the build for the machine this window
    is running on - only that one, because the architecture is in the archive's
    file name and the wrong archive is not a choice but a mistake, and the button
    says which platform and architecture it is about to fetch. A release that
    published nothing for this machine says so where the button would be.
  • What a download unpacks is the engine and upstream's licence, under
    <data directory>/xray-releases/<tag>, and nothing else: the geo data that
    travels in the same archive is left out, and no entry is allowed to write
    outside the directory it was given. The path is written into the Xray executable
    setting, which takes effect at the next start as that setting always does; when
    FP_BROWSER_XRAY_BIN is set the download says so instead of claiming an engine
    is installed, because the variable outranks the file it just wrote. Where the
    binary came from is recorded beside it - repository, release, file, URL, the
    archive's SHA-256 and upstream's .dgst URL for it - and the diagnostics report
    carries that record when there is one, and says nothing when the engine is one
    the reader supplied.

Fixed

  • A download that is still arriving is no longer reported as failed on a slow
    line. The HTTP client's body timeout is a budget for the whole body - its clock
    is not restarted by a read that arrives - and both downloaders had set it to two
    minutes believing it bounded a stalled connection. That made a hundred-megabyte
    core need a line better than a megabyte a second, and the twenty-megabyte engine
    archive better than 170 KB/s, which is the failure a reader on a slower line got
    instead of their browser. Both now use one budget, generous enough that only a
    transfer which has effectively stopped trips it.

Fingerprint Browser 0.2.0

Choose a tag to compare

@github-actions github-actions released this 24 Sep 03:36

Added

  • The window answers the keyboard. A profile row is a target now: Tab reaches it,
    Enter opens the panel that describes it, and Escape puts that panel away and
    gives the keyboard back to the row it came from - as does closing it with the
    pointer. Which row the keyboard is on is drawn as the ring the rest of the
    window's controls use, which is deliberately not the same thing as the row being
    chosen: a reader can see both at once, and tell them apart. Two keys belong to
    the window rather than to any control, and are bound without a key context so
    that a dialog, a menu or a dropdown - all of which bind the same key themselves -
    win over them: Ctrl+F puts the keyboard in the profile filter, switching to
    the page that has the field first, and Escape closes the top layer, which
    means the dialog when one is open and the details panel otherwise, and never the
    window. A window that opens with nothing focused answers Tab with nothing at all
    • keys are matched along the path from the focused element, and there is no path
    • so the first frame puts the keyboard on the view itself, which is not a tab
      stop, and Tab from there reaches the first control in the sidebar. A row's focus
      handle is kept per profile rather than made per frame: a handle made fresh each
      frame would drop the keyboard at every runtime poll, and the test for that is
      the_keyboard_stays_on_its_row_when_the_runtime_reports.
  • The Linux packaging also builds a Debian package.
    packaging/linux/package.sh writes fingerprint-browser_<version>_<arch>.deb
    beside the archive, out of the same staged files: the binary in /usr/bin, the
    menu entry, the icon in its two themes, and the documentation under
    /usr/share/doc/fingerprint-browser - the licence under policy's name for it,
    and the changelog compressed and named changelog.gz. Its dependencies are
    asked of dpkg rather than written down - they are the packages that carry the
    libraries the binary asks the loader for - so the list cannot go stale the next
    time a library moves between packages, and a machine without dpkg-deb says so
    before the build rather than after it. The release workflow installs the
    package with dpkg --install, runs the binary from /usr/bin, and removes it
    again.

Fixed

  • Fixed Windows CI gate warnings in crates/runtime/src/supervisor/ownership.rs
    where the tree parameter was unused under Windows compilation.

  • Fixed Windows extended UNC path prefix (\\?\) comparison mismatch during browser
    data recovery in crates/application/src/browser_data.rs.

  • Fixed Winsock non-blocking socket mode inheritance on accepted sockets in real Xray
    integration tests (crates/runtime/tests/xray_real.rs).

  • Enabled real fingerprint verification tests on Windows and adapted dynamic host
    platform recognition and font fallback measurements in
    crates/runtime/tests/fingerprint_real.rs.

  • Selected navigation items, choice chips, details tabs and log filters retain
    their selected colours on hover and press, using the button's selected visual
    state rather than only its accessibility toggle state.

  • Proxy and core lists now keep names and actions readable in narrow windows.
    Below 1364 logical pixels, secondary metadata moves under the resource name;
    wide windows retain the aligned columns. Layout regressions cover Chinese and
    English at 960, 1200, 1364 and 1440 pixels, including opening each row menu.

  • Sidebar navigation, settings groups, shared choice chips and the three close
    choices now use keyboard-accessible buttons. Appearance and language choices
    also share a wrapping row instead of stretching into separate full-width rows.

  • The About menu opens the diagnostics/about settings group. Ctrl+F no longer
    changes the underlying page or steals focus while a dialog is open.

  • A record the database refuses is now reported as what it actually is. Every
    constraint failure used to be reported as "id already exists", so a profile
    naming a core that was not stored was told its identifier was taken; the
    classification now reads SQLite's extended error code and separates a taken
    identifier, a missing reference (naming which one), a value the schema refuses,
    and a real database failure. The in-memory backend, which had no reference rules
    at all - so the application's own tests proved nothing about them - now refuses
    the same writes for the same reasons, and one contract test runs against both.

  • A profile whose browser could not be stopped is no longer reported as stopped.
    When a session adopted from a previous run could not be identified, read or
    terminated, the stop failure went to the log while the session record, the
    temporary Xray configuration and the profile's state were all cleared anyway - so
    a live browser was left running with nothing tracking it, nothing refusing to
    start it again, and no record for the next run to find. The stop now reports what
    happened; a failure keeps the record, the configuration and the session, leaves
    the profile running so it can be stopped again, and says so in the window.

  • Deleting a proxy a profile uses no longer quietly leaves that profile without a
    proxy. The database's foreign key cleared the reference (ON DELETE SET NULL)
    while the service refused the same deletion, so any path that did not go through
    the service - or two of them at once - silently changed a profile's network
    egress. The key is now restrictive, which is the same rule where nothing can go
    around it.

  • Restoring a configuration backup is now all or nothing. It used to delete every
    stored core, proxy and profile and then write the file's records one at a time,
    with the file only being validated as each record was written - so a file with an
    empty core name, a profile naming a core that was not in it, or a disk that
    filled up halfway left an installation holding neither the old configuration nor
    the new one. Restore now validates the whole file first (the rules a form is
    held to, plus repeated identifiers, dangling references, and a backup whose
    credentials were exported away, each refused with its own sentence) and then
    replaces everything in a single database transaction, so a failure at any point
    leaves the previous configuration exactly as it was.

  • A start, a browser-data copy and a configuration replacement can no longer
    overlap on the same profile. A start returns as soon as its command is queued,
    so the runtime's snapshot still said the profile was stopped while its browser
    was coming up - long enough to begin copying a profile that was already
    starting, or to begin a second copy that cleared a destination the first was
    still writing. Each operation now takes a lease on the profiles it will use, the
    worker that runs a copy owns that lease and gives it back when it returns, fails
    or panics, and replacing the configuration is refused while any profile is held.

  • A profile's start page must be a page. It is appended to the browser's command
    line as an argument of its own, so a value from an imported configuration that
    spelled a switch - --no-proxy-server, say - was Chromium's switch to obey, and
    the proxy the profile's fingerprint depends on was off. The domain validation,
    the service that stores a profile, the configuration import and the launch
    planner all refuse it now, and the error names what is wrong with it.

  • A browser-data copy can no longer destroy the data it is reading. Every path in
    a run is resolved and compared before the first write, so a backup directory
    inside the profile it copies, a restore whose destination contains its source, a
    destination reached through a symlink, and two profiles pointed at overlapping
    directories are all refused with nothing written.

  • A browser-data copy is built whole beside its destination and renamed onto it
    only once it is complete, so a copy that fails partway leaves the directory that
    was there exactly as it was. A run killed between the two renames is put back by
    the next start - or, for a destination inside a backup directory, by the next
    copy - and the profiles it was put back for are named in the window.

  • A desktop that will not take a tray icon no longer hides the window. "Keep
    running" put the window away whether or not the icon had been created, and on a
    desktop with no StatusNotifierItem host - a refusals path the code already
    logged and then ignored - the window went with nothing left to bring it back: a
    running program with no window, no icon and no way to reach it. The icon is now
    started through an injected starter (tray::TrayStarter), a refusal keeps the
    window on screen, and the banner names the desktop's own reason. The behaviour
    is pinned by a test that hands the window a starter which always fails.

  • A long banner no longer pushes its own dismiss button off the window. The
    message for three missing cores is a page of text, and with nothing in the row
    allowed to shrink it took the whole width and carried the button past the edge,
    which left a failure the reader could not put away. The sentence truncates with
    the whole of it on its tooltip, and the log keeps it in full.

  • The New Profile dialog is titled and confirmed in the interface's own language.
    It said New profile and Create in English whatever the language was set
    to, because the two strings were passed to the dialog as literals instead of
    coming from the message catalogue; the profile editor's platform fields were
    hardcoded the same way.

Changed

  • Writing a configuration backup, importing one, restoring one and re-reading a
    core's version no longer run on the thread that draws the window. All four are
    slow in the way a page switch is not - an export reads every core, proxy and
    profile, an import then writes every record in the file one at a time, a restore
    replaces the whole configuration in one transaction...
Read more

Fingerprint Browser 0.1.0

Choose a tag to compare

@github-actions github-actions released this 22 Sep 06:03

Added

  • Three ways to leave, and the question that picks between them. Closing the
    window can keep the program running in the background, leave the program while
    the browsers and proxy tunnels it started keep running, or stop everything -
    and the choice can be remembered, or changed on the Settings page. "In the
    background" means the window is minimized and a tray icon brings it back or
    leaves: a StatusNotifierItem over D-Bus on Linux (ksni, pure Rust, so no GTK
    or libdbus enters the packages) and the shell's notification area on Windows
    (tray-icon); the tray's Quit… always asks rather than obeying the
    remembered answer, so the tray cannot become a program nobody can leave.
  • A start adopts the sessions a previous run deliberately left running, so
    they are running profiles again rather than leftovers to clean up. What a crash
    left is reclaimed exactly as before, and a session record that cannot be marked
    is released and logged rather than stranded. A signal still means stop
    everything. See exit modes.
  • One window per data directory. A second copy cannot take the lock on the data
    directory, prints which process holds it, and exits 3 - instead of opening the
    same database and reading the first copy's running browsers as orphans to stop.
    The lock belongs to the operating system, so a crash cannot strand it, and
    --help, --version and --diagnostics are still answered while a copy runs.
    The lock file is one more row in the diagnostics report's file list.
  • --version prints the version, the commit the binary was built from and the
    platform; the same version is in the window's title bar, and the build, the
    platform and the data directory are the first line of the activity log.
  • --help, in the language the config file names, and refusals for arguments
    this program does not have.
  • --diagnostics writes one report about this installation for a bug report:
    the build, every effective setting with the source it came from, the state and
    permission bits of every file the installation keeps, and the end of the
    activity log. The same report is behind a button on the Settings page. It
    never opens the database and never reads browser data.
  • A program icon, generated from scripts/make-icon.py: a PNG, an ICO with
    seven sizes, and an SVG. On Windows it also goes into the executable's resource
    section, with the product name and version.
  • A Linux archive (packaging/linux/package.sh) with the binary, an installer
    for the current user, a menu entry and the documentation, and a Windows
    installer (packaging/windows/package.ps1) built with Inno Setup. Each ships
    with a checksum, and neither ships or downloads a browser or a proxy engine.
  • .github/workflows/release.yml, which builds both artifacts for a v* tag,
    refuses a tag or a changelog that disagrees with the version, and attaches the
    artifacts to the release. Nothing has been released yet.

Changed

  • The binary is named fingerprint-browser, which is what the help text and the
    documentation have been telling people to run.
  • The distributed build is built with [profile.release]: thin LTO, one codegen
    unit, and symbols kept so a crash can be read. Not panic = "abort", which
    would take the supervisor's child-process cleanup with it.
  • An empty profile list says how to get a browser core in the order a reader can
    act on it: the page inside the window first, then FP_BROWSER_CHROMIUM_BIN and
    a restart. It used to name only the second, while offering a button for the
    first. The whole first run is now written down in
    first run.

Fixed

  • A build made after a commit no longer reports the commit before it. The build
    script relied on cargo's default rule - run again when a file in this package
    changes - and a commit changes what git describe answers without touching any
    file, so a clean tree could produce a binary that named an older commit and
    called itself -dirty. It now watches the git files that answer is read from.
  • A configuration file left in the old per-platform directory is folded into the
    data directory at the next start, and the README no longer claims settings live
    outside the data directory - which stopped being true when the config file
    moved in beside the database.