check / check (push) Successful in 26s
getJSON built its URL with new URL and then overwrote the host and path, which dropped a base path in a self-hosted apiOrigin; postJSON, putJSON and the file and thumbnail download URLs joined strings. All of them now go through one function next to ApiClient that accepts a path with or without a leading slash and an origin with or without a trailing slash or base path, and percent-encodes query parameters. A path containing "?" or "#" is rejected. Model: opus-5-5
183 lines
11 KiB
Markdown
183 lines
11 KiB
Markdown
# 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
|
|
|
|
Tag v1.0.0.
|
|
|
|
# Completed Steps
|
|
|
|
- 2026-09-23: Every `ApiClient` request URL is now built by one function next to
|
|
the class (issue 18), so a self-hosted `apiOrigin` with a base path keeps it
|
|
on every request, a path works with or without a leading slash, and query
|
|
parameters are percent-encoded. A path containing `?` or `#` is rejected with
|
|
an error instead of being silently cut.
|
|
- 2026-09-23: Hardened the JPEG EXIF scan behind `backup-metadata --exif` (issue
|
|
11). Every segment length is checked against the remaining bytes and lengths
|
|
under 2 stop the scan, so a truncated or corrupt original can neither throw
|
|
nor loop. A malformed or unparseable EXIF segment is recorded as
|
|
`imageMetadata.exifError`, and a failure to read the original as
|
|
`imageMetadataError` in the per-file JSON, instead of the field being left
|
|
out.
|
|
- 2026-09-22: Hardened the retry classifier (issue 80). A `POST` or `PUT` is
|
|
replayed only when every errno in the cause chain is a connect errno, and it
|
|
no longer follows redirects. `getRetryOptions()` returns a copy. Tests pin
|
|
every errno the classifier names, the cause-chain depth limit, cycle
|
|
termination, and a fresh deadline per attempt for every retrying entry point.
|
|
The README's endpoint list is the one place that names the requests the replay
|
|
rule covers.
|
|
- 2026-09-22: Stopped `make test` collecting tests from checkouts nested under
|
|
`.claude/` (issue 25). vitest ignores `.gitignore` when finding tests, so a
|
|
nested checkout ran the whole suite again; `vitest.config.ts` now adds
|
|
`.claude/**` to vitest's default excludes, and
|
|
`test/packaging/nested-checkout.test.ts` plants a nested checkout in a temp
|
|
directory and fails if vitest would collect it.
|
|
- 2026-09-22: Dropped the deprecated `@types/libsodium-wrappers-sumo` stub from
|
|
`devDependencies` (issue 27). It shipped no declarations; the types come from
|
|
`libsodium-wrappers-sumo` itself. `yarn.lock` regenerated by `yarn remove`.
|
|
- 2026-09-22: Hardened the client session lifecycle (issue 10).
|
|
`Client.fromJSON` checks every snapshot field and each key's decoded length
|
|
and names the bad field; `toJSON` reads the token through
|
|
`ApiClient.getAuthToken` and throws when there is none; `logout` zeroes the
|
|
key buffers, and `collectionsSince` re-checks for logout after its request so
|
|
it never decrypts with zeroed keys. The CLI reports a corrupt session file
|
|
separately from a missing one (`src/cli-session.ts`).
|
|
- 2026-09-22: Sanitized file names taken from server metadata (issue 9). A new
|
|
`src/filename.ts` holds the one sanitizer, used by `quak get`/`get-thumb`
|
|
without `--out`, `downloadFile`/`downloadThumbnail` without `outPath`, and the
|
|
backup and metadata backup trees; it removes separators, control characters,
|
|
leading dots and Windows device names, and falls back to a name built from the
|
|
ID for an empty title. Originals-cache extensions are letters and digits only,
|
|
else `.bin`. A user-supplied path is used as is. `decryptFile` reads a missing
|
|
or non-string title as "" and rejects metadata that is not a JSON object.
|
|
- 2026-09-22: Rewrote the README API reference (and the Getting Started / usage
|
|
snippets) to match the shipped cache/API library on `next` (issue 53, issue
|
|
13). Documented `Library.open` and its options, the default-read vs `fresh()`
|
|
distinction (and that the CLI's read commands are fresh), the record types and
|
|
`snapshot()`/`subscribe()`, the `albums`/`photos`/`timeline` read surface,
|
|
`Photo` content methods, `thumbnails.ensure`, the `mldata` search surface,
|
|
`backup()`, the three request pools (10/5/25), and the on-disk cache layout;
|
|
noted the deferred content-hash integrity check (issue 68). Docs-only; no code
|
|
changed.
|
|
- 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
|
|
|
|
- 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.
|