Compare commits

..

1 Commits

Author SHA1 Message Date
alexbelgium
55d64f38ae docs(skill): ask for a version rollback before reproducing or reverting
Follow-up to #3026/#3028. The sub_filter was blamed for a download regression
and reverted; the reporter's own rollback to 1.5.3, which predates the filter,
showed identical behaviour and the revert was closed unmerged. Records the
sequencing lesson, the ruled-out layers, and the registry-layer rig.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 09:15:10 +02:00
141 changed files with 92 additions and 136 deletions

View File

@@ -58,3 +58,64 @@ Once the rebuilt add-on is running, re-run the measurement that motivated the wo
cannot be self-verified — a service that reads its environment only at start makes an env-var fix
unproven until the add-on restarts, which needs the user or `ha-cli` with their agreement. If you
cannot restart, the change is **Assumed**, not Verified, and must be reported that way.
## When your change is blamed for a regression, ask for a version rollback first
PR #3026 added an nginx `sub_filter` to the `filebrowser_quantum` ingress vhost. Right after it
merged the user reported Download had broken — the file opened inline instead of downloading — and
confirmed it still worked on the add-on's direct port, which serves `direct.conf` and carries no
`sub_filter`. That made the filter the only ingress-side change in the update, so PR #3028 reverted
it. Meanwhile every measurement said the filter was not involved:
- the shipped bundle's Download action builds an `<a href>` and clicks it, and never calls
`window.open`, which is all the injected shim overrides;
- the shim's own predicate, evaluated live in the running app, returned false for the download
URL, the `inline=true` raw URL and the public-share download URL;
- the download response through the vhost was **byte-identical** with and without the `sub_filter`
(`Content-Disposition: attachment` intact, `Content-Length` unchanged), for `text/plain` and for
the `text/html` case the filter actually scans;
- upstream's frontend download code is unchanged from v1.5.0-stable to v1.5.4-stable; Supervisor's
ingress proxy forwards `Content-Disposition` (`_response_header` drops only `Transfer-Encoding`,
`Content-Length`, `Content-Type`, `Content-Encoding`); and the ingress panel's iframe carries no
`sandbox` attribute, so downloads are not sandbox-blocked.
The measurements were right. The user then rolled their add-on back to **1.5.3**, which predates the
filter, saw the identical behaviour, and the revert was closed unmerged.
**The lesson is about sequencing, not about who was right.** "Works on the old version, breaks on the
new one" is the only cheap experiment that actually isolates a shipped change, and only the reporter
can run it. Ask for it *first* — before building a reproduction, before opening a revert. It costs
them one add-on downgrade and it either confirms the regression or, as here, redirects the whole
investigation. A revert is the fallback for when they cannot roll back, not the opening move.
Two corollaries:
- **Do not let a clean local reproduction settle it either.** Being unable to reproduce is not
evidence of absence, and the reporter watching it fail on their own instance outranks it. Both
sides of that needed the rollback to resolve.
- **`build_from: <image>:latest` means every merge ships an upstream version bump too**, so "it
broke when your PR landed" never implicates the diff on its own. Record which upstream version
each add-on image was built from — the registry config blob's `created` timestamp, the resolved
manifest digest, and the binary's version string. Here: add-on 1.5.3 was built 2026-08-28 23:29
UTC from upstream v1.5.3-stable, 1.5.3.1 on 2026-08-30 06:00 UTC from v1.5.4-stable
(`sha256:e549e1a9…`). If you do use a revert as the experiment, both builds must resolve the same
upstream digest or it proves nothing:
```bash
T=$(curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:<repo>:pull" | jq -r .token)
curl -s -H "Authorization: Bearer $T" \
-H "Accept: application/vnd.oci.image.index.v1+json" \
"https://registry-1.docker.io/v2/<repo>/manifests/latest" \
| jq -r '.manifests[] | select(.platform.architecture=="amd64" and .platform.os=="linux") | .digest'
```
Worth building anyway, because it is reusable and needs no dockerd: a reproduction rig made **out of
the published image's own layers**. Pull the manifest and blobs from the registry with `curl` + `jq`,
untar them in order, then run the extracted binary through the image's own musl loader
(`root/lib/ld-musl-x86_64.so.1 ./filebrowser`) with the real `http/dist` next to it. Put the add-on's
rendered `ingress.conf` in front of it, and a second nginx in front of that to strip the
`/api/hassio_ingress/<token>` prefix the way Supervisor does. That gets the real frontend, the real
backend and the real vhost under a browser — everything except Supervisor itself. Plain `tar` does
not interpret whiteouts, so a file a later layer deletes (`.wh.<name>`) or a directory it marks
opaque (`.wh..wh..opq`) survives into the reconstructed rootfs; check with
`tar tzf <layer> | grep '\.wh\.'` and reach for an OCI-aware unpacker if any turn up.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 404 KiB

BIN
.github/stats.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

@@ -38,7 +38,7 @@ jobs:
run: |
set -euo pipefail
CHANGED_FILES=$(git diff --name-only "$DIFF_RANGE")
UNICODE_SPACES_REGEX='[\x{00A0}\x{2002}\x{2003}\x{2007}\x{2008}\x{2009}\x{202F}\x{205F}\x{3000}\x{200B}]'
UNICODE_SPACES_REGEX=$'[\\u00A0\\u2002\\u2003\\u2007\\u2008\\u2009\\u202F\\u205F\\u3000\\u200B]'
for file in $CHANGED_FILES; do
if [ -f "$file" ]; then
MIME_TYPE=$(file --mime-type -b "$file")
@@ -89,7 +89,7 @@ jobs:
- name: Fix non-printable Unicode spaces in all text files
run: |
set -euo pipefail
UNICODE_SPACES_REGEX='[\x{00A0}\x{2002}\x{2003}\x{2007}\x{2008}\x{2009}\x{202F}\x{205F}\x{3000}\x{200B}]'
UNICODE_SPACES_REGEX=$'[\\u00A0\\u2002\\u2003\\u2007\\u2008\\u2009\\u202F\\u205F\\u3000\\u200B]'
find . -type f ! -path "./.git/*" | while read -r file; do
MIME_TYPE=$(file --mime-type -b "$file")
if [[ "$MIME_TYPE" == text/* ]]; then

View File

@@ -362,50 +362,6 @@ jobs:
"$EXECUTION_FILE" >/dev/null 2>&1
}
# Did the run die because the Claude credential is bad? The action
# reports this uselessly — the failure surfaces as "--json-schema was
# provided but Claude did not return structured_output", which points
# at the schema and not at auth. The execution file carries the truth.
#
# A bad credential shows up in more than one shape, and both have been
# seen in production within a week:
# * revoked token -> error "authentication_failed", HTTP 401
# * malformed token -> error "invalid_request", api_error_status
# null, and the reason only in the SDK's message text ("Invalid
# Authorization header value from CLAUDE_CODE_OAUTH_TOKEN: it
# contains a line break at character 62").
# Matching only the first shape reported the second as a generic
# workflow fault, so the text marker is checked too — but only on an
# object the SDK itself flagged as an API error, so an issue body that
# merely mentions the secret's name cannot fake one. A false positive
# would change only the message: this branch exits 1 either way.
hit_auth_failure() {
[ -n "${EXECUTION_FILE:-}" ] && [ -s "${EXECUTION_FILE:-}" ] || return 1
jq -e '(type == "array") and
any(.[]?;
(type == "object") and
(((.error? // "") == "authentication_failed") or
((.error_status? // 0) == 401) or
((.api_error_status? // 0) == 401) or
(((.is_api_error_message? // false) == true) and
(tostring | test("CLAUDE_CODE_OAUTH_TOKEN|Invalid auth token")))))' \
"$EXECUTION_FILE" >/dev/null 2>&1
}
# The SDK's own words are far more useful than anything this script
# can infer — "it contains a line break at character 62" names the
# exact defect. Surface it verbatim when present.
auth_failure_detail() {
[ -n "${EXECUTION_FILE:-}" ] && [ -s "${EXECUTION_FILE:-}" ] || return 0
jq -r 'if type == "array" then
[ .[]? | select(type == "object")
| select((.is_api_error_message? // false) == true)
| tostring
| capture("(?<m>Invalid Authorization header value[^\"]*|Invalid auth token[^\"]*)")
| .m ] | first // ""
else "" end' "$EXECUTION_FILE" 2> /dev/null || true
}
# GATE 1 — did the action itself run? This is checked BEFORE looking
# at the payload, because the action can fail *after* having written
# a valid structured output: the object would sail through the shape
@@ -424,17 +380,6 @@ jobs:
if [ "${CLASSIFY_OUTCOME:-}" = "failure" ]; then
restore_needs_info
# Checked before anything else, because it is the one failure with
# a specific remedy and it takes down every tier at once — tier 1
# cannot label, so tier 2's batch is empty and the whole pipeline
# goes quiet while each run still fails in a way that reads like a
# per-issue problem. Say plainly what is wrong and what to do.
if hit_auth_failure; then
DETAIL=$(auth_failure_detail)
echo "::error::CLAUDE_CODE_OAUTH_TOKEN is being rejected${DETAIL:+ — $DETAIL}. This is NOT a problem with issue #$ISSUE: every AI workflow is down until the credential is fixed. Regenerate with 'claude setup-token' and re-enter the secret in the CR_PAT environment as a SINGLE line with no line break or trailing newline. Set the AI_DISABLED repo variable to 'true' to silence these runs meanwhile."
exit 1
fi
# ...with one exception. Exhausting the turn budget is NOT a
# workflow fault: the action ran fine and this particular issue was
# just too tangled to finish inside the turn budget. Treating it as systemic

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -1,8 +1,4 @@
## 0.6.27.3 (2026-08-30)
- Doc: explain in the README that Calibre-Web's optional extras (metadata, kobo, gdrive, gmail, goodreads, ldap, oauth, comics) are already installed by the LinuxServer base image, that `pip install calibreweb[...]` inside the container is useless and not persistent, and that the cover fields on the Edit Metadata page are gated on `Enable Uploads` plus the user's `Upload` permission (https://github.com/alexbelgium/hassio-addons/issues/1143)
- Fix: remove the Dockerfile step that claimed to install the Calibre binaries into `/opt/calibre`. There is no `wget` in the image, so the command failed silently and left the layer empty; the binaries are and were provided at start by the default `linuxserver/mods:universal-calibre` docker mod
## 0.6.27.2 (2026-08-23)
- Fix: trust the whole supervisor network range for the ingress auth header instead of the addon's own address, which changes across restarts. The list is only written when that range is missing, so an entry added in the calibre-web admin page is no longer erased on every start (https://github.com/alexbelgium/hassio-addons/pull/3010)

View File

@@ -35,9 +35,11 @@ RUN \
&& echo '#!/bin/sh' > /usr/bin/xdg-desktop-menu && chmod +x /usr/bin/xdg-desktop-menu \
&& echo '#!/bin/sh' > /usr/bin/xdg-mime && chmod +x /usr/bin/xdg-mime
# By default the calibre binaries (calibredb, ebook-convert, ebook-meta) are installed at start by
# the linuxserver/mods:universal-calibre docker mod, set as the default DOCKER_MODS in config.yaml.
# The xdg-* stubs above keep its calibre_postinstall step quiet.
# Install calibre binaries (provides calibredb required for book downloads)
# hadolint ignore=DL4006
RUN \
wget -nv -O- https://download.calibre-ebook.com/linux-installer.sh | sh /dev/stdin install_dir=/opt/calibre
ENV PATH="/opt/calibre:${PATH}"
# Global LSIO modifications
COPY ha_lsio.sh /ha_lsio.sh

View File

@@ -97,18 +97,6 @@ This addon supports mounting both local drives and remote SMB shares:
- **Local drives**: See [Mounting Local Drives in Addons](https://github.com/alexbelgium/hassio-addons/wiki/Mounting-Local-Drives-in-Addons)
- **Remote shares**: See [Mounting Remote Shares in Addons](https://github.com/alexbelgium/hassio-addons/wiki/Mounting-remote-shares-in-Addons)
### Optional Calibre-Web features
Calibre-Web documents optional extras that a manual installation adds with `pip install calibreweb[metadata]` and similar. **You do not need to install anything here**: the LinuxServer base image this add-on builds on installs Calibre-Web's `requirements.txt` *and* its full `optional-requirements.txt` into the application's virtualenv, so the gdrive, gmail, goodreads, ldap, oauth, metadata, comics and kobo dependencies are all present already. Running `pip install calibreweb[...]` inside the container is not a supported way to enable them: it installs the PyPI distribution of Calibre-Web over an installation that already has those dependencies, and it can disturb the versions the base image pinned. It is also thrown away, because the Supervisor recreates the add-on container on restart.
Optional features are switched on in the Calibre-Web web interface, not in the add-on options, under `Admin` -> `Basic Configuration` -> `Feature Configuration` (for example `Enable Uploads`, `Enable Kobo sync`, `Use Goodreads`).
**Book covers.** The `Fetch Cover from URL` and `Upload Cover from Local Disk` fields only appear on a book's `Edit Metadata` page when `Enable Uploads` is ticked in `Feature Configuration` **and** the logged-in user has the `Upload` permission (`Admin` -> the user -> `Upload`). A missing Python package is not what hides them.
**Conversion, metadata embedding and the other Calibre integrations** use command-line binaries such as `ebook-convert`, `ebook-meta` and `calibredb`. Those are installed at start by the `linuxserver/mods:universal-calibre` docker mod, which is the shipped default of the `DOCKER_MODS` option. If you set `DOCKER_MODS` yourself, keep `linuxserver/mods:universal-calibre` in the list (mods are separated by `|`) or those binaries disappear.
**Other compatible Python packages** can be installed from the add-on's custom script (see the section below); `pip` there points at Calibre-Web's own virtualenv. Such a script runs on every start, and it has to, since the container's writable layer does not persist.
### Custom Scripts and Environment Variables
This addon supports custom scripts and environment variables:

View File

@@ -116,5 +116,5 @@ schema:
slug: calibre-web
udev: true
url: https://github.com/alexbelgium/hassio-addons/tree/master/calibre_web
version: "0.6.27.3"
version: "0.6.27.2"
video: true

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -1,15 +1,4 @@
## 1.5.3.2 (2026-08-30)
- Fix Download in the Home Assistant iOS companion app (iOS 17 and later),
where a file opened and showed its content with no way to save it.
FileBrowser downloads by clicking a link that carries no `download`
attribute, and the app's WKWebView only turns a click into a real download
when that attribute is present, so inside the ingress panel the file was
simply rendered. The ingress filter now adds the attribute to FileBrowser's
own download link. "Open file" still opens, and the public-share sidebar's
own download button is not covered. Desktop browsers already downloaded
these and are unchanged, as is direct access on port 8071.
## 1.5.3.1 (2026-08-29)
- Fix "open parent directory" in Tools -> File Size Analyzer under Home
Assistant ingress. FileBrowser opened the parent folder in a new tab, which

View File

@@ -118,4 +118,4 @@ schema:
slug: filebrowser_quantum
udev: true
url: https://github.com/alexbelgium/hassio-addons
version: "1.5.3.2"
version: "1.5.3.1"

View File

@@ -13,59 +13,34 @@ server {
proxy_read_timeout 30m;
proxy_pass %%protocol%%://backend%%subpath%%;
# Two things the ingress panel needs that a plain browser tab does not.
# Both are injected into the page's existing nonce-carrying inline script
# rather than next to <div id="app">: FileBrowser sends
# script-src 'self' 'nonce-<random>', so a standalone inline <script>
# would be blocked. If upstream ever drops that
# window.__pwaDeferredPrompt line the filter stops matching and both
# behaviours revert, which is the state before either fix.
# Tools -> File Size Analyzer (and the other tool views) open a result's
# parent folder with window.open(<absolute url>, '_blank'), because
# goToItem() takes its newTab argument from the context menu's
# showLimitedOptions flag, which those views always set. Behind ingress
# that popup lands on the raw /api/hassio_ingress/<token>/ url with no
# Home Assistant frontend around it to keep the ingress session alive,
# so the new tab answers 401 instead of showing the folder. Turn that
# popup into a navigation of the panel itself.
#
# 1. Opening a folder. The tool views (Tools -> File Size Analyzer and
# the others) always set the context menu's showLimitedOptions flag, and
# openParentFolder() hands that same flag to goToItem() as its newTab
# argument, so the folder is opened with window.open(<url>, '_blank').
# Behind ingress that popup lands on the raw /api/hassio_ingress/<token>/
# url with no Home Assistant frontend around it to keep the ingress
# session alive, so the new tab answers 401 instead of showing the
# folder. Turn it into a navigation of the panel itself. The context
# menu's "go to item" action, offered by search results and the tool
# views, hardcodes the same new tab and is fixed with it.
# The context menu's "go to item" action, which search results and the
# tool views also offer, hardcodes the same new tab and is fixed too.
#
# Scoped to the two prefixes goToItem() builds, "files/" and
# "public/share/", so the window.open calls that download or preview a
# file (they go to api/resources/download) keep their own tab: sending
# an inline raw file to location.assign would replace the whole app. A
# link out of the add-on keeps its own tab as well, unless a user points
# a sidebar link -- or a link inside a file open in the editor -- at this
# same instance's files/ or public/share/ route. Same shape as the komga
# add-on's ingress filter.
# an inline raw file to location.assign would replace the whole app.
# A link out of the add-on keeps its own tab as well, unless a user
# points a sidebar link (or a link inside a file open in the editor) at
# this same instance's files/ or public/share/ route, which then also
# opens in the panel. Same shape as the komga add-on's ingress filter.
#
# 2. Download. FileBrowser downloads a file by building an <a> with no
# download attribute, clicking it, and letting the attachment response do
# the rest. The Home Assistant iOS companion app is a WKWebView, and a
# download happens there only when WebKit turns a navigation *action*
# into a WKDownload, which is what the download attribute does -- the app
# hands that to its own download manager
# (WebViewController+WebKitDelegates.swift, navigationAction:didBecome
# download:). Its response policy delegate returns .allow for every
# sub-frame and never returns .download, so a plain attachment navigation
# inside the ingress panel is just rendered: a text file opens and shows
# its content with no way to save it. Adding the attribute makes the same
# click a real download. Desktop browsers already downloaded these and
# are unaffected, and an empty value keeps the filename the server sends
# in Content-Disposition. Matched on the two exact download endpoints,
# minus inline=true: the "no preview available" fallback renders an
# "Open file" link on the same endpoint with that parameter
# (views/files/Preview.vue), and it is meant to open, not save. Only
# programmatic clicks pass through here -- a person clicking a link never
# calls HTMLAnchorElement.prototype.click -- so this reaches
# FileBrowser's own hidden download anchor and nothing a user clicks.
#
# Not covered: the public-share sidebar downloads with window.open()
# rather than an anchor, and the app's download manager is gated on
# iOS 17 (WebViewController+WebKitDelegates.swift).
sub_filter "window.__pwaDeferredPrompt = null;" "window.__pwaDeferredPrompt = null;(function(){var o=window.open;window.open=function(u,n,f){try{var b=(window.globalVars||{}).baseURL;if(u&&n==='_blank'&&!f&&b){if(b.slice(-1)!=='/')b+='/';var t=new URL(u,location.href);if((t.protocol==='http:'||t.protocol==='https:')&&t.origin===location.origin&&(t.pathname.indexOf(b+'files/')===0||t.pathname.indexOf(b+'public/share/')===0)){location.assign(t.href);return window}}}catch(e){}return o.apply(window,arguments)};var c=HTMLAnchorElement.prototype.click;HTMLAnchorElement.prototype.click=function(){try{var b=(window.globalVars||{}).baseURL;if(b&&this.href&&!this.hasAttribute('download')){if(b.slice(-1)!=='/')b+='/';var t=new URL(this.href,location.href);if(t.origin===location.origin&&t.searchParams.get('inline')!=='true'&&(t.pathname===b+'api/resources/download'||t.pathname===b+'public/api/resources/download')){this.download=''}}}catch(e){}return c.apply(this,arguments)}})();";
# Injected into the page's existing nonce-carrying inline script rather
# than next to <div id="app">: FileBrowser sends
# script-src 'self' 'nonce-<random>', so a standalone inline <script>
# would be blocked. If upstream ever drops that
# window.__pwaDeferredPrompt line the filter simply stops matching and
# the popup behaviour returns, which is the pre-fix state.
sub_filter "window.__pwaDeferredPrompt = null;" "window.__pwaDeferredPrompt = null;(function(){var o=window.open;window.open=function(u,n,f){try{var b=(window.globalVars||{}).baseURL;if(u&&n==='_blank'&&!f&&b){if(b.slice(-1)!=='/')b+='/';var t=new URL(u,location.href);if((t.protocol==='http:'||t.protocol==='https:')&&t.origin===location.origin&&(t.pathname.indexOf(b+'files/')===0||t.pathname.indexOf(b+'public/share/')===0)){location.assign(t.href);return window}}}catch(e){}return o.apply(window,arguments)}})();";
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Some files were not shown because too many files have changed in this diff Show More