Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
063f2d2
fix(frontend): send passwordConfirm from profile change-password (#793)
ajslater Jul 3, 2026
3020d67
update deps and version to v2.1.1. bump news
ajslater Jul 3, 2026
27783ee
test(frontend): work around vitest/valid-expect false positive on exp…
ajslater Jul 4, 2026
4963ad9
update deps
ajslater Jul 4, 2026
2462a41
fix(onlinetag): drop dead effort option; model Metron's fewer requests
ajslater Jul 4, 2026
8245efa
trim news
ajslater Jul 4, 2026
6591384
Merge branch 'main' into develop
ajslater Jul 4, 2026
a1845af
refactor(onlinetag): derive source list + issue-id parser from comicbox
ajslater Jul 4, 2026
cd84ed9
fix(onlinetag): scope the match-mode request-count hint to Comic Vine
ajslater Jul 4, 2026
9980c04
fix(fs): don't let one unreadable folder crash the library scan
ajslater Jul 4, 2026
42b7fe9
update deps
ajslater Jul 4, 2026
dfbf390
update version to 2.1.2
ajslater Jul 4, 2026
1d06607
fix typechecking
ajslater Jul 4, 2026
07ffa2a
news for v2.1.2
ajslater Jul 4, 2026
ed38cb5
format
ajslater Jul 4, 2026
a41eede
Squashed commit of the following:
ajslater Jul 4, 2026
8ebc051
update deps
ajslater Jul 4, 2026
08eadba
refactor(onlinetag): extract resume-param sanitizer to cut complexity
ajslater Jul 4, 2026
227cea4
fix(onlinetag): reference defined constant in match-mode hint
ajslater Jul 4, 2026
76a7d3b
Merge branch 'main' into develop
ajslater Jul 4, 2026
97c62aa
update deps
ajslater Jul 7, 2026
f7abf7d
fix(settings): nest comicbox loglevel/delete_keys under general section
ajslater Jul 11, 2026
da257b0
fix(onlinetag): resolve prompts against current DB path, surface appl…
ajslater Jul 11, 2026
a5f37d8
update deps and format
ajslater Jul 11, 2026
e45ef90
Native OIDC single sign-on (Admin Auth tab) (#798)
ajslater Jul 13, 2026
d03666d
bump version 2.2.0
ajslater Jul 13, 2026
698164c
bump news
ajslater Jul 13, 2026
2de8473
update deps and comicbox
ajslater Jul 13, 2026
9243b5e
adapt ComicVine credential check to simyan v3 (comicbox 4.1.1)
ajslater Jul 13, 2026
4c97679
update deps
ajslater Jul 13, 2026
e6bb026
test(onlinetag): assert merge flag is forwarded, not comicbox's arith…
ajslater Jul 13, 2026
19f7b1b
Merge branch 'main' into develop
ajslater Jul 13, 2026
c7dc81f
update deps
ajslater Jul 19, 2026
cb98ea0
v2.2.1: show Metron account rate limits live (comicbox 4.3.0 / mokkar…
ajslater Jul 19, 2026
b3ec559
update deps
ajslater Jul 22, 2026
86b8403
v2.2.1: community ratings replace critical rating (comicbox 4.4.0)
ajslater Jul 22, 2026
04a7b2d
Merge branch 'main' into develop
ajslater Jul 22, 2026
4f7e516
update deps
ajslater Jul 24, 2026
f7ceb6c
v2.2.2: fix rotated pdf page serving (comicbox-pdffile 0.6.3)
ajslater Jul 24, 2026
8086c4c
update deps, including comicbox 4.5.0
ajslater Jul 24, 2026
00c3a99
Fix silent no-op when clearing tag editor fields
ajslater Jul 22, 2026
172a8c9
Fix lint errors from the ruff 0.16 upgrade
ajslater Jul 24, 2026
f1c8405
update comicbox
ajslater Jul 24, 2026
8be22e5
Clear monochrome by deleting the tag, not writing false
ajslater Jul 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@ width: 128px;
border-radius: 128px;
" />

## v2.2.2

- Fixes
- Clearing a field in the tag editor now removes it from the comic's tags.
Cleared fields were silently left unchanged in the archive (comicbox
4.5.0).
- The tag editor's Monochrome checkbox reads and writes again, and clearing
it removes the tag instead of marking the comic color (comicbox 4.5.1).
- PDF pages scanned upside down or sideways and righted by the pdf's
rotation attribute displayed rotated when the reader served them as images
(comicbox-pdffile 0.6.3).
- Reading a PDF that needs in-memory repair no longer rewrites the file on
disk (comicbox-pdffile 0.6.3).
- PDFs no longer count an embedded metadata file as a page (comicbox 4.5.0).
Imported PDFs correct themselves on their next re-import, or use Force
Update Tags.

## v2.2.1

- Features
Expand Down
18 changes: 14 additions & 4 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion codex/librarian/bookmark/latest_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class CodexLatestVersionUpdater(WorkerStatusBase):
@staticmethod
def _fetch_latest_version():
"""Fetch Latest Remotely."""
response = urlopen(_REPO_URL, timeout=_REPO_TIMEOUT) # noqa: S310
response = urlopen(_REPO_URL, timeout=_REPO_TIMEOUT)
source = response.read()
decoded_source = source.decode("utf-8")
return json.loads(decoded_source)["info"]["version"]
Expand Down
3 changes: 1 addition & 2 deletions codex/librarian/scribe/search/prepare.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,7 @@ def prepare_import_fts_entry(
"""Prepare ComicFTS object from import data."""
cls._create_comicfts_entry_m2ms(entry, existing_m2m_values)
cls._create_comicfts_entry_fks(entry)
for field_name in entry:
value = entry[field_name]
for field_name, value in entry.items():
if isinstance(value, tuple):
entry[field_name] = ",".join(sorted(value))

Expand Down
16 changes: 13 additions & 3 deletions codex/librarian/scribe/tag_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,20 +72,22 @@ def _build_items(
"""Build BulkWriteItem list from task data."""
formats = frozenset(task.formats) if task.formats else None
mode = cast("Mode", task.mode)
delete_keys = frozenset(task.delete_keys) if task.delete_keys else None
items = []
for pk, path in comic_paths.items():
if task.per_comic_patches and pk in task.per_comic_patches:
patch = task.per_comic_patches[pk]
elif task.patch:
patch = task.patch
else:
patch = task.patch or {}
if not patch and not delete_keys:
continue
items.append(
BulkWriteItem(
path=path,
patch=patch,
mode=mode,
formats=formats,
delete_keys=delete_keys,
)
)
return items
Expand Down Expand Up @@ -113,7 +115,15 @@ def _reimport_unwatched(self, comic_paths: dict[int, Path]) -> None:

@staticmethod
def _build_base_config(task: BulkTagWriteTask):
"""Return a config with delete_orig set, or None when not deleting."""
"""
Return a config with delete_orig set, or None when not deleting.

Never pass codex's read-side ``COMICBOX_CONFIG`` here. Comicbox
unions a write's ``delete_keys`` with the base config's, and that
config carries the big parse-skip set of schema fields codex
doesn't consume (pages, reprints, cover_image, ...). Using it as a
write base would strip every one of those from the user's archive.
"""
if not task.delete_original:
return None
cfg = get_config()
Expand Down
4 changes: 4 additions & 0 deletions codex/librarian/scribe/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ class BulkTagWriteTask(ScribeTask):
delete_original: bool = False
patch: dict[str, Any] | None = None
per_comic_patches: dict[int, dict[str, Any]] = field(default_factory=dict)
# Comicbox glom key paths to clear from each archive's metadata. Merging
# can only add or replace (empty patch values are pruned on schema load),
# so cleared tag-editor fields travel here, not in the patch.
delete_keys: tuple[str, ...] = ()
# When True, rename each written archive to the comicbox (comicfn2dict)
# filename scheme after its tags are written. With no patch (rename-only),
# every resolved comic is renamed from its existing on-archive metadata.
Expand Down
2 changes: 1 addition & 1 deletion codex/mail.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
class DBEmailBackend(EmailBackend):
"""SMTP backend that sources connection params from the DB on init."""

def __init__( # noqa: PLR0913 - signature mirrors SMTPBackend.__init__
def __init__( # noqa: PLR0913, PLR0917 - signature mirrors SMTPBackend.__init__
self,
host=None,
port=None,
Expand Down
3 changes: 3 additions & 0 deletions codex/serializers/admin/tagging.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ class TagWriteRequestSerializer(Serializer):
collection = CharField()
pks = ListField(child=CharField())
patch = CharField(required=False, default="")
# Comicbox key paths to clear; separate from the patch because a merge
# write can only add or replace values, never remove them.
delete_keys = ListField(child=CharField(), required=False, default=list)
mode = CharField(required=False, default="update")
formats = ListField(child=CharField(), required=False, default=["COMIC_INFO"])
delete_original = BooleanField(required=False, default=None)
Expand Down
31 changes: 26 additions & 5 deletions codex/views/admin/tagwrite.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import json
from collections.abc import Sequence
from dataclasses import replace
from pathlib import Path
from types import MappingProxyType
from typing import override
Expand Down Expand Up @@ -240,7 +241,7 @@ class AdminTagWritePreflightView(FilteredComicPksView):
"""Check how many comics need conversion before writing."""

@staticmethod
def _preview_one(old_path: Path, metadata: dict | None) -> str:
def _preview_one(old_path: Path, metadata: dict | None, config) -> str:
"""
Return the comicbox-scheme name the given patch produces for one comic.

Expand All @@ -250,17 +251,31 @@ def _preview_one(old_path: Path, metadata: dict | None) -> str:
the archive (I/O). Returns "" when no name could be built.
"""
try:
with Comicbox(old_path, config=COMICBOX_CONFIG, metadata=metadata) as car:
with Comicbox(old_path, config=config, metadata=metadata) as car:
return car.to_string(MetadataFormats.FILENAME) or ""
except Exception:
return ""

def _filename_previews(
self, comic_pks: frozenset[int], patch_str: str
self,
comic_pks: frozenset[int],
patch_str: str,
delete_keys: tuple[str, ...] = (),
) -> list[dict[str, str]]:
"""Preview the rename (old → new) for each selected comic, capped."""
patch = json.loads(patch_str or "null")
metadata = {"comicbox": patch} if patch else None
# Pending cleared fields must vanish from the previewed name exactly
# as the real write (BulkWriteItem.delete_keys) will clear them.
config = COMICBOX_CONFIG
if delete_keys:
config = replace(
config,
general=replace(
config.general,
delete_keys=config.general.delete_keys | frozenset(delete_keys),
),
)
comics = (
Comic.objects.filter(pk__in=comic_pks)
.only("pk", "path")
Expand All @@ -270,7 +285,10 @@ def _filename_previews(
for comic in comics:
old_path = Path(comic.path)
previews.append(
{"old": old_path.name, "new": self._preview_one(old_path, metadata)}
{
"old": old_path.name,
"new": self._preview_one(old_path, metadata, config),
}
)
return previews

Expand Down Expand Up @@ -306,7 +324,9 @@ def post(self, request):
"delete_original": delete_original,
"rename": rename,
"filename_previews": self._filename_previews(
comic_pks, data.get("patch") or ""
comic_pks,
data.get("patch") or "",
tuple(data.get("delete_keys") or ()),
),
"skipped": self.skipped_read_only,
}
Expand Down Expand Up @@ -346,6 +366,7 @@ def post(self, request):
task = BulkTagWriteTask(
comic_pks=comic_pks,
patch=json.loads(data.get("patch") or "null"),
delete_keys=tuple(data.get("delete_keys") or ()),
mode=data["mode"],
formats=tuple(data["formats"]),
delete_original=delete_original,
Expand Down
2 changes: 1 addition & 1 deletion codex/views/browser/breadcrumbs.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def __init__(self, *args, **kwargs) -> None:
"""Set params for the type checker."""
super().__init__(*args, **kwargs)
# Use 0 to indicate unmemoized because None is a valid value
self._collection_instance: BrowserCollectionModel | None | int = 0
self._collection_instance: BrowserCollectionModel | int | None = 0

def _get_collection_query(self, model):
"""Get the collection query for the collection instance."""
Expand Down
2 changes: 1 addition & 1 deletion codex/views/error.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

def codex_exception_handler(
exc, context
) -> JsonResponse | None | HttpResponseRedirect | Response:
) -> JsonResponse | HttpResponseRedirect | Response | None:
"""Assume OPDS clients want redirects instead of errors."""
response = None
request = context.get("request")
Expand Down
2 changes: 1 addition & 1 deletion codex/views/opds/error.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def _get_redirect_to_start_response(request: HttpRequest) -> HttpResponseRedirec

def codex_opds_exception_handler(
exc, context
) -> JsonResponse | None | HttpResponseRedirect | Response:
) -> JsonResponse | HttpResponseRedirect | Response | None:
"""
Assume OPDS clients want redirects instead of errors.

Expand Down
Loading