Files
quak/TODO.md
T
sneak 7335e93d65
check / check (push) Successful in 24s
Resumable, deletion-aware collection and file enumeration (closes #38)
Add collectionsSince/filesSince on Client: they take a starting cursor,
decrypt live records, surface tombstoned ids in a separate `deleted`
list, and return the max updationTime seen as the cursor to resume from.
A tombstone has no key or metadata to decrypt, so it is a bare id rather
than a hollowed-out record that strict tsc would reject.

filesSince paginates the diff from the given cursor and, when the server
reports hasMore but the page's max updationTime does not exceed the
cursor it was fetched with, throws instead of looping forever.

listCollections/listFiles become thin wrappers that enumerate from
sinceTime 0 and drop deletions, so existing callers and tests are
unaffected.

Closes #7.

Model: opus-4-8
2026-09-22 10:19:25 +00:00

7.7 KiB

Workflow

  • branch (from main)
  • do the work in Next Step
  • move Next Step to the top of Completed Steps
  • move the top item of Future Steps into Next Step
  • commit (TODO.md changes in the same commit as the work)
  • merge to main if the branch is not protected, otherwise open a PR
  • push

Status

pre-1.0

Next Step

Update the README API reference section to match the current implementation.

Completed Steps

  • 2026-09-22: Added resumable, deletion-aware enumeration to Client (issue 38, closes issue 7). collectionsSince/filesSince take a starting cursor, decrypt live records, surface tombstoned ids in a separate deleted list (a tombstone has nothing to decrypt, so it is a bare id, not a hollow record), and return the max updationTime seen as the cursor to resume from. filesSince refuses to loop when the diff reports hasMore without advancing the cursor (issue 7). listCollections/listFiles are now thin wrappers that enumerate from sinceTime: 0 and drop deletions, so existing callers are unaffected.
  • 2026-09-22: Carried file size, thumbnail size, and the deletion flag through decryptFile (issue 37, foundation for the cache/API design). Live files now populate file.size/thumbnail.size from the server's info (left undefined when the server omits it), and isDeleted is carried from the diff row onto EnteFile. No caller change: listFiles still filters deleted rows before decrypting. Surfacing a tombstone through decryption belongs to the enumeration unit (issue 38).
  • 2026-08-10: Made lint-once.test.ts enforce what its header claims. It walked make check only, so it never read Dockerfile — the image CI builds through script/cibuild — and a second prettier --check . could be added there with the suite staying green. The walk now also starts at .gitea/workflows/check.yml and follows its run: steps, so the graph under test is the one CI executes rather than the one someone assumed it executes. The lockfile assertion was a substring check against the whole of script/bootstrap, which has two install sites and so reported the branch the containers never take; the two branches are now resolved separately and every yarn install in each is required to be --frozen-lockfile. Prettier is counted per occurrence instead of per line, so two invocations chained with && no longer read as one, and edges are followed on counted lines instead of being skipped. Every way for the walk to reach nothing — an unknown target, an unknown script, a missing file, a node with no commands, an unknown node kind — is a thrown error rather than a quiet zero. Every assertion in the file was mutation-tested individually.
  • 2026-08-10: Stopped make check running prettier --check . twice. Since linting moved into Docker, the duplicate was one container pass and one host pass of the same check: script/lint builds Dockerfile.lint, which runs prettier as a build step, and script/check then called script/fmt-check as well. The host call is gone from script/check and from script/precommit; the container keeps checking formatting, because a successful Dockerfile.lint build is what CI treats as proof of a clean tree, and it is also what still fails the pre-commit hook on a badly formatted tree. script/fmt-check survives as a standalone entrypoint, whose verdict cannot drift from the container's. A test walks the invocation graph from each entrypoint — through the Makefile shims, the script/ calls and the docker build — and asserts the prettier count, so the duplication cannot come back unnoticed.
  • 2026-08-10: Moved all linting into Docker. script/lint builds a new root Dockerfile.lint, which copies the repo into the digest-pinned node image and runs eslint and prettier as build steps, so a successful build is a clean lint; no host lint path remains and yarn lint is gone from package.json. A fail-closed LINT_EPOCH guard stops Docker serving the linter layers from cache, which is how a lint build returns success in under a second having linted nothing. The lint stage inside Dockerfile and its COPY --from=lint ordering hack are gone: that image now runs make test and make build only, because script/check calls script/lint and running it in a container would mean docker inside docker. script/cibuild builds the lint image first, then the test and build image.
  • 2026-08-09: Made make docker green and policy-conformant. Multi-stage Dockerfile: a lint stage runs make fmt-check and make lint, and the check stage takes a COPY --from=lint dependency on it before running make check and make build. CHECK_EPOCH and a fail-closed guard stop Docker serving those two layers from cache, which is what let a build report success without running the suite. script/projectname says quak, so the image is tagged quak; script/bootstrap updates apt lists before installing, so a Debian base works; .dockerignore no longer ships the compiled binary, the caches or agent worktrees into the build context, and keeps .gitignore in it for prettier.
  • 2026-08-09: Fixed the TypeScript build. rootDir is the repo root, so bin/ compiles alongside src/ instead of failing with TS6059; output is dist/src/ and dist/bin/, which is where main, types and bin.quak now point. script/build verifies the declared entrypoints exist after the compiler runs and makes the CLI executable, the Dockerfile runs make build as well as make check, and a quak script makes the README's yarn quak <command> examples work.
  • 2026-08-09: Retry policy: no retry on 4xx (except 408 and 429), exponential backoff with full jitter on 5xx, transport failures and truncated transfers, under per-attempt deadlines that cover the response body as well as the request. Downloads retry request, stream consumption and decryption as one unit; postJSON and putJSON are replayed only when the connection was never established.
  • 2026-08-09: Downloads verify the secretstream terminated on TAG_FINAL and write output atomically: a truncated body is rejected instead of landing on disk as a short file, and plaintext is staged in a sibling temp file and renamed into place, so a failed download leaves the destination untouched.
  • 2026-07-07 Adopted scripts-to-rule-them-all: script/ entrypoints, Makefile shims, README Entrypoints section
  • 2026-06-10: Decrypted collections shared by other users (sealed-box keys); listCollections drops deleted-collection tombstones.
  • 2026-06-10: Login hardening: dual-2FA empty-string fields handled, TOTP preferred when a passkey is also enrolled, interactive input via @inquirer/prompts.
  • 2026-06-10: Replaced sharp with pure JS (jpeg-js + exif-reader); added single-binary bun build and make install.
  • 2026-06-09: Added backup-metadata command (ML data always included, --exif opt-in); rewrote README to match the implementation; added thumbnail helper tests.
  • 2026-05-13: Full CLI surface: login, backup with dedup symlink layout, collections, files, get, get-thumb, thumbnail repair helpers.
  • 2026-05-13: Client OO API with literate usage tests; file download and decryption; all three metadata layers decrypted and persisted; renamed quack to quak.
  • 2026-05-11: SRP login flow (email OTP + TOTP) and ApiClient.

Future Steps

  • Tag v1.0.0.
  • Future desktop client, separate repo:
    • Electron app skeleton consuming this library.
    • Local SQLite cache keyed on (collectionID, fileID, updationTime).
    • Background sync worker streaming new files into the cache.
    • Gallery UI: thumbnails, full-image view, basic search.
    • Upload, delete, and share operations in the library.