Repository navigation
Releases: SandObserver/Stackyard
Release list
1.6.0-rc.2
ghcr.io/sandobserver/stackyard:1.6.0-rc.2
1.6.0-rc.2 - 2026-08-12
Second release candidate for 1.6.0: 56 changes since 1.5.2, 17 of them new since rc.1. The headline is still importing an existing gethomepage or Dashy config. New in this candidate: the dashboard and Settings adapt to tablets, touch and large screens, widgets no longer poll in lockstep, and community icons are served through Stackyard instead of a public CDN.
This is a pre-release for testing and is not published to the demo. Worth exercising: importing a real config, the dashboard and Settings on a tablet and on a large screen, reordering Settings rows by touch, a dashboard with several widgets left running, and any badge or widget that stores a credential.
Before upgrading: sessions now expire after 12 hours idle rather than 30 days, so everyone signs in again (override with SESSION_MAX_AGE_DAYS). Requests that change something must send an Origin header, which affects scripts calling the API directly. The API listens on loopback only and can no longer be reached around Nginx. A stored credential is bound to the request it was saved for, so changing where a badge or widget sends it means entering it again. Allowing a self-signed certificate now applies only to addresses on your own network, so anything pointed at a public address needs a trusted certificate.
Added
- Import links and folders from a gethomepage or Dashy YAML config.
- The Docker Hub repository description is published from
README.mdon each stable release. - The support documentation describes the log format, the level abbreviations and how to read the logs.
Changed
- Dashboard tiles scale with the window, so six columns stay square on a tablet and fill the width of a large screen.
- Settings keeps its sidebar on a tablet, narrowed.
- Widgets spread their polling, so several on one dashboard no longer fetch from your services on the same tick.
- Importing from another dashboard reads a list written level with its key, a value folded across lines, and an empty
[]or{}, each of which used to refuse the whole file. - Confirming an import and naming a new folder use in-page dialogs instead of the browser's own.
- The container prints six lines when it starts, instead of twenty-three.
- Nginx request errors now appear in
docker logs, and Nginx no longer writes an access log to a file inside the container that nothing rotated. - A misconfigured
TRUST_PROXYproduces one short warning instead of two long ones.
Fixed
- Resizing a browser window across the mobile breakpoint now switches the dashboard layout, instead of keeping the old one until a reload.
- Dragging a Settings row to reorder it now works by touch on a tablet.
- Controls in Settings reach the 44px minimum on a touch device.
- Settings respects the notch and home indicator when installed as a PWA.
- One unreachable service no longer delays every other tile's refresh by six seconds.
- A badge whose service did not answer keeps its last value and is marked out of date, instead of showing nothing.
- Icons from the community set are fetched once through Stackyard and kept in memory, instead of being loaded from a public CDN on every page load.
- A badge blocked for pointing at a private address now names
ALLOW_PRIVATE_IPS=trueas the setting that allows it. - An Admin save no longer changes the Unsplash wallpaper on every open dashboard. The photo is kept for a day.
- The dashboard reopens on the page you were last on instead of the first one.
- The Docker socket proxy address is checked when it is saved, and a wrong one is refused with the reason and what to change about it, instead of being stored.
- The icon preview in the app editor keeps its inset and no longer reverts to the initial letter while an icon name is being typed.
- Exporting the config now reports a failure instead of saving an error response as
stackyard-config.json. - A session that expires while you are working in Admin now raises the sign-in box over the page, and the interrupted save completes once you are back in, instead of failing as "Unauthorised" until the page is reloaded.
- Saving an app or widget no longer reports "Updated" when the write failed; the editor stays open with the form intact.
- A failed save no longer leaves the admin list showing a delete, reorder, drag-drop or folder change the server never received.
- Importing a config reports "Imported" only once the write has landed.
- A failed label-visibility toggle now reports the error instead of failing silently with the switch left in its new position.
- A settings save that is refused for a missing or weak password no longer saves the rest of the screen first.
- The Password Protection switch is read back from the server after a save, so it no longer shows a state the server rejected.
- The Books, Now Playing and Backup widgets now show why they failed, such as a missing API key or a rejected password, instead of a generic error.
- The last visible row in a Settings group no longer draws a separator under itself, which showed under Password Protection whenever protection was off.
- The startup banner no longer prints a Web UI address that pointed at the internal API port and worked for nobody.
- The Connections map spreads a country's highlighted dots across it, clustered around the capital, instead of bunching them at the country's centre.
- Badges no longer lose their rounded shape in Safari after a long session.
- Turning Password Protection off now deletes the stored password. While protection is off, anyone who can reach the dashboard can set a new one.
- A password typed in the same save that turns Password Protection off is no longer stored and then immediately deleted, which signed out every other device for nothing.
- The sign-out row no longer stays on screen after Password Protection is turned off, where pressing it returned an error.
- The password and Unsplash key status labels are translated instead of always reading in English.
- Escape and the Tab trap keep working in a folder or prompt overlay after clicking text inside it.
Security
- Pages no longer permit stylesheets or fonts from Google Fonts, which nothing requests.
- The
linux/arm64release image is scanned for vulnerabilities before it is published, the same aslinux/amd64. - A stored credential is only reused for the request it was saved for. Changing where a badge or a widget sends it now requires entering it again.
- A tile colour that is not a colour no longer reaches a stylesheet, so a stored value cannot name a host for the browser to fetch from.
- An embedded widget is granted only presentation features, never camera, microphone or location, whatever its stored settings ask for.
- A badge no longer sends a header whose value is empty.
- The Password Protection switch refuses a request that does not plainly say on or off, instead of reading it as off and deleting the stored password.
- A Connections tile that cannot reach a service now says so in plain words instead of printing the address it tried, which put an internal host and port on the dashboard.
- A failed connection test no longer writes the request path to the log, where a service that carries its API key in the path would have left it.
- Allowing a self-signed certificate now applies only to addresses on your own network, wherever the request came from. A badge or health check pointed at a public address is verified again, so an app relying on this for one must use a trusted certificate.
- Importing from another dashboard asks before allowing self-signed certificates, instead of taking the setting from the file.
- Sessions now expire after 12 hours idle instead of 30 days, and a session in use is extended. Override with
SESSION_MAX_AGE_DAYS. - Requests that change something must state their origin. A script calling the API directly now has to send an
Originheader naming the address it is calling. - The API listens on loopback only, so it can no longer be reached around Nginx.
- The pre-login status reply no longer says whether a password has been set.
1.6.0-rc.1
ghcr.io/sandobserver/stackyard:1.6.0-rc.1
1.6.0-rc.1 - 2026-08-12
Release candidate for 1.6.0: 39 changes since 1.5.2. The headline is importing an existing gethomepage or Dashy config, and most of the rest tightens what Admin does when a save fails and what a stored credential is allowed to reach.
This is a pre-release for testing and is not published to the demo. Worth exercising: importing a real config, saving in Admin while signed out or offline, and any badge or widget that stores a credential.
Before upgrading: sessions now expire after 12 hours idle rather than 30 days, so everyone signs in again (override with SESSION_MAX_AGE_DAYS). Requests that change something must send an Origin header, which affects scripts calling the API directly. The API listens on loopback only and can no longer be reached around Nginx. A stored credential is bound to the request it was saved for, so changing where a badge or widget sends it means entering it again. Allowing a self-signed certificate now applies only to addresses on your own network, so anything pointed at a public address needs a trusted certificate.
Added
- Import links and folders from a gethomepage or Dashy YAML config.
- The Docker Hub repository description is published from
README.mdon each stable release. - The support documentation describes the log format, the level abbreviations and how to read the logs.
Changed
- Importing from another dashboard reads a list written level with its key, a value folded across lines, and an empty
[]or{}, each of which used to refuse the whole file. - Confirming an import and naming a new folder use in-page dialogs instead of the browser's own.
- The container prints six lines when it starts, instead of twenty-three.
- Nginx request errors now appear in
docker logs, and Nginx no longer writes an access log to a file inside the container that nothing rotated. - A misconfigured
TRUST_PROXYproduces one short warning instead of two long ones.
Fixed
- Exporting the config now reports a failure instead of saving an error response as
stackyard-config.json. - A session that expires while you are working in Admin now raises the sign-in box over the page, and the interrupted save completes once you are back in, instead of failing as "Unauthorised" until the page is reloaded.
- Saving an app or widget no longer reports "Updated" when the write failed; the editor stays open with the form intact.
- A failed save no longer leaves the admin list showing a delete, reorder, drag-drop or folder change the server never received.
- Importing a config reports "Imported" only once the write has landed.
- A failed label-visibility toggle now reports the error instead of failing silently with the switch left in its new position.
- A settings save that is refused for a missing or weak password no longer saves the rest of the screen first.
- The Password Protection switch is read back from the server after a save, so it no longer shows a state the server rejected.
- The Books, Now Playing and Backup widgets now show why they failed, such as a missing API key or a rejected password, instead of a generic error.
- The last visible row in a Settings group no longer draws a separator under itself, which showed under Password Protection whenever protection was off.
- The startup banner no longer prints a Web UI address that pointed at the internal API port and worked for nobody.
- The Connections map spreads a country's highlighted dots across it, clustered around the capital, instead of bunching them at the country's centre.
- Badges no longer lose their rounded shape in Safari after a long session.
- Turning Password Protection off now deletes the stored password. While protection is off, anyone who can reach the dashboard can set a new one.
- A password typed in the same save that turns Password Protection off is no longer stored and then immediately deleted, which signed out every other device for nothing.
- The sign-out row no longer stays on screen after Password Protection is turned off, where pressing it returned an error.
- The password and Unsplash key status labels are translated instead of always reading in English.
- Escape and the Tab trap keep working in a folder or prompt overlay after clicking text inside it.
Security
- A stored credential is only reused for the request it was saved for. Changing where a badge or a widget sends it now requires entering it again.
- A tile colour that is not a colour no longer reaches a stylesheet, so a stored value cannot name a host for the browser to fetch from.
- An embedded widget is granted only presentation features, never camera, microphone or location, whatever its stored settings ask for.
- A badge no longer sends a header whose value is empty.
- The Password Protection switch refuses a request that does not plainly say on or off, instead of reading it as off and deleting the stored password.
- A Connections tile that cannot reach a service now says so in plain words instead of printing the address it tried, which put an internal host and port on the dashboard.
- A failed connection test no longer writes the request path to the log, where a service that carries its API key in the path would have left it.
- Allowing a self-signed certificate now applies only to addresses on your own network, wherever the request came from. A badge or health check pointed at a public address is verified again, so an app relying on this for one must use a trusted certificate.
- Importing from another dashboard asks before allowing self-signed certificates, instead of taking the setting from the file.
- Sessions now expire after 12 hours idle instead of 30 days, and a session in use is extended. Override with
SESSION_MAX_AGE_DAYS. - Requests that change something must state their origin. A script calling the API directly now has to send an
Originheader naming the address it is calling. - The API listens on loopback only, so it can no longer be reached around Nginx.
- The pre-login status reply no longer says whether a password has been set.
Full changelog: v1.5.2...v1.6.0-rc.1
1.5.2
ghcr.io/sandobserver/stackyard:1.5.2
Changed
- Finishing first-run setup on an install with nothing on the dashboard now opens Settings, where the first app or widget is added, instead of an empty dashboard.
Fixed
- Disk Health, System Stats, Backup, Now Playing and the two Connections views now show why a widget failed instead of an HTTP status or a generic message.
- The admin save toast now carries its success or failure as a coloured fill that meets WCAG AA, instead of a thin accent bar on one edge.
Security
- The image no longer ships Python setuptools, which arrived as a packaging dependency of supervisor and nothing in the container used.
1.5.2-rc.1
Release candidate for 1.5.2. Not recommended for general use; latest still points at 1.5.1.
ghcr.io/sandobserver/stackyard:1.5.2-rc.1
Changed
- Finishing first-run setup on an install with nothing on the dashboard now opens Settings, where the first app or widget is added, instead of an empty dashboard.
Fixed
- Disk Health, System Stats, Backup, Now Playing and the two Connections views now show why a widget failed instead of an HTTP status or a generic message.
- The admin save toast now carries its success or failure as a coloured fill that meets WCAG AA, instead of a thin accent bar on one edge.
1.5.1
1.5.1 - 2026-08-08
A fix release. One item matters more than the rest: on 1.5.0, saving anything failed if you reached Stackyard directly on a mapped port. If you run it as http://your-server:8700 rather than behind a reverse proxy, upgrade.
Fixed
- Saving settings, logging in, and every other write now work when Stackyard is reached directly on a mapped port, such as
http://server:8700. nginx forwarded a Host header with the port stripped while the browser's Origin kept it, so the two never matched and every write was refused as an origin mismatch. Only installs behind a reverse proxy on 443 were unaffected, which is how it survived 1.5.0. - German, Spanish, French, Persian and Chinese translate a further 34 strings that were left as English in their catalogues.
- Admin translates the Dock, Health and Badge pills, the Edit button, the Secret checkbox, the colour and poll-interval labels, the widget Name and Widget Type labels, and the editor's section title. All were English in every language.
- The poll interval reads as one sentence in every language instead of wrapping the field in English words.
- App and widget names in the Settings list sit beside their icon in Persian instead of aligning to the far side of the row.
- The divider between the Settings navigation and the section beside it runs the full length of the section instead of stopping one screen down.
- The icon preview in the app editor no longer draws its artwork edge to edge.
- Colour swatches that wrap onto a second row line up under the row above instead of splitting to opposite edges.
Also published to Docker Hub
From this release, images are mirrored:
ghcr.io/sandobserver/stackyard:1.5.1
docker.io/sandobserver/stackyard:1.5.1
ghcr.io remains the one to prefer, and the one the signature below covers.
Verifying this release
cosign verify ghcr.io/sandobserver/stackyard:1.5.1 \
--certificate-identity-regexp '^https://github.com/SandObserver/stackyard/' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
The build also scans the image with Trivy, failing on a fixable HIGH or CRITICAL, and attaches an SPDX SBOM. See docs/security.md.
For maintainers
- A Playwright suite drives the admin UI against the built image, in Chromium and WebKit. It found the origin bug on its first working run.
- Those browser tests run on pull requests that touch the interface, on every merge, and as a gate on the release: a tag can no longer publish an image they reject.
- Backend coverage is measured against a floor, the frontend path map is checked, formatting is enforced, and the module-system split between the API and the frontend is a lint rule rather than a convention.
Full changelog: v1.5.0...v1.5.1
1.5.1-rc.1
1.5.1-rc.1 - 2026-08-08
Candidate for 1.5.1. It carries one fix that matters more than the rest: writes were refused on any install reached directly on a mapped port.
Fixed
- Saving settings, logging in, and every other write now work when Stackyard is reached directly on a mapped port, such as
http://server:8700. nginx forwarded a Host header with the port stripped while the browser's Origin kept it, so the two never matched and every write was refused as an origin mismatch. Only installs behind a reverse proxy on 443 were unaffected, which is why this survived 1.5.0. - German, Spanish, French, Persian and Chinese translate a further 34 strings that were left as English in their catalogues.
- Admin translates the Dock, Health and Badge pills, the Edit button, the Secret checkbox, the colour and poll-interval labels, the widget Name and Widget Type labels, and the editor's section title.
- The poll interval reads as one sentence in every language instead of wrapping the field in English words.
- App and widget names in the Settings list sit beside their icon in Persian instead of aligning to the far side of the row.
- The divider between the Settings navigation and the section beside it runs the full length of the section instead of stopping one screen down.
- The icon preview in the app editor no longer draws its artwork edge to edge.
- Colour swatches that wrap onto a second row line up under the row above instead of splitting to opposite edges.
If you are testing this
The origin fix is the one to exercise. Browse your instance directly on its mapped port, not through a reverse proxy, and save something in Settings. On 1.5.0 that fails with "Forbidden: origin mismatch".
For maintainers
- A Playwright suite now drives the admin UI against the built image, in Chromium and WebKit. It found the origin bug on its first working run.
- Those browser tests run on pull requests that touch the interface, on every merge, and as a gate on the release itself: a tag can no longer publish an image they reject.
- Backend coverage is measured and floored, the frontend path map is checked, and formatting is enforced.
- This is the first release mirrored to Docker Hub as well as ghcr.io.
Full changelog: v1.5.0...v1.5.1-rc.1
1.5.0
1.5.0 - 2026-08-08
A large security, accessibility and localization release: 90 changes since 1.4.0. Widgets are now translated throughout, in all six languages, and the release image is scanned, signed and inventoried.
Before upgrading: a config containing two items with the same id is now refused, with the id named; the largest accepted config save is 2 MB rather than 4 MB; and a third-party widget whose manifest breaks the stricter showIf or viewField rules will no longer load. Your password is re-hashed at a higher work factor the next time you log in. If your Docker socket URL is stored as tcp://, it is corrected to http:// automatically.
Added
- Widgets are translated: every settings form and every string inside a widget, in all six languages. A widget carries its own translations in its folder, so a third-party widget can ship its own. See docs/i18n.md.
- Sign out all devices in Admin → General → Security, without changing your password.
TRUSTED_PROXYnames where a front reverse proxy sits, so Stackyard sees real client addresses through it.- API error responses carry a machine-readable
kindalongside the existing message. See docs/api-errors.md. - Admin says why a widget's definition was refused, instead of leaving it silently absent.
Changed
- The accent colour is now teal, and the interface uses Apple's system colours throughout. Increased-contrast mode moves the whole palette rather than a few pieces of it.
- The layout and every widget follow the interface text direction, so Persian mirrors correctly throughout. The System Summary is deliberately excepted: it is figures and charts, and reads the same way in every language.
- Passwords are hashed with a five times higher work factor, and the stored hash records the parameters it was made with. An existing password is upgraded on next login.
- Widget manifests are checked more strictly: a
showIfmust name a sibling field and carry a condition, andviewFieldmust name a declared field whose options match the declared views. - Admin secondary text, placeholders and control borders meet WCAG contrast minimums in the default theme.
- API responses and the PWA manifest are compressed, and carry
Vary: Accept-Encoding. - Browser support is now stated: Safari and iOS Safari 16.2 and newer, and current Chrome, Edge and Firefox.
- The largest accepted config save is 2 MB rather than 4 MB.
- The clock's day and month names follow the selected language.
Security
- The container image no longer ships npm, corepack or yarn. Nothing in it used them, and their bundled dependencies were the only HIGH or CRITICAL findings in the image, including a critical denial of service in
tar. - A widget can no longer forward a raw error message from a service it contacted to the browser, which could name an internal address or path. 17 places did it.
- A widget's data function receives only the shared dashboard settings, as a frozen copy. It previously received the session signing key and the password hash, and could modify them.
- TLS verification is no longer skipped for a public IPv6 address when "skip TLS verification" is on.
- App and widget links using a script-bearing scheme are refused on save and ignored on render. Protocol handlers like
ssh://,vnc://andrdp://keep working. - Uploaded SVG icons are sanitized by rebuilding them from an allowlist rather than by removing known-bad patterns.
- Translated strings that may contain markup are limited to
<strong>,<em>,<code>and<br>with no attributes. - Messages between the dashboard and its widgets are checked against the page origin.
- The outbound guard blocks carrier-grade NAT, multicast, reserved and broadcast addresses, IETF assignment and benchmarking ranges, and IPv6 multicast. Outbound requests are restricted to
httpandhttps. - Widget pages can no longer be framed by other sites, and every page states its framing policy.
- Authentication settings can no longer be written through a config save or an imported config.
- A widget whose definition cannot be loaded no longer has its settings sent to the browser or written to an export.
- Log values are quoted and escaped, so a value containing a newline cannot forge a log line.
- nginx no longer reports its version in the
Serverheader or on error pages.
Fixed
- A Docker socket URL stored as
tcp://is corrected tohttp://on upgrade, instead of every app backed by a container reporting unhealthy. - The first-run password prompt asks for the password twice and can show what you typed, instead of locking the install out on an unnoticed typo.
- App and folder names keep their own text direction, so an English name in a Persian dashboard no longer truncates from the wrong end.
- The divider between the Settings navigation and the section beside it runs the full length of the section instead of stopping one screen down.
- Widgets stop polling while the dashboard tab is in the background, and refresh on return.
- Widgets keep showing their last reading through a brief outage and say how long ago it was fresh.
- Settings honours reduced motion, reduced transparency and increased contrast. Only the dashboard did.
- The page dots are reachable with Tab and activated with Enter or Space, and a page change is announced to a screen reader.
- Keyboard focus stays inside an open folder and the first-run password prompt, and returns to whatever opened them.
- Saving settings waits for the data to reach the disk, so a power cut cannot leave a truncated configuration.
- The container exits and restarts when the API cannot be started, instead of staying up with a dead API inside it.
- Rate limiting is per client rather than shared, and the polling endpoints are rate limited.
- Two items can no longer end up sharing an id, and a config containing duplicates is refused with the id named.
- Uploading an icon no longer overwrites an existing file of the same name, and icon search works in any capitalisation.
- System stats read
/proccorrectly: network speed, memory withoutMemAvailable, and CPU. - An install running a release candidate is told when the matching stable release is out.
- An open dashboard picks up every settings change, not only a renamed or relinked app.
- Installing to a home screen uses a properly padded icon, and the browser status bar matches the dashboard.
Removed
- Badge polling no longer sends a copy of each service's full response to the browser alongside the extracted number.
- The API no longer sends CORS preflight headers or answers
OPTIONS.
Verifying this release
The image is signed with cosign, keylessly, so the signature is bound to the workflow that built it:
cosign verify ghcr.io/sandobserver/stackyard:1.5.0 \
--certificate-identity-regexp '^https://github.com/SandObserver/stackyard/' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
The build also scans the image with Trivy, failing on a fixable HIGH or CRITICAL, and attaches an SPDX SBOM to the run. See docs/security.md.
Full changelog: v1.4.0...v1.5.0
1.5.0-rc.3
1.5.0-rc.3 - 2026-08-08
Third and, unless soaking finds something, final release candidate. Everything in rc.2, plus a hardened image. This is the candidate to run.
Security
- The container image no longer ships npm, corepack or yarn. Nothing in it used them: the container runs nginx, node and python3, and the API has no dependencies to install. Their bundled copies of
tar,brace-expansion,ip-addressandundiciwere the only HIGH or CRITICAL findings in the image, including a critical denial of service intar(CVE-2026-59873). Removing them removes the findings rather than silencing them, and leaves no package manager for anyone who gets into a running container.
Everything from rc.2
- A Docker socket URL stored as
tcp://is corrected tohttp://on upgrade, instead of every app backed by a container reporting unhealthy. - Setting a dashboard password asks for it twice and can be shown while typing.
- App and folder names keep their own text direction, so an English name in a Persian dashboard no longer truncates from the wrong end.
- Widgets are translated throughout: every settings form and every string inside a widget, in all six languages.
- The System Summary keeps its layout left to right in every language.
For maintainers
Release builds now scan, sign and inventory the image:
-
Trivy scans the built image and fails the release on a fixable HIGH or CRITICAL.
-
The image is signed keylessly with cosign. Verify it with:
cosign verify ghcr.io/sandobserver/stackyard:1.5.0-rc.3 \ --certificate-identity-regexp '^https://github.com/SandObserver/stackyard/' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com -
An SPDX SBOM is attached to the build as an artifact.
-
Whether a tag moves
latestis now decided by a real semver check rather than by looking for a hyphen in the ref, so a mistyped tag can no longer be published as stable.
Full changelog: v1.5.0-rc.2...v1.5.0-rc.3
1.5.0-rc.2
1.5.0-rc.2 - 2026-08-08
Second release candidate. Fixes everything reported against rc.1, and completes widget translation: every settings form and every string inside a widget now follows the selected language, in all six.
Upgrading from rc.1 needs nothing. If rc.1 turned all of your apps red, that is the first fix below and it is applied automatically.
Fixed
- A Docker socket URL stored as
tcp://is corrected tohttp://on upgrade. Outbound requests are now restricted to http and https, which refused the address Admin itself used to suggest, so the container list came back empty and every app backed by a container reported unhealthy. - Setting a dashboard password now asks for it twice and can be shown while typing. A mistyped password rotates the session secret and signs every device out, so it locked the install with nothing on screen to catch it.
- App and folder names now keep their own text direction. An English name in a Persian dashboard was laid out right-to-left and truncated from the wrong end: "Backup and Storage" rendered as "…nd Storage".
- The System Summary widget keeps its layout left to right in every language, so its value column and sparklines read the same way everywhere.
Added
- Widgets are now translated: every settings form, every status, empty state and error, and the names screen readers announce. 332 strings per language across the ten widgets.
- A widget carries its own translations in its own folder, at
i18n/<code>.json, so a third-party widget can ship its own languages without a change to core. A manifest with no catalog keeps working exactly as before. See docs/i18n.md. - The clock's day and month names now come from the selected locale rather than a fixed English list.
For maintainers
- A tagged release could never publish: the release build's final check compared its own cache-busting stamp pass against the committed tree, so the intended changes read as a failure to converge. It compares the two passes with each other now. This is why rc.1 has no image behind its tag.
Full changelog: v1.5.0-rc.1...v1.5.0-rc.2
1.5.0-rc.1
1.5.0-rc.1 - 2026-08-08
Release candidate. A large security, accessibility and localization release: 88 changes since 1.4.0. Please run it and report anything off before it is promoted to stable.
Before upgrading: a config containing two items with the same id is now refused, with the id named; the largest accepted config save is 2 MB rather than 4 MB; and a third-party widget whose manifest breaks the stricter showIf or viewField rules will no longer load. Your password is re-hashed at a higher work factor the next time you log in.
Added
- Sign out all devices in Admin → General → Security, without changing your password.
TRUSTED_PROXYnames where a front reverse proxy sits, so Stackyard sees real client addresses through it.- API error responses carry a machine-readable
kindalongside the existing message. See docs/api-errors.md. - Admin now says why a widget's definition was refused, instead of leaving it silently absent.
Changed
- The accent colour is now teal, and the interface uses Apple's system colours throughout. Increased-contrast mode moves the whole palette rather than a few pieces of it.
- Widgets follow the interface text direction, so Persian mirrors throughout. The layout follows the text direction by itself, without a hand-maintained list of per-element exceptions.
- Passwords are hashed with a five times higher work factor, and the stored hash records the parameters it was made with. An existing password is upgraded on next login.
- Widget manifests are checked more strictly: a
showIfmust name a sibling field and carry a condition, andviewFieldmust name a declared field whose options match the declared views. All bundled widgets comply. - Admin secondary text, placeholders and control borders meet WCAG contrast minimums in the default theme.
- API responses and the PWA manifest are now compressed, and carry
Vary: Accept-Encoding. - Browser support is now stated: Safari and iOS Safari 16.2 and newer, and current Chrome, Edge and Firefox.
- The largest accepted config save is 2 MB rather than 4 MB.
Security
- A widget can no longer forward a raw error message from a service it contacted to the browser, which could name an internal address or path. 17 places did it.
- A widget's data function now receives only the shared dashboard settings, as a frozen copy. It previously received the session signing key and the password hash, and could modify them.
- TLS verification is no longer skipped for a public IPv6 address when "skip TLS verification" is on.
- App and widget links using a script-bearing scheme are refused on save and ignored on render. Protocol handlers like
ssh://,vnc://andrdp://keep working. - Uploaded SVG icons are sanitized by rebuilding them from an allowlist rather than by removing known-bad patterns.
- Translated strings that may contain markup are limited to
<strong>,<em>,<code>and<br>with no attributes. - Messages between the dashboard and its widgets are checked against the page origin.
- The outbound guard now blocks carrier-grade NAT, multicast, reserved and broadcast addresses, IETF assignment and benchmarking ranges, and IPv6 multicast. Outbound requests are restricted to
httpandhttps. - Widget pages can no longer be framed by other sites, and every page states its framing policy.
- Authentication settings can no longer be written through a config save or an imported config.
- A widget whose definition cannot be loaded no longer has its settings sent to the browser or written to an export.
- Log values are quoted and escaped, so a value containing a newline cannot forge a log line.
- nginx no longer reports its version in the
Serverheader or on error pages.
Fixed
- Widgets stop polling while the dashboard tab is in the background, and refresh on return.
- Widgets keep showing their last reading through a brief outage and say how long ago it was fresh; half of them previously bypassed that handling.
- Settings now honours reduced motion, reduced transparency and increased contrast. Only the dashboard did.
- The page dots are reachable with Tab and activated with Enter or Space, and a page change is announced to a screen reader.
- Keyboard focus stays inside an open folder and the first-run password prompt, and returns to whatever opened them.
- Dashboard and widget text that was always English is now translated, including widget status text, the red-tile reason and the first-run prompt.
- Saving settings waits for the data to reach the disk, so a power cut cannot leave a truncated configuration.
- The container now exits and restarts when the API cannot be started, instead of staying up with a dead API inside it.
- Rate limiting is per client rather than shared, and the polling endpoints are rate limited.
- Two items can no longer end up sharing an id, and a config containing duplicates is refused with the id named.
- Uploading an icon no longer overwrites an existing file of the same name, and icon search now works in any capitalisation.
- System stats read
/proccorrectly: network speed came from the wrong columns once an interface had carried about 10 MB, memory reported 100% withoutMemAvailable, and CPU could read blank. - An install running a release candidate is now told when the matching stable release is out.
- An open dashboard picks up every settings change, not only a renamed or relinked app.
- Installing to a home screen uses a properly padded icon, and the browser status bar matches the dashboard.
Removed
- Badge polling no longer sends a copy of each service's full response to the browser alongside the extracted number.
- The API no longer sends CORS preflight headers or answers
OPTIONS.
Full changelog: v1.4.0...main