Skip to content

v2.0.3#788

Merged
ajslater merged 375 commits into
mainfrom
develop
Jun 23, 2026
Merged

v2.0.3#788
ajslater merged 375 commits into
mainfrom
develop

Conversation

@ajslater

Copy link
Copy Markdown
Owner
  • Fixes
    • CBR comics with high-precision file timestamps no longer fail to import
      (comicbox 4.0.2).
  • Features
    • Libraries can be marked read-only to protect their comic files from tag
      changes. Read-only comics hide their Edit Tags and Online Tagging buttons,
      and bulk edits spanning several libraries skip them.

ajslater and others added 29 commits June 18, 2026 00:41
Give every .0 release a short hyphen-delimited title and convert the
existing ### flavor-subtitles to the same format.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Granian assigns url_path_prefix verbatim to the ASGI root_path, which
Django strips from the request path with removeprefix(). A value like
"codex" (no leading slash) never matches the "/codex/..." request path,
so path_info keeps the prefix, ServeStatic 500s on every static file,
and STATIC_URL / email verification links come out malformed.

Normalize the prefix to the root_path / SCRIPT_NAME convention (one
leading slash, no trailing slash) at the single read site, so "codex",
"/codex/", etc. all resolve to "/codex".

Co-Authored-By: Claude Opus 4.8 <[email protected]>
A bulk online-tag scan handed every comic to comicbox's search path, so
a comic codex already holds a Metron/Comic Vine issue id for was
re-searched (resolving the series by name and listing issues per
candidate) instead of fetched directly by that id.

run_session now runs _prefetch_stored_ids first: build_stored_id_map
reads numeric issue ids straight from the Identifier table (so it works
even when the id never round-tripped into the file), each identified
comic is fetched by explicit id in one API call, written through the
same BulkTagWriteTask path, and dropped from the search set. Prefetched
comics stay in path_to_pk so the status table shows them matched and
Resume skips them; unresolved ids fall back to search. Skipped on dry
runs and for sources without credentials.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
The v4 client hardcoded root-absolute paths ("/api/v4/", "/api/v4/ws"),
so under a reverse-proxy subpath (server.url_path_prefix, e.g. "/codex")
every API call 404'd and the websocket connected to "/api/v4/ws" without
the prefix. Channels' outermost URLRouter requires the path to start with
root_path, so it raised "No route found for path '/api/v4/ws'" on every
connect (GitHub #784).

Derive the API and WebSocket bases from window.CODEX.APP_PATH (which
Django already resolves with the prefix) via shared APP_BASE / V4_BASE
exports in api/v4/base.js, and consume them from notify, browser, reader,
and the two admin tabs that built cover/schema URLs by hand. At the server
root APP_PATH is "/", so the bases stay "/api/v4/..." — no regression.

Also drops a stray leading slash in getPDFInBrowserURL that produced a
protocol-relative "//api/v4/..." URL.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Move the subpath (url_path_prefix) frontend fix from v2.0.1 into a new
v2.0.2 section.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
run-test-proxy.sh launches a local nginx that wraps nginx/default.conf
(a linuxserver-style server block) so you can test Codex behind a reverse
proxy under a url_path_prefix subpath.

Previously it regenerated the cert and all configs into a temp dir on
every run. Generate that scaffold once into nginx/test-proxy/ and check it
in: the self-signed localhost cert, the events/http wrapper, the ssl.conf
stand-in, and default.conf adapted for standalone use (80/443 -> 8080/8443,
absolute ssl include rewritten). A plain run now just launches nginx; only
the gitignored tmp/ working dir is created at run time. --regenerate
rebuilds the scaffold from default.conf after edits.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Eliminate the nginx/ directory:
  - nginx/run-test-proxy.sh -> bin/run-test-proxy.sh
  - nginx/test-proxy/       -> test-proxy/
  - drop nginx/default.conf; test-proxy/server.conf is now the committed,
    hand-edited source of truth (no more regenerate-from-default step)

Drop the Docker-based nginx dev proxy in favor of the native one:
  - remove the unused nginx service from compose.yaml
  - delete bin/dev-reverse-proxy.sh (was broken: referenced a missing
    nginx/nginx.yaml) and point `make dev-reverse-proxy` at run-test-proxy.sh

The script now just launches nginx against the committed test-proxy/ scaffold
(regenerating only the self-signed cert, and only with --new-cert or if
absent); edit test-proxy/server.conf to change ports/backend/routing.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
The localhost cert is throwaway and regenerated on first run by
bin/run-test-proxy.sh, so there's no need to publish it. Gitignore
cert.pem / cert.key and remove them from the repo.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
The location block defined its own proxy_set_header (Remote-User), which
made nginx drop every server-level proxy_set_header — including the
WebSocket Upgrade/Connection headers — so sockets never upgraded.

Move Remote-User up to the server block so the location inherits all the
headers, and fix two issues that inheritance then exposed:

  - Connection was hardcoded "Upgrade", which 400'd normal requests once
    actually applied; drive it from a $http_upgrade map ($connection_upgrade)
    so only real WS requests upgrade.
  - X-Forwarded-Host was $server_name ("_"); with codex's
    USE_X_FORWARDED_HOST=True that made Django 400 every dynamic request
    (DisallowedHost). Forward $host instead.

Verified through the proxy: normal HTTP/HTTPS 200, static 200, ws/wss
handshakes return 101 Switching Protocols.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Chrome flagged the tagging tab's single v-form for cramming multiple
credential groupings into one form. Split the Metron and Comic Vine
credential panels into their own sibling forms (the password-free
defaults sections keep the outer form), so each form maps to one save
action.

Add autocomplete to the credential/URL fields to keep these
third-party service secrets out of the browser password manager:
new-password on the password/API-key inputs (which Chrome honors where
it ignores off), off on the username and URL fields.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
* Add read-only library flag to block tag writes

Mark a Library read_only to protect its comics from all archive-mutating
operations (Edit Tags + Tag Online). Enforced server-side at the single
resolve_comic_pks funnel and again at the task executors; mixed selections
write only editable comics and report a skipped count. The metadata payload
gains an `editable` aggregate that hides the edit/tag buttons when nothing
in the selection is writable.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

* Fix undefined updateLabel warning in admin library table

The edit dialog bound :label="updateLabel", but updateLabel was never
defined on the component, so Vue warned on every render once a library
row existed. The dialog's label prop already falls back to the table
name, so drop the broken binding.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

---------

Co-authored-by: Claude Opus 4.8 <[email protected]>
@ajslater ajslater merged commit d3e4c2f into main Jun 23, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant