diff --git a/crdt-sync/.pre-commit-config.yaml b/crdt-sync/.pre-commit-config.yaml index c97dca6..b65407a 100644 --- a/crdt-sync/.pre-commit-config.yaml +++ b/crdt-sync/.pre-commit-config.yaml @@ -65,7 +65,7 @@ repos: types: [python] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.2 + rev: v0.16.7 hooks: - id: ruff args: [--fix, --unsafe-fixes, --exit-non-zero-on-fix, --show-fixes] diff --git a/crdt-sync/crdt_sync/__init__.py b/crdt-sync/crdt_sync/__init__.py index 4b94eb3..61526aa 100644 --- a/crdt-sync/crdt_sync/__init__.py +++ b/crdt-sync/crdt_sync/__init__.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Shared CRDT merge scheme + pluggable remote sync transports.""" from __future__ import annotations diff --git a/crdt-sync/crdt_sync/_autherrors.py b/crdt-sync/crdt_sync/_autherrors.py index 25e352b..e31bc11 100644 --- a/crdt-sync/crdt_sync/_autherrors.py +++ b/crdt-sync/crdt_sync/_autherrors.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Turning a Firebase auth response into a message, and reading its verdict. Split from :mod:`crdt_sync._firebase_auth`, which keeps the token provider. diff --git a/crdt-sync/crdt_sync/_config.py b/crdt-sync/crdt_sync/_config.py index 6cafa44..f2113ea 100644 --- a/crdt-sync/crdt_sync/_config.py +++ b/crdt-sync/crdt_sync/_config.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """The one place every app learns how to reach Firebase. Before this module each caller re-read ``~/.config/crdt-sync/firebase.json`` diff --git a/crdt-sync/crdt_sync/_credentials.py b/crdt-sync/crdt_sync/_credentials.py index 33da087..f87ec87 100644 --- a/crdt-sync/crdt_sync/_credentials.py +++ b/crdt-sync/crdt_sync/_credentials.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Firebase credentials and the stores that persist them. Split from :mod:`crdt_sync._firebase_auth`, which keeps the token provider. diff --git a/crdt-sync/crdt_sync/_device.py b/crdt-sync/crdt_sync/_device.py index 091c511..9b7edca 100644 --- a/crdt-sync/crdt_sync/_device.py +++ b/crdt-sync/crdt_sync/_device.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """This device's stable sync identity. Every device that pushes into a namespace needs an id that is *its own*: it diff --git a/crdt-sync/crdt_sync/_fberrors.py b/crdt-sync/crdt_sync/_fberrors.py index 98ae6f2..3623ca0 100644 --- a/crdt-sync/crdt_sync/_fberrors.py +++ b/crdt-sync/crdt_sync/_fberrors.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """The errors the Firebase sync backend raises. Split from :mod:`crdt_sync._firebase` for the 250-line cap, mirroring diff --git a/crdt-sync/crdt_sync/_fbkeys.py b/crdt-sync/crdt_sync/_fbkeys.py index c287903..a953734 100644 --- a/crdt-sync/crdt_sync/_fbkeys.py +++ b/crdt-sync/crdt_sync/_fbkeys.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Escaping arbitrary strings into Firebase Realtime Database keys. Split from :mod:`crdt_sync._firebase`, which keeps the client. RTDB forbids diff --git a/crdt-sync/crdt_sync/_firebase.py b/crdt-sync/crdt_sync/_firebase.py index 05a05aa..57b3dba 100644 --- a/crdt-sync/crdt_sync/_firebase.py +++ b/crdt-sync/crdt_sync/_firebase.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Firebase Realtime Database as dumb keyed storage, over its REST API. The RTDB REST endpoints are plain HTTPS, so this works identically under diff --git a/crdt-sync/crdt_sync/_firebase_auth.py b/crdt-sync/crdt_sync/_firebase_auth.py index edd6c0b..d5d129a 100644 --- a/crdt-sync/crdt_sync/_firebase_auth.py +++ b/crdt-sync/crdt_sync/_firebase_auth.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Firebase Authentication over its REST API, with no SDK dependency. The Firebase Admin SDK authenticates with a service-account key, which diff --git a/crdt-sync/crdt_sync/_gherrors.py b/crdt-sync/crdt_sync/_gherrors.py index e11667c..464e2c0 100644 --- a/crdt-sync/crdt_sync/_gherrors.py +++ b/crdt-sync/crdt_sync/_gherrors.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """The errors the GitHub sync backend raises. Split from :mod:`crdt_sync._github` for the 250-line cap. Kept separate so a diff --git a/crdt-sync/crdt_sync/_github.py b/crdt-sync/crdt_sync/_github.py index ce88359..b79fd67 100644 --- a/crdt-sync/crdt_sync/_github.py +++ b/crdt-sync/crdt_sync/_github.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Minimal GitHub Contents API client used as dumb file storage. GitHub is used purely as file storage via the REST Contents API, not a git diff --git a/crdt-sync/crdt_sync/_hlc.py b/crdt-sync/crdt_sync/_hlc.py index 3cbe07a..c99dc35 100644 --- a/crdt-sync/crdt_sync/_hlc.py +++ b/crdt-sync/crdt_sync/_hlc.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Hybrid Logical Clock: a totally-ordered, monotonic per-node timestamp. An HLC combines wall-clock time with a logical counter so that two ticks diff --git a/crdt-sync/crdt_sync/_http.py b/crdt-sync/crdt_sync/_http.py index 259d15a..9111b07 100644 --- a/crdt-sync/crdt_sync/_http.py +++ b/crdt-sync/crdt_sync/_http.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Pooled HTTP sessions, so every request does not re-handshake. Every call site in this package used the module-level ``requests.get`` / diff --git a/crdt-sync/crdt_sync/_log.py b/crdt-sync/crdt_sync/_log.py index 57710b6..13bf5f3 100644 --- a/crdt-sync/crdt_sync/_log.py +++ b/crdt-sync/crdt_sync/_log.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Log: a collection of Records keyed by id, with a union-merge.""" from __future__ import annotations diff --git a/crdt-sync/crdt_sync/_mirror.py b/crdt-sync/crdt_sync/_mirror.py index d5598cb..f525220 100644 --- a/crdt-sync/crdt_sync/_mirror.py +++ b/crdt-sync/crdt_sync/_mirror.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """A remote store that writes to two backends and reads from both. Exists for the GitHub -> Firebase cutover: each app can move to Firebase diff --git a/crdt-sync/crdt_sync/_pull.py b/crdt-sync/crdt_sync/_pull.py index ef9f40a..a4e089f 100644 --- a/crdt-sync/crdt_sync/_pull.py +++ b/crdt-sync/crdt_sync/_pull.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Downloading peers' logs, skipping the ones we already have. Split from :mod:`crdt_sync._sync`, which keeps the push half and the diff --git a/crdt-sync/crdt_sync/_record.py b/crdt-sync/crdt_sync/_record.py index 3fa02c8..b0eed82 100644 --- a/crdt-sync/crdt_sync/_record.py +++ b/crdt-sync/crdt_sync/_record.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Record: a per-field LWW map with a sticky (monotonic) delete flag. This is the unit CRDT merges operate on. It's deliberately domain-agnostic -- diff --git a/crdt-sync/crdt_sync/_remote.py b/crdt-sync/crdt_sync/_remote.py index 55e6986..c29eb3e 100644 --- a/crdt-sync/crdt_sync/_remote.py +++ b/crdt-sync/crdt_sync/_remote.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Backend-neutral remote-storage contract that sync talks through. Deliberately tiny: :func:`crdt_sync.sync_log` and every app-level sync diff --git a/crdt-sync/crdt_sync/_revisions.py b/crdt-sync/crdt_sync/_revisions.py index 5b955cb..a090e5a 100644 --- a/crdt-sync/crdt_sync/_revisions.py +++ b/crdt-sync/crdt_sync/_revisions.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """The revision string a device publishes alongside its log. Split from :mod:`crdt_sync._sync` so both the push and pull halves can import diff --git a/crdt-sync/crdt_sync/_store.py b/crdt-sync/crdt_sync/_store.py index c0b879d..3ce3006 100644 --- a/crdt-sync/crdt_sync/_store.py +++ b/crdt-sync/crdt_sync/_store.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Local JSON persistence for a ``Log``. Mirrors ``crdt_sync_dart``'s ``logToJson``/``FileLogPersistence`` serialization diff --git a/crdt-sync/crdt_sync/_sync.py b/crdt-sync/crdt_sync/_sync.py index ea9c3ed..e1effe5 100644 --- a/crdt-sync/crdt_sync/_sync.py +++ b/crdt-sync/crdt_sync/_sync.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Domain-agnostic pull/merge/push sync orchestration. Generalizes diet_guard's original ``_sync.py`` (pull every other device's diff --git a/crdt-sync/crdt_sync/_syncargs.py b/crdt-sync/crdt_sync/_syncargs.py index 356a799..c6a47be 100644 --- a/crdt-sync/crdt_sync/_syncargs.py +++ b/crdt-sync/crdt_sync/_syncargs.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """The three argument bundles :func:`crdt_sync.sync_log` takes. Split from :mod:`crdt_sync._sync` for the 250-line cap. Grouping sync_log's diff --git a/crdt-sync/crdt_sync/_syncstate.py b/crdt-sync/crdt_sync/_syncstate.py index 087d940..7b620ac 100644 --- a/crdt-sync/crdt_sync/_syncstate.py +++ b/crdt-sync/crdt_sync/_syncstate.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Where a device records what it has already pushed and pulled. Split from :mod:`crdt_sync._sync`, which keeps the sync itself. The state is diff --git a/crdt-sync/crdt_sync/tests/__init__.py b/crdt-sync/crdt_sync/tests/__init__.py index 72c1224..9245f86 100644 --- a/crdt-sync/crdt_sync/tests/__init__.py +++ b/crdt-sync/crdt_sync/tests/__init__.py @@ -1 +1,2 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for the crdt_sync package.""" diff --git a/crdt-sync/crdt_sync/tests/conftest.py b/crdt-sync/crdt_sync/tests/conftest.py index efc6dc3..e947f4b 100644 --- a/crdt-sync/crdt_sync/tests/conftest.py +++ b/crdt-sync/crdt_sync/tests/conftest.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Shared pytest fixtures for crdt_sync's test suite.""" from __future__ import annotations diff --git a/crdt-sync/crdt_sync/tests/test_config.py b/crdt-sync/crdt_sync/tests/test_config.py index b139e8b..5fb69f0 100644 --- a/crdt-sync/crdt_sync/tests/test_config.py +++ b/crdt-sync/crdt_sync/tests/test_config.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for the shared Firebase configuration loader. Every failure mode here is one that would otherwise surface as an diff --git a/crdt-sync/crdt_sync/tests/test_config_client.py b/crdt-sync/crdt_sync/tests/test_config_client.py index 734708b..6a35ff1 100644 --- a/crdt-sync/crdt_sync/tests/test_config_client.py +++ b/crdt-sync/crdt_sync/tests/test_config_client.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for building a sync client from a config file. Split from ``test_config.py`` (250-line cap); diff --git a/crdt-sync/crdt_sync/tests/test_config_credentials.py b/crdt-sync/crdt_sync/tests/test_config_credentials.py index a6e11de..808a2dd 100644 --- a/crdt-sync/crdt_sync/tests/test_config_credentials.py +++ b/crdt-sync/crdt_sync/tests/test_config_credentials.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for reading the sync password and building a client from config. Split from ``test_config.py`` (250-line cap), which keeps loading and diff --git a/crdt-sync/crdt_sync/tests/test_device.py b/crdt-sync/crdt_sync/tests/test_device.py index e0bc7e8..d64def1 100644 --- a/crdt-sync/crdt_sync/tests/test_device.py +++ b/crdt-sync/crdt_sync/tests/test_device.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for this device's persisted sync identity.""" from __future__ import annotations diff --git a/crdt-sync/crdt_sync/tests/test_firebase.py b/crdt-sync/crdt_sync/tests/test_firebase.py index 76b51c4..78efdc3 100644 --- a/crdt-sync/crdt_sync/tests/test_firebase.py +++ b/crdt-sync/crdt_sync/tests/test_firebase.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for the Realtime Database sync client. The HTTP layer is fully mocked, so every branch -- success, absent path, diff --git a/crdt-sync/crdt_sync/tests/test_firebase_auth.py b/crdt-sync/crdt_sync/tests/test_firebase_auth.py index 85934a6..945ac8a 100644 --- a/crdt-sync/crdt_sync/tests/test_firebase_auth.py +++ b/crdt-sync/crdt_sync/tests/test_firebase_auth.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for Firebase Authentication over the REST API. The HTTP layer is fully mocked, so every branch -- sign-in, refresh, rotated diff --git a/crdt-sync/crdt_sync/tests/test_firebase_auth_google.py b/crdt-sync/crdt_sync/tests/test_firebase_auth_google.py index 69f93ed..dda47b6 100644 --- a/crdt-sync/crdt_sync/tests/test_firebase_auth_google.py +++ b/crdt-sync/crdt_sync/tests/test_firebase_auth_google.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for the Google sign-in path. Split from ``test_firebase_auth.py`` (250-line cap), which keeps credential diff --git a/crdt-sync/crdt_sync/tests/test_firebase_auth_session.py b/crdt-sync/crdt_sync/tests/test_firebase_auth_session.py index 355b638..848d282 100644 --- a/crdt-sync/crdt_sync/tests/test_firebase_auth_session.py +++ b/crdt-sync/crdt_sync/tests/test_firebase_auth_session.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for the auth session lifecycle. Split from ``test_firebase_auth.py`` (250-line cap); diff --git a/crdt-sync/crdt_sync/tests/test_firebase_auth_tokens.py b/crdt-sync/crdt_sync/tests/test_firebase_auth_tokens.py index 73aa0dd..0deba54 100644 --- a/crdt-sync/crdt_sync/tests/test_firebase_auth_tokens.py +++ b/crdt-sync/crdt_sync/tests/test_firebase_auth_tokens.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for ID-token refresh and the session lifecycle. Split from ``test_firebase_auth.py`` (250-line cap), which keeps credential diff --git a/crdt-sync/crdt_sync/tests/test_firebase_writes.py b/crdt-sync/crdt_sync/tests/test_firebase_writes.py index 7702250..073efac 100644 --- a/crdt-sync/crdt_sync/tests/test_firebase_writes.py +++ b/crdt-sync/crdt_sync/tests/test_firebase_writes.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for Firebase writes, string maps and deletion. Split from ``test_firebase.py`` (250-line cap), which keeps key escaping diff --git a/crdt-sync/crdt_sync/tests/test_github.py b/crdt-sync/crdt_sync/tests/test_github.py index 61ba678..45f4c87 100644 --- a/crdt-sync/crdt_sync/tests/test_github.py +++ b/crdt-sync/crdt_sync/tests/test_github.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for the GitHub Contents API sync client. The HTTP layer is fully mocked (``requests.get``/``requests.put``), so every diff --git a/crdt-sync/crdt_sync/tests/test_github_writes.py b/crdt-sync/crdt_sync/tests/test_github_writes.py index 6039245..f981dfd 100644 --- a/crdt-sync/crdt_sync/tests/test_github_writes.py +++ b/crdt-sync/crdt_sync/tests/test_github_writes.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for GitHub writes and access checks. Split from ``test_github.py`` (250-line cap), which keeps the read paths. diff --git a/crdt-sync/crdt_sync/tests/test_hlc.py b/crdt-sync/crdt_sync/tests/test_hlc.py index 68b9c50..656764b 100644 --- a/crdt-sync/crdt_sync/tests/test_hlc.py +++ b/crdt-sync/crdt_sync/tests/test_hlc.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for the Hybrid Logical Clock.""" from __future__ import annotations diff --git a/crdt-sync/crdt_sync/tests/test_log.py b/crdt-sync/crdt_sync/tests/test_log.py index 2ef6837..4aa559e 100644 --- a/crdt-sync/crdt_sync/tests/test_log.py +++ b/crdt-sync/crdt_sync/tests/test_log.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for merge_logs, including its CRDT convergence properties.""" from __future__ import annotations diff --git a/crdt-sync/crdt_sync/tests/test_mirror.py b/crdt-sync/crdt_sync/tests/test_mirror.py index 40ce11e..174fa7f 100644 --- a/crdt-sync/crdt_sync/tests/test_mirror.py +++ b/crdt-sync/crdt_sync/tests/test_mirror.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for the dual-write client used during the GitHub -> Firebase cutover. The asymmetry is the whole point and is what these assert: a primary failure diff --git a/crdt-sync/crdt_sync/tests/test_mirror_fallback.py b/crdt-sync/crdt_sync/tests/test_mirror_fallback.py index e789e59..fc09f53 100644 --- a/crdt-sync/crdt_sync/tests/test_mirror_fallback.py +++ b/crdt-sync/crdt_sync/tests/test_mirror_fallback.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for mirror fallback reads, revision maps and lifecycle. Split from ``test_mirror.py`` (250-line cap), which keeps plain writes, diff --git a/crdt-sync/crdt_sync/tests/test_record.py b/crdt-sync/crdt_sync/tests/test_record.py index 7b3e9df..5ed0253 100644 --- a/crdt-sync/crdt_sync/tests/test_record.py +++ b/crdt-sync/crdt_sync/tests/test_record.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for Record, merge_field, and merge_record.""" from __future__ import annotations diff --git a/crdt-sync/crdt_sync/tests/test_store.py b/crdt-sync/crdt_sync/tests/test_store.py index 2a916c4..193da8b 100644 --- a/crdt-sync/crdt_sync/tests/test_store.py +++ b/crdt-sync/crdt_sync/tests/test_store.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for local Log persistence (dump/load and atomic file read/write).""" from __future__ import annotations diff --git a/crdt-sync/crdt_sync/tests/test_sync.py b/crdt-sync/crdt_sync/tests/test_sync.py index e1b94a9..2bfc450 100644 --- a/crdt-sync/crdt_sync/tests/test_sync.py +++ b/crdt-sync/crdt_sync/tests/test_sync.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for the pull/merge/push sync orchestration.""" from __future__ import annotations diff --git a/crdt-sync/crdt_sync/tests/test_sync_revisions.py b/crdt-sync/crdt_sync/tests/test_sync_revisions.py index ac43e4c..a62b035 100644 --- a/crdt-sync/crdt_sync/tests/test_sync_revisions.py +++ b/crdt-sync/crdt_sync/tests/test_sync_revisions.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for the revision tracking that keeps sync inside the free tier. Two savings, both measured against the GitHub-backed history this replaces: diff --git a/crdt-sync/crdt_sync/tests/test_sync_revisions_backends.py b/crdt-sync/crdt_sync/tests/test_sync_revisions_backends.py index 67396b0..5d470a9 100644 --- a/crdt-sync/crdt_sync/tests/test_sync_revisions_backends.py +++ b/crdt-sync/crdt_sync/tests/test_sync_revisions_backends.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for backends without bulk map reads, and publishing order. Split from ``test_sync_revisions.py`` (250-line cap); diff --git a/crdt-sync/crdt_sync/tests/test_sync_revisions_peers.py b/crdt-sync/crdt_sync/tests/test_sync_revisions_peers.py index 31e2e82..3d18801 100644 --- a/crdt-sync/crdt_sync/tests/test_sync_revisions_peers.py +++ b/crdt-sync/crdt_sync/tests/test_sync_revisions_peers.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for revision handling across peers. Split from ``test_sync_revisions.py`` (250-line cap), which keeps revision diff --git a/crdt-sync/crdt_sync/tests/test_sync_skips.py b/crdt-sync/crdt_sync/tests/test_sync_skips.py index 612e5e5..299d210 100644 --- a/crdt-sync/crdt_sync/tests/test_sync_skips.py +++ b/crdt-sync/crdt_sync/tests/test_sync_skips.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for the peer files sync_log declines to merge. Split from ``test_sync.py`` (250-line cap). A peer whose pushed file is diff --git a/crdt-sync/crdt_sync/tests/test_tool_google_id_token.py b/crdt-sync/crdt_sync/tests/test_tool_google_id_token.py index 84b19fb..2b7405f 100644 --- a/crdt-sync/crdt_sync/tests/test_tool_google_id_token.py +++ b/crdt-sync/crdt_sync/tests/test_tool_google_id_token.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for ``tool/google_id_token``, the loopback OAuth ID-token minter. No network and no browser. The callback server is replaced wholesale so the diff --git a/crdt-sync/crdt_sync/tests/test_tool_google_id_token_cli.py b/crdt-sync/crdt_sync/tests/test_tool_google_id_token_cli.py index abbc6a9..7c801bd 100644 --- a/crdt-sync/crdt_sync/tests/test_tool_google_id_token_cli.py +++ b/crdt-sync/crdt_sync/tests/test_tool_google_id_token_cli.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for ``tool/google_id_token``'s command line entry point. Split from :mod:`test_tool_google_id_token` to stay under the repo's 250-line diff --git a/crdt-sync/crdt_sync/tests/test_tool_interop_seed.py b/crdt-sync/crdt_sync/tests/test_tool_interop_seed.py index 9543a10..ab4eda3 100644 --- a/crdt-sync/crdt_sync/tests/test_tool_interop_seed.py +++ b/crdt-sync/crdt_sync/tests/test_tool_interop_seed.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for ``tool/interop_seed``, the Python half of the interop check. Patched at the module's own boundary -- ``firebase_client_for``, diff --git a/crdt-sync/crdt_sync/tests/test_tool_link_google.py b/crdt-sync/crdt_sync/tests/test_tool_link_google.py index f7d70fd..da21618 100644 --- a/crdt-sync/crdt_sync/tests/test_tool_link_google.py +++ b/crdt-sync/crdt_sync/tests/test_tool_link_google.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for token inspection and linking in ``tool/link_google``. ``main()`` and ``_read_token`` are covered in diff --git a/crdt-sync/crdt_sync/tests/test_tool_link_google_main.py b/crdt-sync/crdt_sync/tests/test_tool_link_google_main.py index bacabaa..280c6b8 100644 --- a/crdt-sync/crdt_sync/tests/test_tool_link_google_main.py +++ b/crdt-sync/crdt_sync/tests/test_tool_link_google_main.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for ``_read_token`` and ``main()`` in ``tool/link_google``. Split from :mod:`test_tool_link_google` to stay under the 250-line cap. diff --git a/crdt-sync/crdt_sync/tests/test_tool_link_google_token.py b/crdt-sync/crdt_sync/tests/test_tool_link_google_token.py index 8fcf7b6..cf1b62a 100644 --- a/crdt-sync/crdt_sync/tests/test_tool_link_google_token.py +++ b/crdt-sync/crdt_sync/tests/test_tool_link_google_token.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for ``describe_google_token`` in ``tool/link_google``. Split from :mod:`test_tool_link_google` (which covers linking) and diff --git a/crdt-sync/crdt_sync/tests/test_tool_migrate_github.py b/crdt-sync/crdt_sync/tests/test_tool_migrate_github.py index 0f0141b..87d404d 100644 --- a/crdt-sync/crdt_sync/tests/test_tool_migrate_github.py +++ b/crdt-sync/crdt_sync/tests/test_tool_migrate_github.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for the GitHub-reading half of ``tool/migrate_github_to_firebase``. ``main()`` is covered in :mod:`test_tool_migrate_github_main`, split to stay diff --git a/crdt-sync/crdt_sync/tests/test_tool_migrate_github_main.py b/crdt-sync/crdt_sync/tests/test_tool_migrate_github_main.py index 6808e1b..f1bdcc3 100644 --- a/crdt-sync/crdt_sync/tests/test_tool_migrate_github_main.py +++ b/crdt-sync/crdt_sync/tests/test_tool_migrate_github_main.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for ``main()`` in ``tool/migrate_github_to_firebase``. Split from :mod:`test_tool_migrate_github` to stay under the 250-line cap. diff --git a/crdt-sync/crdt_sync/tests/test_tool_oauth_callback.py b/crdt-sync/crdt_sync/tests/test_tool_oauth_callback.py index 47d3070..4a47548 100644 --- a/crdt-sync/crdt_sync/tests/test_tool_oauth_callback.py +++ b/crdt-sync/crdt_sync/tests/test_tool_oauth_callback.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for the one-shot OAuth redirect catcher in ``tool/_oauth_callback``. No network access: everything binds 127.0.0.1 on a port the module itself diff --git a/crdt-sync/crdt_sync/tests/test_tool_preflight_firebase.py b/crdt-sync/crdt_sync/tests/test_tool_preflight_firebase.py index 15367e7..75f4b22 100644 --- a/crdt-sync/crdt_sync/tests/test_tool_preflight_firebase.py +++ b/crdt-sync/crdt_sync/tests/test_tool_preflight_firebase.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for the individual checks in ``tool/preflight_firebase``. ``main()`` and the loading helpers are covered in diff --git a/crdt-sync/crdt_sync/tests/test_tool_preflight_firebase_main.py b/crdt-sync/crdt_sync/tests/test_tool_preflight_firebase_main.py index ba2b527..188b4d1 100644 --- a/crdt-sync/crdt_sync/tests/test_tool_preflight_firebase_main.py +++ b/crdt-sync/crdt_sync/tests/test_tool_preflight_firebase_main.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for ``tool/preflight_firebase``'s loaders and ``main()``. Split from :mod:`test_tool_preflight_firebase` to stay under the 250-line file diff --git a/crdt-sync/crdt_sync/tests/test_tool_seed_session.py b/crdt-sync/crdt_sync/tests/test_tool_seed_session.py index 2e8d120..b3fa9ae 100644 --- a/crdt-sync/crdt_sync/tests/test_tool_seed_session.py +++ b/crdt-sync/crdt_sync/tests/test_tool_seed_session.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for ``seed_apps`` in ``tool/seed_session``. ``main()`` is covered in :mod:`test_tool_seed_session_main`, split to stay diff --git a/crdt-sync/crdt_sync/tests/test_tool_seed_session_main.py b/crdt-sync/crdt_sync/tests/test_tool_seed_session_main.py index 5269268..46a543f 100644 --- a/crdt-sync/crdt_sync/tests/test_tool_seed_session_main.py +++ b/crdt-sync/crdt_sync/tests/test_tool_seed_session_main.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for ``main()`` in ``tool/seed_session``. Split from :mod:`test_tool_seed_session` to stay under the 250-line cap. diff --git a/crdt-sync/tool/__init__.py b/crdt-sync/tool/__init__.py index 971c6eb..0815a6a 100644 --- a/crdt-sync/tool/__init__.py +++ b/crdt-sync/tool/__init__.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """One-shot operational scripts for crdt_sync. A package rather than a loose directory so the linter treats these as real diff --git a/crdt-sync/tool/_oauth_callback.py b/crdt-sync/tool/_oauth_callback.py index 0bf3967..68686b9 100644 --- a/crdt-sync/tool/_oauth_callback.py +++ b/crdt-sync/tool/_oauth_callback.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """The one-shot local HTTP server that catches Google's OAuth redirect. Split from :mod:`google_id_token`, which keeps the token exchange. Google diff --git a/crdt-sync/tool/_seeded_apps.py b/crdt-sync/tool/_seeded_apps.py index 1c9fd6c..c885031 100644 --- a/crdt-sync/tool/_seeded_apps.py +++ b/crdt-sync/tool/_seeded_apps.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Which apps keep a desktop Firebase session, and why the list is hand-kept. Split out of ``seed_session.py`` for the 250-line cap. The rationale is worth diff --git a/crdt-sync/tool/google_id_token.py b/crdt-sync/tool/google_id_token.py index 7d3e5c2..548cd24 100644 --- a/crdt-sync/tool/google_id_token.py +++ b/crdt-sync/tool/google_id_token.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki r"""Fetch a Google ID token for this project, via a local loopback OAuth flow. ``link_google.py`` needs a Google ID token whose ``aud`` claim is an OAuth diff --git a/crdt-sync/tool/interop_seed.py b/crdt-sync/tool/interop_seed.py index 2fa986f..c1c3337 100644 --- a/crdt-sync/tool/interop_seed.py +++ b/crdt-sync/tool/interop_seed.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Write the Python half of the live cross-language interop check. ``crdt_sync_dart/tool/interop_check.dart`` merges a Dart record into diff --git a/crdt-sync/tool/link_google.py b/crdt-sync/tool/link_google.py index a0e3091..a9a1f35 100644 --- a/crdt-sync/tool/link_google.py +++ b/crdt-sync/tool/link_google.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Link a Google identity to the existing sync account, keeping the same uid. The security rules pin exactly one uid (``auth.uid === ''``), so signing diff --git a/crdt-sync/tool/migrate_github_to_firebase.py b/crdt-sync/tool/migrate_github_to_firebase.py index 94b4c14..58bf22f 100644 --- a/crdt-sync/tool/migrate_github_to_firebase.py +++ b/crdt-sync/tool/migrate_github_to_firebase.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """One-shot migration of the GitHub-backed sync repo into Firebase RTDB. Reads through the GitHub API rather than a local clone: a checkout goes stale diff --git a/crdt-sync/tool/preflight_firebase.py b/crdt-sync/tool/preflight_firebase.py index 6a14550..fd2fbd9 100644 --- a/crdt-sync/tool/preflight_firebase.py +++ b/crdt-sync/tool/preflight_firebase.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Validate ``~/.config/crdt-sync`` before anything is migrated. Every failure this catches is one that would otherwise surface *after* data diff --git a/crdt-sync/tool/seed_session.py b/crdt-sync/tool/seed_session.py index 68c4520..3651243 100644 --- a/crdt-sync/tool/seed_session.py +++ b/crdt-sync/tool/seed_session.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki r"""Reseed each app's desktop Firebase session by signing in with Google. The sync account moved to Google-only sign-in, so the password flow that used diff --git a/freedays/.pre-commit-config.yaml b/freedays/.pre-commit-config.yaml index 319008d..76bf5ff 100644 --- a/freedays/.pre-commit-config.yaml +++ b/freedays/.pre-commit-config.yaml @@ -65,7 +65,7 @@ repos: types: [python] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.2 + rev: v0.16.7 hooks: - id: ruff args: [--fix, --unsafe-fixes, --exit-non-zero-on-fix, --show-fixes] diff --git a/freedays/freedays/__init__.py b/freedays/freedays/__init__.py index 4433ae9..220f764 100644 --- a/freedays/freedays/__init__.py +++ b/freedays/freedays/__init__.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """One shared pool of pre-declared days on which every gate app stands down. The whole point of this package fits in one call. A gate app asks:: diff --git a/freedays/freedays/_api.py b/freedays/freedays/_api.py index ea9803d..f8d1994 100644 --- a/freedays/freedays/_api.py +++ b/freedays/freedays/_api.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Ask, mark, release, report -- the whole public surface. :func:`is_free_day` is the one every gate app calls, and it is deliberately diff --git a/freedays/freedays/_audit.py b/freedays/freedays/_audit.py index 78ab297..271afbd 100644 --- a/freedays/freedays/_audit.py +++ b/freedays/freedays/_audit.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Append-only, HMAC-signed trail of every mark and release. Nothing reads this to make a decision -- the CRDT log is the truth, and this diff --git a/freedays/freedays/_budget.py b/freedays/freedays/_budget.py index 30da3db..eed6995 100644 --- a/freedays/freedays/_budget.py +++ b/freedays/freedays/_budget.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Calendar-year accounting for the pool. One global pool, shared by every app: a day is either free everywhere or diff --git a/freedays/freedays/_cli.py b/freedays/freedays/_cli.py index 51d5e76..e7ffb55 100644 --- a/freedays/freedays/_cli.py +++ b/freedays/freedays/_cli.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """The ``freedays`` command: the only thing that ever talks about free days. Every subcommand that says anything is pull, never push: nothing notifies, diff --git a/freedays/freedays/_cli_output.py b/freedays/freedays/_cli_output.py index 3968e1a..1d27ed3 100644 --- a/freedays/freedays/_cli_output.py +++ b/freedays/freedays/_cli_output.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """The one stdout/stderr sink every ``freedays`` subcommand writes through. A thin wrapper over ``sys.stdout.write`` (the shape diet-guard and home-guard diff --git a/freedays/freedays/_constants.py b/freedays/freedays/_constants.py index 1d78297..153b5c9 100644 --- a/freedays/freedays/_constants.py +++ b/freedays/freedays/_constants.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """On-disk locations, remote paths and the one policy number that matters. Every path here is a *default*. Public functions take the corresponding diff --git a/freedays/freedays/_day.py b/freedays/freedays/_day.py index d9303bb..e305a37 100644 --- a/freedays/freedays/_day.py +++ b/freedays/freedays/_day.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """What "today" means, in one place. The apps this package serves disagreed about this before it existed: diff --git a/freedays/freedays/_errors.py b/freedays/freedays/_errors.py index a4296e8..94d5445 100644 --- a/freedays/freedays/_errors.py +++ b/freedays/freedays/_errors.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Why a mark or un-mark was refused. Each of these is a *policy* refusal with a message meant to be shown to the diff --git a/freedays/freedays/_hmac.py b/freedays/freedays/_hmac.py index 1864e9c..1332fd9 100644 --- a/freedays/freedays/_hmac.py +++ b/freedays/freedays/_hmac.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Signing the audit trail, using nothing but the standard library. This deliberately does not call ``gatelock.log_integrity``, even though the diff --git a/freedays/freedays/_model.py b/freedays/freedays/_model.py index 094a49b..33cee11 100644 --- a/freedays/freedays/_model.py +++ b/freedays/freedays/_model.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Translating between a free day and a crdt-sync ``Record``. The record id *is* the ISO date, so two devices marking the same day produce diff --git a/freedays/freedays/_paths.py b/freedays/freedays/_paths.py index 9c358b1..aa72089 100644 --- a/freedays/freedays/_paths.py +++ b/freedays/freedays/_paths.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Every file this package owns, as one value that can be swapped wholesale. Threading five separate ``*_path`` keyword arguments through the API was the diff --git a/freedays/freedays/_sync.py b/freedays/freedays/_sync.py index 35a4f82..c791558 100644 --- a/freedays/freedays/_sync.py +++ b/freedays/freedays/_sync.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Pushing and pulling the pool, so a day taken on the phone lands on the PC. Sync is a *separate* operation from every decision in :mod:`freedays._api`. diff --git a/freedays/freedays/tests/__init__.py b/freedays/freedays/tests/__init__.py index 1333fa0..beb2205 100644 --- a/freedays/freedays/tests/__init__.py +++ b/freedays/freedays/tests/__init__.py @@ -1 +1,2 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for :mod:`freedays`.""" diff --git a/freedays/freedays/tests/conftest.py b/freedays/freedays/tests/conftest.py index ab2d085..5c9e831 100644 --- a/freedays/freedays/tests/conftest.py +++ b/freedays/freedays/tests/conftest.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Redirect every on-disk path away from real user state, for every test. This is one ``monkeypatch`` of one function, and that is the whole point. diff --git a/freedays/freedays/tests/test_api.py b/freedays/freedays/tests/test_api.py index e20a7b6..99ab319 100644 --- a/freedays/freedays/tests/test_api.py +++ b/freedays/freedays/tests/test_api.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Marking, releasing, and the budget arithmetic behind both.""" from __future__ import annotations diff --git a/freedays/freedays/tests/test_audit.py b/freedays/freedays/tests/test_audit.py index 3980078..2145180 100644 --- a/freedays/freedays/tests/test_audit.py +++ b/freedays/freedays/tests/test_audit.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """The audit trail: best-effort, signed, and never in the way.""" from __future__ import annotations diff --git a/freedays/freedays/tests/test_cli.py b/freedays/freedays/tests/test_cli.py index d1e8d92..ef6f6dd 100644 --- a/freedays/freedays/tests/test_cli.py +++ b/freedays/freedays/tests/test_cli.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """The command line: the only surface that ever mentions free days.""" from __future__ import annotations diff --git a/freedays/freedays/tests/test_day.py b/freedays/freedays/tests/test_day.py index bce53ee..b6bdbc1 100644 --- a/freedays/freedays/tests/test_day.py +++ b/freedays/freedays/tests/test_day.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Date handling: the one place allowed an opinion about "today".""" from __future__ import annotations diff --git a/freedays/freedays/tests/test_hmac.py b/freedays/freedays/tests/test_hmac.py index 92475c7..19e5918 100644 --- a/freedays/freedays/tests/test_hmac.py +++ b/freedays/freedays/tests/test_hmac.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Signing, and the ways it is allowed to fail.""" from __future__ import annotations diff --git a/freedays/freedays/tests/test_model.py b/freedays/freedays/tests/test_model.py index 325e5b1..0b49541 100644 --- a/freedays/freedays/tests/test_model.py +++ b/freedays/freedays/tests/test_model.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Record translation, and the CRDT properties the design leans on.""" from __future__ import annotations diff --git a/freedays/freedays/tests/test_paths.py b/freedays/freedays/tests/test_paths.py index ad51b05..f85efe8 100644 --- a/freedays/freedays/tests/test_paths.py +++ b/freedays/freedays/tests/test_paths.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """The path bundle itself, exercised unpatched. The autouse redirect in ``conftest`` replaces ``resolve_paths`` *as imported diff --git a/freedays/freedays/tests/test_sync.py b/freedays/freedays/tests/test_sync.py index 604a186..0d4b44d 100644 --- a/freedays/freedays/tests/test_sync.py +++ b/freedays/freedays/tests/test_sync.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Sync: convergence across devices, and silence when the network is not there.""" from __future__ import annotations diff --git a/gatelock/.pre-commit-config.yaml b/gatelock/.pre-commit-config.yaml index 43724c6..38695e0 100644 --- a/gatelock/.pre-commit-config.yaml +++ b/gatelock/.pre-commit-config.yaml @@ -65,7 +65,7 @@ repos: types: [python] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.2 + rev: v0.16.7 hooks: - id: ruff args: [--fix, --unsafe-fixes, --exit-non-zero-on-fix, --show-fixes] diff --git a/gatelock/gatelock/__init__.py b/gatelock/gatelock/__init__.py index 1118765..6c1a2b2 100644 --- a/gatelock/gatelock/__init__.py +++ b/gatelock/gatelock/__init__.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Shared lock-window and HMAC log-integrity backend for blocking-overlay apps. Used by ``screen-locker``, ``diet-guard`` and ``wake-alarm``, which all need diff --git a/gatelock/gatelock/_arbiter.py b/gatelock/gatelock/_arbiter.py index 5534544..90215fb 100644 --- a/gatelock/gatelock/_arbiter.py +++ b/gatelock/gatelock/_arbiter.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Decide which of N locker apps may hold the screen. On 2026-07-25 two lockers armed at the same boot. One won the X grab; the diff --git a/gatelock/gatelock/_arming.py b/gatelock/gatelock/_arming.py index e14cd16..af5cc9d 100644 --- a/gatelock/gatelock/_arming.py +++ b/gatelock/gatelock/_arming.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Bringing the lock up: VT-disable, backdrop, surfaces, and the input grab. Split out of :mod:`gatelock._window`, which keeps the object's construction diff --git a/gatelock/gatelock/_claims.py b/gatelock/gatelock/_claims.py index ba63a24..a9c5e08 100644 --- a/gatelock/gatelock/_claims.py +++ b/gatelock/gatelock/_claims.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """The claim an app publishes while it holds the screen, and how to read one. Split from :mod:`gatelock._arbiter`, which keeps the arbiter itself. A claim diff --git a/gatelock/gatelock/_config.py b/gatelock/gatelock/_config.py index 5c35a40..1e6a44f 100644 --- a/gatelock/gatelock/_config.py +++ b/gatelock/gatelock/_config.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Declarative configuration and design tokens for :class:`LockWindow`. Split out of :mod:`gatelock._window` so that the runtime mechanics (grab, VT, diff --git a/gatelock/gatelock/_density.py b/gatelock/gatelock/_density.py index 394eebe..65c1c04 100644 --- a/gatelock/gatelock/_density.py +++ b/gatelock/gatelock/_density.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Screen-height-derived compaction for the design-system type/space scales. The token scales in :class:`~gatelock._window.LockConfig` are authored in diff --git a/gatelock/gatelock/_detect.py b/gatelock/gatelock/_detect.py index 5257779..1506193 100644 --- a/gatelock/gatelock/_detect.py +++ b/gatelock/gatelock/_detect.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Notice output changes fast, by four means at once. The lock must react the moment a monitor wakes up, and must not miss a monitor diff --git a/gatelock/gatelock/_escape.py b/gatelock/gatelock/_escape.py index 00cd1f1..f726f55 100644 --- a/gatelock/gatelock/_escape.py +++ b/gatelock/gatelock/_escape.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """A sanctioned, rate-limited way out of a lock. A hard lock without an escape hatch is a trap. screen-locker learned this and diff --git a/gatelock/gatelock/_escape_types.py b/gatelock/gatelock/_escape_types.py index 7d48307..8f9c762 100644 --- a/gatelock/gatelock/_escape_types.py +++ b/gatelock/gatelock/_escape_types.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """The policy, history and draft types the escape hatch works with. Split from :mod:`gatelock._escape`, which keeps the tracker. Data and two date diff --git a/gatelock/gatelock/_fitcheck.py b/gatelock/gatelock/_fitcheck.py index ed40a19..531d67a 100644 --- a/gatelock/gatelock/_fitcheck.py +++ b/gatelock/gatelock/_fitcheck.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Measure whether a lock screen fits its surface, at a chosen resolution. A lock surface is fullscreen, grabbed and (on the production path) VT-locked: diff --git a/gatelock/gatelock/_grabwatch.py b/gatelock/gatelock/_grabwatch.py index 49b62ae..44334bb 100644 --- a/gatelock/gatelock/_grabwatch.py +++ b/gatelock/gatelock/_grabwatch.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Deciding whether this application still holds the input grab. Split from :mod:`gatelock._recovery`, which re-asserts the grab when this diff --git a/gatelock/gatelock/_guards.py b/gatelock/gatelock/_guards.py index 7e015e2..887ad32 100644 --- a/gatelock/gatelock/_guards.py +++ b/gatelock/gatelock/_guards.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Safety guards shared by every locker app. Both guards here were duplicated across the consuming repos (and one of them diff --git a/gatelock/gatelock/_hooks.py b/gatelock/gatelock/_hooks.py index 44a18f1..fb4ee77 100644 --- a/gatelock/gatelock/_hooks.py +++ b/gatelock/gatelock/_hooks.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """The callback interface an app embedding :class:`~gatelock.LockWindow` fills. Split out of :mod:`gatelock._window` so the pure *interface* -- what an app diff --git a/gatelock/gatelock/_keyboard.py b/gatelock/gatelock/_keyboard.py index c3d3201..e8d1e17 100644 --- a/gatelock/gatelock/_keyboard.py +++ b/gatelock/gatelock/_keyboard.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki r"""Keyboard-operability helpers for lock surfaces. Every function here works around a **Tk default**, not an application mistake. diff --git a/gatelock/gatelock/_output_types.py b/gatelock/gatelock/_output_types.py index 0faba13..36d1fc5 100644 --- a/gatelock/gatelock/_output_types.py +++ b/gatelock/gatelock/_output_types.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """The value types describing a display output and a scan of them. Split from :mod:`gatelock._outputs` so the two backends diff --git a/gatelock/gatelock/_outputs.py b/gatelock/gatelock/_outputs.py index 7d67f32..2f49160 100644 --- a/gatelock/gatelock/_outputs.py +++ b/gatelock/gatelock/_outputs.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Enumerate X11 outputs and tell live ones apart from dark ones. The distinction this module exists to make is ``connected`` versus **live**. diff --git a/gatelock/gatelock/_placement.py b/gatelock/gatelock/_placement.py index 9e51147..9bfdb22 100644 --- a/gatelock/gatelock/_placement.py +++ b/gatelock/gatelock/_placement.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Where a lock surface sits, and putting it back when it drifts. Split from :mod:`gatelock._surfaces`, which owns creating and destroying the diff --git a/gatelock/gatelock/_preempt.py b/gatelock/gatelock/_preempt.py index 0fe9c7f..5c8f1a0 100644 --- a/gatelock/gatelock/_preempt.py +++ b/gatelock/gatelock/_preempt.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Diagnosing and clearing a blocked global input grab. Split out of :mod:`gatelock._window`, which owns the *acquisition* loop; this diff --git a/gatelock/gatelock/_queue.py b/gatelock/gatelock/_queue.py index 19041d6..1b22bb6 100644 --- a/gatelock/gatelock/_queue.py +++ b/gatelock/gatelock/_queue.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Wait our turn instead of fighting for the screen. The lockers form a ladder (see ``_arbiter.py``'s module docstring: wake up, diff --git a/gatelock/gatelock/_randr.py b/gatelock/gatelock/_randr.py index 3347ede..4982f7b 100644 --- a/gatelock/gatelock/_randr.py +++ b/gatelock/gatelock/_randr.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """The python-xlib RandR backend: instant output-change events. Split from :mod:`gatelock._outputs`, which keeps the value types and the diff --git a/gatelock/gatelock/_recovery.py b/gatelock/gatelock/_recovery.py index 7ce3880..06ae94e 100644 --- a/gatelock/gatelock/_recovery.py +++ b/gatelock/gatelock/_recovery.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Keep the lock covering every live output, and never weaken it. This loop is the watchdog the 2026-07-25 incident needed. A monitor that comes diff --git a/gatelock/gatelock/_recovery_types.py b/gatelock/gatelock/_recovery_types.py index ac4eb5b..151f35d 100644 --- a/gatelock/gatelock/_recovery_types.py +++ b/gatelock/gatelock/_recovery_types.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """The value types one recovery tick produces and consumes. Split from :mod:`gatelock._recovery` for the 250-line cap. Data only -- no diff --git a/gatelock/gatelock/_root.py b/gatelock/gatelock/_root.py index 134d5b5..f9b7da3 100644 --- a/gatelock/gatelock/_root.py +++ b/gatelock/gatelock/_root.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Safe Tk root window that never lets a callback exception escape.""" from __future__ import annotations diff --git a/gatelock/gatelock/_scrollable.py b/gatelock/gatelock/_scrollable.py index 68f9998..791699e 100644 --- a/gatelock/gatelock/_scrollable.py +++ b/gatelock/gatelock/_scrollable.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """A keyboard-scrollable, focus-following viewport for lock-surface content. Build it inside a lock surface, pack widgets into :attr:`content`, then call diff --git a/gatelock/gatelock/_scrollfit.py b/gatelock/gatelock/_scrollfit.py index 25b25c7..528348d 100644 --- a/gatelock/gatelock/_scrollfit.py +++ b/gatelock/gatelock/_scrollfit.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Measuring a scroll viewport's content and switching its layout to match. Split out of :mod:`gatelock._scrollable`, which keeps the widget wiring, focus diff --git a/gatelock/gatelock/_scrollkeys.py b/gatelock/gatelock/_scrollkeys.py index f9f7a46..c9f2df3 100644 --- a/gatelock/gatelock/_scrollkeys.py +++ b/gatelock/gatelock/_scrollkeys.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Keyboard and wheel scrolling for a :class:`~gatelock._scrollable.ScrollableSurface`. Split out of :mod:`gatelock._scrollable`, which keeps the viewport's geometry diff --git a/gatelock/gatelock/_surface_types.py b/gatelock/gatelock/_surface_types.py index 2df87cc..4dd4d86 100644 --- a/gatelock/gatelock/_surface_types.py +++ b/gatelock/gatelock/_surface_types.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """The value types describing a lock surface and a change to the set of them. Split from :mod:`gatelock._surfaces` for the 250-line cap. Data and one diff --git a/gatelock/gatelock/_surfaces.py b/gatelock/gatelock/_surfaces.py index 35c6a90..bdf297f 100644 --- a/gatelock/gatelock/_surfaces.py +++ b/gatelock/gatelock/_surfaces.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Own every lock window: one per live output, plus the backdrop. This is the **only** module allowed to create, move or destroy windows. That @@ -50,6 +51,7 @@ from gatelock._surface_types import ( SurfaceDelta, SurfaceInfo, ) +from gatelock._surfaces_focus import focus_surface, preferred_focus_index from gatelock._textmirror import TextMirror, mirror_text_widgets # Names this module re-exports after the 250-line split. Listed explicitly so @@ -185,21 +187,11 @@ class SurfaceSet: def focus_surface(self, index: int) -> SurfaceInfo | None: """Move keyboard focus to one surface, returning what got it.""" - for surface in self._surfaces.values(): - if surface.info.index == index: - surface.window.focus_force() - return surface.info - return None + return focus_surface(self._surfaces, index) def preferred_focus_index(self) -> int: - """Index of the surface that should take initial focus. - - The live primary output if there is one, else the first surface. - """ - for surface in self._surfaces.values(): - if surface.info.is_primary: - return surface.info.index - return 0 + """Index of the surface that should take initial focus.""" + return preferred_focus_index(self._surfaces) def _create(self, info: SurfaceInfo) -> None: """Create one surface window and let the app build its UI inside.""" diff --git a/gatelock/gatelock/_surfaces_focus.py b/gatelock/gatelock/_surfaces_focus.py new file mode 100644 index 0000000..cd1e999 --- /dev/null +++ b/gatelock/gatelock/_surfaces_focus.py @@ -0,0 +1,40 @@ +# Copyright (c) 2026 Krzysztof Rudnicki +"""Which surface takes keyboard focus, and moving it there. + +Split from ``_surfaces.py`` (250-line cap). Focus is a read-then-point +concern: it selects among surfaces that already exist and never creates, +moves or destroys one, so it sits outside the structural-change containment +that ``_surfaces`` module docstring describes -- and the static invariant in +``test_recovery_invariants.py`` globs ``_surfaces*.py``, so this sibling is +covered by that guard the moment it exists, with no list to extend. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Mapping + + from gatelock._placement import _Surface + from gatelock._surface_types import SurfaceInfo + + +def focus_surface(surfaces: Mapping[str, _Surface], index: int) -> SurfaceInfo | None: + """Move keyboard focus to the surface at ``index``, returning what got it.""" + for surface in surfaces.values(): + if surface.info.index == index: + surface.window.focus_force() + return surface.info + return None + + +def preferred_focus_index(surfaces: Mapping[str, _Surface]) -> int: + """Index of the surface that should take initial focus. + + The live primary output if there is one, else the first surface. + """ + for surface in surfaces.values(): + if surface.info.is_primary: + return surface.info.index + return 0 diff --git a/gatelock/gatelock/_textmirror.py b/gatelock/gatelock/_textmirror.py index 05d3979..283736e 100644 --- a/gatelock/gatelock/_textmirror.py +++ b/gatelock/gatelock/_textmirror.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Keeping several Text widgets showing the same string. Split from :mod:`gatelock._surfaces` for the 250-line cap. A lock renders one diff --git a/gatelock/gatelock/_userintent.py b/gatelock/gatelock/_userintent.py index 8143ceb..d961098 100644 --- a/gatelock/gatelock/_userintent.py +++ b/gatelock/gatelock/_userintent.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Deciding whether a focus change was driven by the user or by the app. Split out of :mod:`gatelock._scrollable`, which owns the viewport itself. diff --git a/gatelock/gatelock/_vt.py b/gatelock/gatelock/_vt.py index 5ae9e23..6ea6be9 100644 --- a/gatelock/gatelock/_vt.py +++ b/gatelock/gatelock/_vt.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """VT-switch (Ctrl+Alt+Fn) disable/restore via setxkbmap. Disabling VT switching is what stops a locked window from being bypassed by diff --git a/gatelock/gatelock/_widget_fills.py b/gatelock/gatelock/_widget_fills.py new file mode 100644 index 0000000..389583e --- /dev/null +++ b/gatelock/gatelock/_widget_fills.py @@ -0,0 +1,48 @@ +# Copyright (c) 2026 Krzysztof Rudnicki +"""Colour derivation for the shared widgets: hover blends and variant fills. + +Split from ``widgets.py`` (250-line cap). The seam is deliberate rather than +arbitrary: everything here is pure colour arithmetic over ``LockConfig``, with +no ``tkinter`` import and no widget construction, so it is testable without a +display. ``widgets.py`` keeps the Tk-facing half and re-exports these names, +because :mod:`gatelock.widgets` is the public surface the gate apps import. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Literal + +if TYPE_CHECKING: + from gatelock._window import LockConfig + +ButtonVariant = Literal["primary", "secondary", "danger"] +"""Emphasis role for :func:`gatelock.widgets.make_button`, not a colour.""" + +# Blend fraction toward white for a button's hover/active fill. +_HOVER_LIGHTEN = 0.12 +_HEX_CHANNEL_OFFSETS = (1, 3, 5) +_RGB_MAX = 255 + + +def _lighten(hex_color: str, amount: float = _HOVER_LIGHTEN) -> str: + """Blend ``hex_color`` toward white by ``amount``, for a hover state.""" + channels = ( + int(hex_color[offset : offset + 2], 16) for offset in _HEX_CHANNEL_OFFSETS + ) + return "#" + "".join( + f"{round(channel + (_RGB_MAX - channel) * amount):02x}" for channel in channels + ) + + +def _button_fills(config: LockConfig) -> dict[ButtonVariant, tuple[str, str]]: + """Return the fill/text pair for each variant, read from the palette. + + All three pairs use ``on_fill`` (never ``fg``) for text drawn on a filled + surface, per tokens.md -- which is the whole point of keying off a + variant instead of letting each call site invent its own hex pair. + """ + return { + "primary": (config.accent, config.on_fill), + "secondary": (config.field_bg, config.fg), + "danger": (config.danger, config.on_fill), + } diff --git a/gatelock/gatelock/_window.py b/gatelock/gatelock/_window.py index 4ec1960..fa0f3d2 100644 --- a/gatelock/gatelock/_window.py +++ b/gatelock/gatelock/_window.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Lock window orchestration: surfaces, input grab, VT-disable, safe lifecycle. Generalizes the window mechanics that wake_alarm, screen-locker, and diff --git a/gatelock/gatelock/_xrandr.py b/gatelock/gatelock/_xrandr.py index fca2987..66882aa 100644 --- a/gatelock/gatelock/_xrandr.py +++ b/gatelock/gatelock/_xrandr.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Reading outputs by shelling out to `xrandr --query`. Split from :mod:`gatelock._outputs`, which keeps the value types and the diff --git a/gatelock/gatelock/log_integrity.py b/gatelock/gatelock/log_integrity.py index 8eb28a3..641f9e1 100644 --- a/gatelock/gatelock/log_integrity.py +++ b/gatelock/gatelock/log_integrity.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """HMAC-based integrity checking for signed state entries. Ported from the byte-for-byte-duplicated copies in testsAndMisc's diff --git a/gatelock/gatelock/tests/__init__.py b/gatelock/gatelock/tests/__init__.py index f7f5ba7..0ea037e 100644 --- a/gatelock/gatelock/tests/__init__.py +++ b/gatelock/gatelock/tests/__init__.py @@ -1 +1,2 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for the gatelock package.""" diff --git a/gatelock/gatelock/tests/conftest.py b/gatelock/gatelock/tests/conftest.py index dcea351..524cc30 100644 --- a/gatelock/gatelock/tests/conftest.py +++ b/gatelock/gatelock/tests/conftest.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Shared fixtures for gatelock tests. ``LockWindow`` takes its Tk root as a constructor argument (composition, not diff --git a/gatelock/gatelock/tests/test_arbiter.py b/gatelock/gatelock/tests/test_arbiter.py index fca18c7..8043510 100644 --- a/gatelock/gatelock/tests/test_arbiter.py +++ b/gatelock/gatelock/tests/test_arbiter.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for cross-app arbitration. The strength-check table is the most important thing here: it is the invariant diff --git a/gatelock/gatelock/tests/test_arbiter_lifecycle.py b/gatelock/gatelock/tests/test_arbiter_lifecycle.py index efee878..74b82f6 100644 --- a/gatelock/gatelock/tests/test_arbiter_lifecycle.py +++ b/gatelock/gatelock/tests/test_arbiter_lifecycle.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for an arbiter claim's life after it is taken. Split from ``test_arbiter.py`` (250-line cap): that file covers grab diff --git a/gatelock/gatelock/tests/test_arbiter_release.py b/gatelock/gatelock/tests/test_arbiter_release.py index f9edcbd..aa35ab4 100644 --- a/gatelock/gatelock/tests/test_arbiter_release.py +++ b/gatelock/gatelock/tests/test_arbiter_release.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for identifying the current holder and releasing a claim. Split from ``test_arbiter.py`` (250-line cap). ``test_arbiter_lifecycle.py`` diff --git a/gatelock/gatelock/tests/test_density.py b/gatelock/gatelock/tests/test_density.py index 96af833..d59383b 100644 --- a/gatelock/gatelock/tests/test_density.py +++ b/gatelock/gatelock/tests/test_density.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for the screen-height-derived compaction of the token scales.""" from __future__ import annotations diff --git a/gatelock/gatelock/tests/test_detect.py b/gatelock/gatelock/tests/test_detect.py index 9453672..57c2753 100644 --- a/gatelock/gatelock/tests/test_detect.py +++ b/gatelock/gatelock/tests/test_detect.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for layered output-change detection.""" from __future__ import annotations diff --git a/gatelock/gatelock/tests/test_escape.py b/gatelock/gatelock/tests/test_escape.py index 76bcccb..241cb98 100644 --- a/gatelock/gatelock/tests/test_escape.py +++ b/gatelock/gatelock/tests/test_escape.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for the shared escape hatch. The load-bearing property is that nothing here can ever *widen* the budget: diff --git a/gatelock/gatelock/tests/test_escape_budget.py b/gatelock/gatelock/tests/test_escape_budget.py index 65608c8..5196996 100644 --- a/gatelock/gatelock/tests/test_escape_budget.py +++ b/gatelock/gatelock/tests/test_escape_budget.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for the escape-hatch budget, lockout and recording. Split from ``test_escape.py`` (250-line cap), which keeps policy defaults, diff --git a/gatelock/gatelock/tests/test_fitcheck.py b/gatelock/gatelock/tests/test_fitcheck.py index 1d8404d..08d0583 100644 --- a/gatelock/gatelock/tests/test_fitcheck.py +++ b/gatelock/gatelock/tests/test_fitcheck.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for the pre-ship fit measurement. This is the harness every consuming app gates its layout on, so it has to be diff --git a/gatelock/gatelock/tests/test_guards.py b/gatelock/gatelock/tests/test_guards.py index 3c94ddd..ad5f69a 100644 --- a/gatelock/gatelock/tests/test_guards.py +++ b/gatelock/gatelock/tests/test_guards.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for the shared safety guards.""" from __future__ import annotations diff --git a/gatelock/gatelock/tests/test_keyboard.py b/gatelock/gatelock/tests/test_keyboard.py index 8d1e30c..a96121a 100644 --- a/gatelock/gatelock/tests/test_keyboard.py +++ b/gatelock/gatelock/tests/test_keyboard.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for the keyboard affordances every lock surface depends on. A lock takes a global grab with no other window on screen: a control that diff --git a/gatelock/gatelock/tests/test_log_integrity.py b/gatelock/gatelock/tests/test_log_integrity.py index 9c88d38..4357378 100644 --- a/gatelock/gatelock/tests/test_log_integrity.py +++ b/gatelock/gatelock/tests/test_log_integrity.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for log_integrity HMAC signing and verification.""" from __future__ import annotations diff --git a/gatelock/gatelock/tests/test_outputs.py b/gatelock/gatelock/tests/test_outputs.py index 096d5a0..ea7e20c 100644 --- a/gatelock/gatelock/tests/test_outputs.py +++ b/gatelock/gatelock/tests/test_outputs.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for output enumeration and the live-vs-dark predicate. The regression that matters most is :data:`XRANDR_INCIDENT`: on 2026-07-25 an diff --git a/gatelock/gatelock/tests/test_outputs_enumerator.py b/gatelock/gatelock/tests/test_outputs_enumerator.py index 21bd344..93264ea 100644 --- a/gatelock/gatelock/tests/test_outputs_enumerator.py +++ b/gatelock/gatelock/tests/test_outputs_enumerator.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for OutputEnumerator and the scan it returns. Split from ``test_outputs.py`` (250-line cap). ``test_outputs_randr.py`` diff --git a/gatelock/gatelock/tests/test_outputs_randr.py b/gatelock/gatelock/tests/test_outputs_randr.py index 9d1dcd5..9a40d13 100644 --- a/gatelock/gatelock/tests/test_outputs_randr.py +++ b/gatelock/gatelock/tests/test_outputs_randr.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for the python-xlib RandR backend and the enumerator over it. Split from ``test_outputs.py`` (250-line cap), which keeps rect geometry, diff --git a/gatelock/gatelock/tests/test_queue.py b/gatelock/gatelock/tests/test_queue.py index 018c1c1..d9b7fca 100644 --- a/gatelock/gatelock/tests/test_queue.py +++ b/gatelock/gatelock/tests/test_queue.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for the layered wait.""" from __future__ import annotations diff --git a/gatelock/gatelock/tests/test_recovery.py b/gatelock/gatelock/tests/test_recovery.py index aa20ab8..1d71fa1 100644 --- a/gatelock/gatelock/tests/test_recovery.py +++ b/gatelock/gatelock/tests/test_recovery.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for the monotonic recovery loop. Three layers, per the release plan: a static AST check that the weakening calls diff --git a/gatelock/gatelock/tests/test_recovery_invariants.py b/gatelock/gatelock/tests/test_recovery_invariants.py index 3f90bcd..192ed50 100644 --- a/gatelock/gatelock/tests/test_recovery_invariants.py +++ b/gatelock/gatelock/tests/test_recovery_invariants.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Static checks that the recovery loop *cannot* weaken the lock. Split from ``test_recovery.py`` (250-line cap). These read the source with diff --git a/gatelock/gatelock/tests/test_recovery_reassertion.py b/gatelock/gatelock/tests/test_recovery_reassertion.py index 088bbdc..720ece9 100644 --- a/gatelock/gatelock/tests/test_recovery_reassertion.py +++ b/gatelock/gatelock/tests/test_recovery_reassertion.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for what the recovery loop re-asserts on each tick. Split from ``test_recovery.py`` (250-line cap). That file keeps the static diff --git a/gatelock/gatelock/tests/test_root.py b/gatelock/gatelock/tests/test_root.py index bdc97c0..7634cc1 100644 --- a/gatelock/gatelock/tests/test_root.py +++ b/gatelock/gatelock/tests/test_root.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for GateRoot's safe callback-exception handling.""" from __future__ import annotations diff --git a/gatelock/gatelock/tests/test_scrollable.py b/gatelock/gatelock/tests/test_scrollable.py index d2f6e5c..0d55088 100644 --- a/gatelock/gatelock/tests/test_scrollable.py +++ b/gatelock/gatelock/tests/test_scrollable.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """The viewport moves for the user, and for nobody else. These run against real Tk: the behaviour under test is Tk's own focus diff --git a/gatelock/gatelock/tests/test_scrollable_into_view.py b/gatelock/gatelock/tests/test_scrollable_into_view.py index 4efa64d..0a5a115 100644 --- a/gatelock/gatelock/tests/test_scrollable_into_view.py +++ b/gatelock/gatelock/tests/test_scrollable_into_view.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for scrolling a specific widget into view. Split from ``test_scrollable.py`` (250-line cap), which keeps the fit states diff --git a/gatelock/gatelock/tests/test_surfaces.py b/gatelock/gatelock/tests/test_surfaces.py index 5a5ea57..8ca0b52 100644 --- a/gatelock/gatelock/tests/test_surfaces.py +++ b/gatelock/gatelock/tests/test_surfaces.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for the surface set: one lock window per live output.""" from __future__ import annotations diff --git a/gatelock/gatelock/tests/test_surfaces_focus.py b/gatelock/gatelock/tests/test_surfaces_focus.py index e92f6f8..5a6bcd4 100644 --- a/gatelock/gatelock/tests/test_surfaces_focus.py +++ b/gatelock/gatelock/tests/test_surfaces_focus.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for focus handling, the backdrop, teardown and the text mirror. Split from ``test_surfaces.py`` to hold the shared 250-line cap. diff --git a/gatelock/gatelock/tests/test_vt.py b/gatelock/gatelock/tests/test_vt.py index 9226967..d398091 100644 --- a/gatelock/gatelock/tests/test_vt.py +++ b/gatelock/gatelock/tests/test_vt.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for VT-switch disable/restore.""" from __future__ import annotations diff --git a/gatelock/gatelock/tests/test_widget_group.py b/gatelock/gatelock/tests/test_widget_group.py index 8e7e3be..a664625 100644 --- a/gatelock/gatelock/tests/test_widget_group.py +++ b/gatelock/gatelock/tests/test_widget_group.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """A group holds every per-output copy, and survives losing one. Membership and lifecycle: what a group contains, what ``discard``/``clear``/ diff --git a/gatelock/gatelock/tests/test_widget_group_ops.py b/gatelock/gatelock/tests/test_widget_group_ops.py index 68832f7..6b6521a 100644 --- a/gatelock/gatelock/tests/test_widget_group_ops.py +++ b/gatelock/gatelock/tests/test_widget_group_ops.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Reading a group, focusing it, and binding across it. Focus is the one operation that deliberately does *not* fan out, and diff --git a/gatelock/gatelock/tests/test_widgets.py b/gatelock/gatelock/tests/test_widgets.py index ac5e345..d113245 100644 --- a/gatelock/gatelock/tests/test_widgets.py +++ b/gatelock/gatelock/tests/test_widgets.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """A button the keyboard can actually press, in colours that always contrast. Run against real Tk: the two defects these widgets exist to prevent are Tk's diff --git a/gatelock/gatelock/tests/test_window.py b/gatelock/gatelock/tests/test_window.py index 86795cd..e4a71a4 100644 --- a/gatelock/gatelock/tests/test_window.py +++ b/gatelock/gatelock/tests/test_window.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for LockConfig and LockWindow.""" from __future__ import annotations diff --git a/gatelock/gatelock/tests/test_window_grab.py b/gatelock/gatelock/tests/test_window_grab.py index 300a839..4d77000 100644 --- a/gatelock/gatelock/tests/test_window_grab.py +++ b/gatelock/gatelock/tests/test_window_grab.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for acquiring the global input grab and its retry loop. Split from ``test_window.py`` to hold the shared 250-line cap. diff --git a/gatelock/gatelock/tests/test_window_lifecycle.py b/gatelock/gatelock/tests/test_window_lifecycle.py index 88f84e7..65b535e 100644 --- a/gatelock/gatelock/tests/test_window_lifecycle.py +++ b/gatelock/gatelock/tests/test_window_lifecycle.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for LockWindow signal handling, keepalive, close, and run lifecycle.""" from __future__ import annotations diff --git a/gatelock/gatelock/tests/test_window_surfaces.py b/gatelock/gatelock/tests/test_window_surfaces.py index e34e41e..4657737 100644 --- a/gatelock/gatelock/tests/test_window_surfaces.py +++ b/gatelock/gatelock/tests/test_window_surfaces.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for LockWindow's v0.2.0 behaviour: arming, holder naming, teardown.""" from __future__ import annotations diff --git a/gatelock/gatelock/tests/test_window_surfaces_lifecycle.py b/gatelock/gatelock/tests/test_window_surfaces_lifecycle.py index d3d059b..569f1d9 100644 --- a/gatelock/gatelock/tests/test_window_surfaces_lifecycle.py +++ b/gatelock/gatelock/tests/test_window_surfaces_lifecycle.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for focus notification and close on a lock window. Split from ``test_window_surfaces.py`` to hold the shared 250-line cap. diff --git a/gatelock/gatelock/tests/test_window_surfaces_preempt.py b/gatelock/gatelock/tests/test_window_surfaces_preempt.py index 15bf239..a23c00d 100644 --- a/gatelock/gatelock/tests/test_window_surfaces_preempt.py +++ b/gatelock/gatelock/tests/test_window_surfaces_preempt.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Tests for standing a weaker grab holder down. Split from ``test_window_surfaces.py`` to hold the shared 250-line cap; diff --git a/gatelock/gatelock/widget_group.py b/gatelock/gatelock/widget_group.py index 34b241c..4147950 100644 --- a/gatelock/gatelock/widget_group.py +++ b/gatelock/gatelock/widget_group.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """One logical widget, fanned out across every output's surface. A gate covers every monitor the user can see, so each surface needs its own diff --git a/gatelock/gatelock/widgets.py b/gatelock/gatelock/widgets.py index 783b6af..d2d5d70 100644 --- a/gatelock/gatelock/widgets.py +++ b/gatelock/gatelock/widgets.py @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Krzysztof Rudnicki """Composite widgets every gate app was rebuilding from the same tokens. ``LockConfig`` already owns the palette and the type scale, but a palette is @@ -24,16 +25,15 @@ from __future__ import annotations from dataclasses import dataclass import tkinter as tk -from typing import TYPE_CHECKING, Literal +from typing import TYPE_CHECKING + +from gatelock._widget_fills import ButtonVariant, _button_fills, _lighten if TYPE_CHECKING: from collections.abc import Callable from gatelock._window import LockConfig, TypeRole -ButtonVariant = Literal["primary", "secondary", "danger"] -"""Emphasis role for :func:`make_button`, not a colour.""" - @dataclass(frozen=True, slots=True) class ButtonStyle: @@ -92,38 +92,10 @@ class RowStyle: # spacing scale). _BUTTON_PADY = 12 _BUTTON_PADX = 24 -# Blend fraction toward white for a button's hover/active fill. -_HOVER_LIGHTEN = 0.12 -_HEX_CHANNEL_OFFSETS = (1, 3, 5) -_RGB_MAX = 255 # Narrowest wrap worth honouring; below this a "wrapped" line is one word. _MIN_WRAP = 320 -def _lighten(hex_color: str, amount: float = _HOVER_LIGHTEN) -> str: - """Blend ``hex_color`` toward white by ``amount``, for a hover state.""" - channels = ( - int(hex_color[offset : offset + 2], 16) for offset in _HEX_CHANNEL_OFFSETS - ) - return "#" + "".join( - f"{round(channel + (_RGB_MAX - channel) * amount):02x}" for channel in channels - ) - - -def _button_fills(config: LockConfig) -> dict[ButtonVariant, tuple[str, str]]: - """Return the fill/text pair for each variant, read from the palette. - - All three pairs use ``on_fill`` (never ``fg``) for text drawn on a filled - surface, per tokens.md -- which is the whole point of keying off a - variant instead of letting each call site invent its own hex pair. - """ - return { - "primary": (config.accent, config.on_fill), - "secondary": (config.field_bg, config.fg), - "danger": (config.danger, config.on_fill), - } - - # The one high-emphasis button per screen reads larger than everything else # (rule 3: size is another axis of prominence, alongside colour); every other # variant shares the smaller chrome size.