You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Status. Early software from one maintainer. The hosted test suite passes on Ubuntu, macOS and Windows for Python 3.9 to 3.14 (all 18 jobs on this commit, run by hand on 2026-10-16). This release fixes a page error when the theme is switched while the arrival crow flies (seen in Chromium, Firefox and WebKit). The README lists what is still open under Known issues.
[0.10.1] - 2026-10-16
Fixed: a theme changed by the page address while the crow flies
On the TV's sea, changing #theme= at the moment a call's crow was flying to the call plate (the first seconds after a load
with calls waiting) threw "Cannot read properties of null" ten times a second until the flight's time ran out, in every
browser: the new theme has no crow, and the flight kept drawing one. The flight now ends when its sprite is gone.
Changed: Firefox and WebKit are checked headless, and the README says what was seen
The Support table's Firefox row said "expected to work"; it now says what a headless check found, and the Safari row says
which WebKit was run. A new Known issue: headless WebKit does not draw the page board's sea panel at 1440 px and wider. docs/device-checklist.md has the commands ("Firefox and WebKit headless check").
Checked on
Headless Chromium 156, Firefox 157 and WebKit 27.2 (Playwright 1.64.1, on Linux under WSL2, software rendered), one at a
time, at 1920 x 1080 and 390 x 844 (DPR 3; Firefox without phone emulation), on weatherdeck run --demo: the front door
(the sea on the TV, the page board on the phone), the Lists door, the Deferred and someone-else's-turn fold, Read all,
worker notes, Copy (the button says "Copied" in all three), the key and Esc, #theme=ship-deck and default, #preview=lost, late, host and empty, #night=1, #controls=1 and a button in it, the tv view, and the static
demo. No sideways scroll anywhere, every self-hosted face loaded, no failed request and, after the fix above, no page
error in any engine. The only console lines are WebKit's refusal of Playwright's own screenshot style by the content
security policy (none without a screenshot).
Differences from Chromium: WebKit leaves the page board's sea panel empty at 1920 and 1440 px (the lists' columns; 390 px
draws it), and Firefox's system face on Linux is a few pixels wider, so one call line on the default theme's sea ends in
an ellipsis where Chromium's fits. Everything else matched, step by step, in the screenshots.
Not checked: a real Safari, iPhone, TV, or a desktop Firefox on a real screen.
Also in the source repository, not in the release zip: the hosted test workflow's job now times out after 11 minutes.
Status. Early software from one maintainer. The hosted test suite (453 tests) passes on Ubuntu, macOS and Windows for Python 3.9 to 3.14 (all 18 jobs, run by hand on 2026-10-16 against this release). The README lists what is still open under Known issues.
[0.10.0] - 2026-10-16
Added: what is deferred or someone else's turn, beside "Needs you" (feed 1.12)
A folded section under "Needs you" on the page board (and on the Bridge, under DEFERRED and SOMEONE ELSE'S TURN): the viewer's dated
holds that still hold, each with its date and reason, and the cards that are someone else's turn (the supervisor's blocked notices, a
change held for the release train waiting on a verdict, a stale-summary notice), each with whose it is, since when and the verdict it waits
on. Neither group is ever counted in the waiting-on-you number, and a hold comes back as a call the moment its date passes.
Feed 1.12, optional fields only: an attention card's actor{role, name}, hold{bucket, until, reason, since} and verdict {name, state, as_of}; a next[] row's hold; a machine row's lanes{running, total}; a lane's branch; a collector's made_at and refresh_s. A feed without them reads exactly as before. The host sets aside (deferred or theirs) on cards and queued rows and leaves
them out of counts.waiting_on_you, counts.calls, the "since you last looked" call marks and a lane's needs_you.
Lanes per machine across every home: the host adds every source's own lanes count for a machine (machines[].lanes{running, total, homes, unreported, or_more}), so two homes on one machine are one count; the machine row says "lanes 3 running of 5 · 2 homes". A home silent past lost_after_s is
not added: it is unreported and the count says "or more".
A copy's made age: a collector with made_at is aged from its maker's own stamp, not from when it was read, so a summary that is
re-touched without being remade still grows old; past its limit it is a Problem and leads the trust line ("made 40 min ago and is
remade every 5 min: STALE"), and the Crow's nest says the made age while it is fine.
Changed: rows say what is recorded, in whole titles
Whole titles: a lane row shows the item's whole title, wrapped, instead of the source's short title; a place that must fit one line
(the TV log) still cuts it at a whole word.
Can't tell only when nothing is recorded: a lane whose state cannot be read but whose last word was done or paused says what its record
holds (its PR open: "Finished; waiting on upstream review", or "Paused; its PR is waiting on upstream review" after a paused last word;
merged; a hold; or "Branch only, no worker running"); a Can't tell row with a
recorded last word leads with that word and its age, and "can't be read" is said only when nothing it said is recorded.
FYI in plain words: each FYI says what kind of notice it is and whose it is ("whose it is: not reported" without a 1.12 actor).
Sources disagree, both sides: each line says what the one source says and what the other says or does not say about the same ask.
The demo carries synthetic 1.12 data: a deferred call and queued row, the supervisor's cards (one waiting on a verdict), an FYI with its
owner, a second home's lane on the primary machine, two Can't tell lanes whose record says more, and a fresh and a stale made copy.
Fixed: the log header's landed count is the bar's landed score
The sea view's log header could say "0 LANDED" under a bar that said "6 LANDED, LAST 24 H": the header counted only the listed lanes that had
finished, while the bar counts the work that landed in its window. The header now shows the bar's number, as the calm line already did; the phone,
whose bar has no score, adds the window ("6 LANDED IN 24 H").
Changed: README pictures, and the hosted test suite passes on Windows, macOS and Linux
README pictures: a TV-sized hero, a short list of what you see, a tour clip (with a still frame beside it) and a phone shot, all taken from the
demo, whose sources are invented. The README's test table says what the hosted runs were and were not (the hosted Windows image is Windows Server,
not Windows 11).
The hosted test run: all 453 tests pass on GitHub's hosted Ubuntu, macOS and Windows runners on Python 3.9 to 3.14 (run by hand on 2026-10-16;
the first run failed 13 tests on every Windows leg). The fixes are in the tests and in scripts/merged-check, not in the board: the page-script
tests send the Node harness its data on stdin instead of the command line (Windows refuses a command line over 32,767 characters) and decode its
answer as UTF-8 (a cut title's ellipsis came back as three other characters under the Windows code page); the doctor tests' simulated Linux
machine answers an interpreter path and file modes the way a Linux machine does; and scripts/merged-check removes its scratch clone even where
git's object files are read-only (Windows).
Status. Early software from one maintainer. On the first run of the test suite on GitHub's hosted runners (2026-10-16) macOS and Linux passed and the hosted Windows image failed 13 of 433 tests; they were fixed the same day (mostly tests: large Node inputs, a code-page decode, simulated-Linux assumptions; plus one real fix: the merge-check script left a scratch clone behind on Windows) and a second run passed all 18 jobs (Ubuntu, macOS, Windows; Python 3.9 to 3.14). The fixes are on main, not in this 0.9.0 zip. Details below are the changelog for this version.
[0.9.0] - 2026-10-16
Changed: a silent feed reads as silent, with the machine's liveness from its beat
A source that went quiet now says how long, against the interval it declares ("Late 1 min (missing after 4 x 20 s)", "Silent for 6 min (missing after 4 x 20 s)"), on the Sources list, in the evidence line, on the machine row and in the trust line. A source that declares no interval_s says "interval not declared" and is never called late or missing.
The machine behind a silent source says what its beat shows: "machine last seen 40 s ago via its beat", "machine not seen for 20 min, its beat is silent too" (the beat stopped as well), or "machine not seen" (no beat was ever seen). Silence alone never reads as down.
The view gains two derived fields (no new feed field): a source's interval_declared and a machine's beat_seen. The wording is in defaults.json (src_say_*, machine_seen_*, machine_unseen).
Added: Linux lingering preflight, a beat with every join, a doctor check
weatherdeck join --dry-run checks lingering on Linux (not WSL, nor a host without a systemd user manager unless --scheduler cron or system is chosen). A systemd user unit is stopped when the
last login session of its user ends, unless lingering is on, and returns only at the next login, so the board shows the machine missing in between.
With lingering off the plan is not ready (LINGER_OFF, with the one-line remedy loginctl enable-linger <user>); a lingering state that could not be read
is LINGER_UNKNOWN, never a pass. --scheduler cron (a crontab line) or --scheduler system (a system-level unit) is the named alternative and does not
need lingering. The join never changes lingering, and removing it leaves lingering as found. An adapter's pusher that is a user unit is named at risk too.
Every join plans the beat (beat-<machine>, docs/beat.md) beside any adapter pusher on the machine, registered the same way and under the same lingering rule.
The dry run prints that plan, what it leaves alone and how to undo it.
weatherdeck doctor has one more check, systemd user timers survive logout: lingering and the next elapse of a Weatherdeck user timer; warn with the
remedy when lingering is off, not applicable on macOS, Windows and hosts without systemd. Advice only; doctor never runs enable-linger.
Changed: open-source text
MIT licensed (see LICENSE); README, CONTRIBUTING, SECURITY, SUPPORT and CODE_OF_CONDUCT (Contributor Covenant 2.1) carry real text, with reports routed through GitHub's private vulnerability reporting and issues, and no placeholders left.
Changed: the demo never shows this machine's name, and the README gives the demo's silence its real timing
weatherdeck run --demo serves as "demo host" when hosts.label is not set, where it used to show the machine's own hostname on the page. A configured hosts.label still wins, and a real (non-demo) host still defaults to its hostname.
README: the demo's silent source stops reporting after about two and a half minutes, shows late from about 2:40 and silent about three minutes after start (the README had put silent at two and a half minutes).
Removed: the bundled adapter left this repository (the product and the feed are untouched)
adapters/ is gone. The tool-specific adapter that lived there (its pusher, replay, installers, host preset, runbooks, fixtures and its
own tests) is kept and built in its own repository now. Nothing a running install uses changes: every machine runs an installed copy, and
the next package comes from there under the same names and layout. To feed the board from your own tool, see docs/adapters.md; docs/runbook-windows.md and docs/runbook-macos.md
are the host guides.
scripts/build-package builds the product zip only (weatherdeck-<version>.zip). It no longer reads adapters/*/packages.json; a
package that bundles an adapter (its pusher, or a host package of the product plus the adapter's files) is built from the adapter's
repository, which runs this script for the product part.
scripts/self-audit exempts nothing by default (it used to skip adapters/ in the vendor check; --exempt DIR still marks an adapter
directory), and --denylist can be given more than once. Its built-in vendor list keeps only the generic AI-provider and VPN names; the names of
tools the core meets only through an adapter belong in a private denylist kept outside the tree.
MIGRATION.md is gone. The schema-id table (why the wire ids still say lookout.*) is now the "Schema ids" section of docs/feed-spec.md, with the ids unchanged.
scripts/merged-check, the CI workflow, CONTRIBUTING.md, the README and the pull request template no longer mention the adapter's suite,
replay or directory: the core suite (python3 -m unittest discover -s tests) is the one suite here.
Changed: a merged PR is never trouble or an ask, and old records fold under Housekeeping (readability slice 3; the feed is untouched)
No agent gone after a merge. A lane whose PR merged is never agent_gone, whatever its state says (src/weatherdeck/lanes.py agent_gone). A lane paused after its PR merged, whose worker's session then ended, read AGENT GONE and was counted off course;
the exemption covered only a failed, done or unknown state, as the feed spec's "No agent at the work" already promised for any state.
An approval whose PR merged or was closed is an old record. It was already left out of the waiting-on-you number; its row no
longer reads Waiting on you or Finished among the live rows, and its sailor is not at the rail.
Housekeeping holds every old record the board can tell from the feed alone, each with its reason as its sentence: an old approval
("Old approval: PR #1 merged, nothing left to approve."), a lane whose PR merged and whose worker's session ended without the lane
being closed ("Finished but never closed: ..."), and the rows that read Can't tell only because their books are old ("Old record:
..."). The Lists page folds them under "Housekeeping: N old records", closed until tapped; the phone's list ends with the same fold; the
sea's "+N more" line counts them as "N old records". None of them is counted as waiting on you, off course (including a project's head
on the Gun deck), a trouble badge, the Gun deck door's outline, a Problems line, the waiting count or the waits outside the fleet, and the page never closes or
changes any of them. Words are hk_* in labels (the rs_approval_merged, rs_approval_closed, rs_gone_merged and rs_unread
sentences are now hk_approval_merged, hk_approval_closed, hk_gone_merged and hk_unread). The host's counts.off_course still
counts an old record whose only sign is its books' words, which only the page reads. See docs/theme-spec.md 24.3.
Changed: approval lanes join the waiting-on-you number, one ask is one line, and a "sources disagree" badge (readability pass, second part)
A lane that waits for your OK is counted. A lane that says it waits for the viewer's OK (handoff.state: awaiting_you) with
its PR open (link.state: open), and that no call names, is an ask of its own: it joins the one waiting-on-you number on the
call band, the Lists header, the Bridge door and the ship plates, and is drawn as its own line ("Waiting for your OK: <title>")
with its own reason ("Its lane says it is waiting for your OK; has not raised it as a call", naming the source that
raised the calls when exactly one did). A raised call keeps its own reason line. A lane whose PR merged or closed is not counted.
One ask is one line. A call whose item names the lane, or, with no item, whose link.url is the lane's link.url,
absorbs the lane. The host decides it (src/weatherdeck/view.pylane_asks) and marks the lane lane.asks_you in /api/view; counts.waiting_on_you counts the calls plus those lanes.
The number says how old it is: "as of 3 min ago" beside it on the call band and the Lists header, from the oldest report
behind its asks (counts.calls_snapshot_at when the view carries one, else when the host received each feed); "as of: not
reported" when no ask has a known time.
"Sources disagree". When the calls and the lanes that say they wait for the viewer's OK report different sets (a lane no
call names, or a call whose lane does not say it waits), a small badge sits beside the number, and its detail lists each
difference: under the Lists header, under the calls on the band, and on the Bridge under "Sources disagree". The view lists them
as waiting_disagree. Nothing in it is counted, and the board closes nothing.
The Bridge heads the lane asks "Lanes waiting for your OK"; its rows no longer squash when the list is longer than the place,
the list scrolls instead.
A call or lane ask with no start time says "Waiting since: not r...