ADMIN: Settings → Admin → Jobs page (Immich-style) — see, pause, resume and stop background work #315

Closed
opened 2026-09-28 08:02:23 +00:00 by kayg · 10 comments
Owner

Owner (2026-09-28, voice grill #304 V3): 'there needs to be a Settings → Jobs page in the admin section like Immich which shows what's going on and jobs can be paused or stopped.'

Context: background work runs on the SQLite Worker (crates/calternal-db/src/worker.rs: leases, retries, active-job dedup, per-kind concurrency). Kinds today and soon: search reindex and embeddings (#258), thumbnails and photo ML, dedup scrub, file reconcile, user purge, mail sync (#313), voice transcription (#304), imports, and the weekly jobs.

Build (reuse Settings components; opinionated and easy; deep-linkable per §33):

  • Settings → Admin → Jobs: one row per job kind (queue) with a live status: running, waiting, failed, and completed in the last 24 h; per-kind concurrency; a progress bar for long jobs (for example 'Embeddings 6,960 / 70,000'); the last error.
  • Actions per queue: Pause, Resume, Run now / Start missing (for rebuild-type jobs), Clear failed, Retry failed. Per running job: Stop (cancel cleanly at the next checkpoint; never corrupt data). All actions are admin-only, with step-up auth for destructive ones, audited in security events, and 403 for non-admins (the #268 authz rules).
  • Pause state persists across restarts. A paused queue shows why and since when.
  • Live updates without a reload (use the existing change/event channel; see the #298 live-update fix).
  • Also a per-User view in Settings (not admin) for the User's own jobs (my transcriptions, my imports, my mail backfill), read-only except Cancel.
  • API + OpenAPI + generated client; tests; an adversarial round (non-admin access, pause/stop races, a stop during a write, a flood of Run now).
    Evidence: screenshots light and dark, desktop and phone.
Owner (2026-09-28, voice grill #304 V3): 'there needs to be a Settings → Jobs page in the admin section like Immich which shows what's going on and jobs can be paused or stopped.' Context: background work runs on the SQLite Worker (crates/calternal-db/src/worker.rs: leases, retries, active-job dedup, per-kind concurrency). Kinds today and soon: search reindex and embeddings (#258), thumbnails and photo ML, dedup scrub, file reconcile, user purge, mail sync (#313), voice transcription (#304), imports, and the weekly jobs. Build (reuse Settings components; opinionated and easy; deep-linkable per §33): - Settings → Admin → **Jobs**: one row per job kind (queue) with a live status: running, waiting, failed, and completed in the last 24 h; per-kind concurrency; a progress bar for long jobs (for example 'Embeddings 6,960 / 70,000'); the last error. - Actions per queue: **Pause**, **Resume**, **Run now** / **Start missing** (for rebuild-type jobs), **Clear failed**, **Retry failed**. Per running job: **Stop** (cancel cleanly at the next checkpoint; never corrupt data). All actions are admin-only, with step-up auth for destructive ones, audited in security events, and 403 for non-admins (the #268 authz rules). - Pause state persists across restarts. A paused queue shows why and since when. - Live updates without a reload (use the existing change/event channel; see the #298 live-update fix). - Also a per-User view in Settings (not admin) for the User's own jobs (my transcriptions, my imports, my mail backfill), read-only except Cancel. - API + OpenAPI + generated client; tests; an adversarial round (non-admin access, pause/stop races, a stop during a write, a flood of Run now). Evidence: screenshots light and dark, desktop and phone.
Author
Owner

Starting job/jobs-page from dev at fba83527f2cccf2334934bb1fd0932be7c0e209b. I am tracing the existing durable calternal-db queue and admin route patterns. I will add the shared job registration/control API first and push that API commit so the parallel voice job can integrate it, then build the admin Jobs view and verify the requested routes and actions.

Starting `job/jobs-page` from `dev` at `fba83527f2cccf2334934bb1fd0932be7c0e209b`. I am tracing the existing durable `calternal-db` queue and admin route patterns. I will add the shared job registration/control API first and push that API commit so the parallel voice job can integrate it, then build the admin Jobs view and verify the requested routes and actions.
Author
Owner

Finding: the existing queue stored jobs and the Worker kept handler concurrency only in a transient map; there was no queryable job-kind catalogue. I added a catalogue migration and named Worker registration, and the calternal-db integration test verifies that the running Worker publishes kind, label and concurrency through the shared queue API. The admin view will read this same SQLite queue.

Finding: the existing queue stored jobs and the Worker kept handler concurrency only in a transient map; there was no queryable job-kind catalogue. I added a catalogue migration and named Worker registration, and the `calternal-db` integration test verifies that the running Worker publishes kind, label and concurrency through the shared queue API. The admin view will read this same SQLite queue.
Author
Owner

Implementation decision: user Home deletion and transfer were a second polling executor outside the shared job queue. They now register in the calternal-db Worker queue with durable retries and progress. Admin Stop is disabled for this kind because its access revocation commits before filesystem cleanup; stopping after that point could leave an account half-deleted. The Settings detail explains why it must finish. Expired Home archive purges also use the shared queue.

Implementation decision: user Home deletion and transfer were a second polling executor outside the shared job queue. They now register in the calternal-db Worker queue with durable retries and progress. Admin Stop is disabled for this kind because its access revocation commits before filesystem cleanup; stopping after that point could leave an account half-deleted. The Settings detail explains why it must finish. Expired Home archive purges also use the shared queue.
Author
Owner

Starting #315 on branch job/jobs-page, based on dev at fba83527f2cccf2334934bb1fd0932be7c0e209b. The shared queue registration and durable controls are committed and pushed so #373 can use the API. I am finishing the server routes, Settings views, and production/adversarial review now.

Starting #315 on branch `job/jobs-page`, based on `dev` at `fba83527f2cccf2334934bb1fd0932be7c0e209b`. The shared queue registration and durable controls are committed and pushed so #373 can use the API. I am finishing the server routes, Settings views, and production/adversarial review now.
Author
Owner

Completed Forgejo #315 on job/jobs-page. I merged dev into this branch once and pushed it. The branch head is 30a23d917789ac2eae042ef3e3290cad0100b22f.

Built

  • Reused the calternal-db Worker queue. Added durable job-kind registration and pause state, ownership, progress checkpoints, payload-free summaries, and safe queue controls.
  • Added Admin Jobs and personal Background work routes. Admin controls require fresh step-up. User routes return only the current User's jobs; a foreign job ID returns 404. Job links use stable IDs.
  • Registered the existing search rebuild, file thumbnail, upload cleanup, archive purge, backup, CAS scrub, and Plugin jobs in the shared Worker. User Home deletion/transfer also uses that queue. The public Worker::register_when_controlled_named API is pushed for the parallel voice job #373.
  • Added the Settings UI, OpenAPI/client types, screenshot capture helper, and Jobs API adversarial probe.

Files

  • Queue and Worker: crates/calternal-db/src/{jobs.rs,worker.rs,db.rs,migrations.rs}, crates/calternal-db/src/migrations/0003_job_kinds.sql through 0005_job_execution_state.sql, crates/calternal-db/tests/queue.rs
  • Routes and auth: crates/calternal-server/src/wire.rs, crates/calternal-auth/src/store.rs, crates/calternal-plugin/src/lib.rs
  • Settings UI and API contract: apps/web/src/lib/jobs/api.ts, apps/web/src/routes/settings/{[...path]/+page.svelte,sections.ts,admin/JobsGroup.svelte,jobs/JobDetails.svelte,jobs/JobRow.svelte,jobs/MyJobsGroup.svelte}, contracts/openapi.json, packages/api-client/src/generated.ts
  • Adversarial and screenshot probes: tests/adversarial/{jobs.py,jobs-screenshots.mjs,run.sh,setup.mjs,authz_matrix.py}

Gate output

  • cargo fmt --check: no stdout or stderr; exit 0.

  • cargo clippy --all-targets -- -D warnings:

        Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 04s
    
  • cargo test stopped at the existing collaboration performance assertion:

    test session::public_edit_limit_tests::ten_thousand_markdown_blocks_open_and_sync_within_two_seconds ... FAILED
    10,000-block collaboration phases: parse=1.639163314s, Yrs=527.974898ms, block-index=34.721714ms, first-sync=57.060574ms (616204 bytes), snapshot=52.02759ms (616199 bytes), total=2.31094809s
    test result: FAILED. 18 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.39s
    

    This SLOW-only test exceeded its 2s limit by 0.31094809s on the shared build host. I left the existing expectation unchanged.

  • bun run check:

    svelte-check found 0 errors and 0 warnings
    
  • bun run test:

    Test Files  2 failed | 115 passed (117)
          Tests  2 failed | 762 passed (764)
    

    The two failures were KeyboardShortcutsCard > puts the current view group first and closes with Escape and StatRow summary cards > toggles all four cards together and remembers the mode; both hit the existing 5000ms timeout during the 277.36s suite run. These are SLOW-only timeouts. No test expectation was changed.

  • Production web build:

    ✓ built in 1m 29s
      Wrote site to "build"
      ✔ done
    
  • Adversarial probe:

    Jobs API adversarial probe: 401/403 isolation, Unicode/double pause, malformed and oversized input, 17 concurrent controls, terminal stop conflict, per-User list/detail/cancel, audit events: PASS
    
  • Cleanup: Removed 26144 files, 16.9GiB total; apps/web/build removed.

Decisions and gaps

  • Admin Stop is disabled for User Home deletion/transfer. Access revocation commits before filesystem cleanup, so stopping after that point could leave the account incomplete. The job stays queued for durable retry and its detail view explains why it must finish.
  • docs/DESIGN.md §27 says “No importers for now.” I did not add an importer job kind. The shared Plugin registration API is ready for an importer when that work is designed.
  • The voice worker remains in parallel job #373; its registration entry point is available on this pushed branch.
  • The full Rust suite stopped at the SLOW collaboration limit, and the web suite had the two SLOW component timeouts above. The API adversarial probe passed; it found no non-SLOW security or consistency issue.

Post-merge production screenshots (390, 820, and 1440 CSS px; light and dark)

Screen 390 px 820 px 1440 px
Admin Jobs Light · Dark Light · Dark Light · Dark
My jobs Light · Dark Light · Dark Light · Dark
Admin Backups Light · Dark Light · Dark Light · Dark
Completed Forgejo #315 on `job/jobs-page`. I merged `dev` into this branch once and pushed it. The branch head is `30a23d917789ac2eae042ef3e3290cad0100b22f`. **Built** - Reused the `calternal-db` Worker queue. Added durable job-kind registration and pause state, ownership, progress checkpoints, payload-free summaries, and safe queue controls. - Added Admin Jobs and personal Background work routes. Admin controls require fresh step-up. User routes return only the current User's jobs; a foreign job ID returns 404. Job links use stable IDs. - Registered the existing search rebuild, file thumbnail, upload cleanup, archive purge, backup, CAS scrub, and Plugin jobs in the shared Worker. User Home deletion/transfer also uses that queue. The public `Worker::register_when_controlled_named` API is pushed for the parallel voice job #373. - Added the Settings UI, OpenAPI/client types, screenshot capture helper, and Jobs API adversarial probe. **Files** - Queue and Worker: `crates/calternal-db/src/{jobs.rs,worker.rs,db.rs,migrations.rs}`, `crates/calternal-db/src/migrations/0003_job_kinds.sql` through `0005_job_execution_state.sql`, `crates/calternal-db/tests/queue.rs` - Routes and auth: `crates/calternal-server/src/wire.rs`, `crates/calternal-auth/src/store.rs`, `crates/calternal-plugin/src/lib.rs` - Settings UI and API contract: `apps/web/src/lib/jobs/api.ts`, `apps/web/src/routes/settings/{[...path]/+page.svelte,sections.ts,admin/JobsGroup.svelte,jobs/JobDetails.svelte,jobs/JobRow.svelte,jobs/MyJobsGroup.svelte}`, `contracts/openapi.json`, `packages/api-client/src/generated.ts` - Adversarial and screenshot probes: `tests/adversarial/{jobs.py,jobs-screenshots.mjs,run.sh,setup.mjs,authz_matrix.py}` **Gate output** - `cargo fmt --check`: no stdout or stderr; exit 0. - `cargo clippy --all-targets -- -D warnings`: ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 04s ``` - `cargo test` stopped at the existing collaboration performance assertion: ```text test session::public_edit_limit_tests::ten_thousand_markdown_blocks_open_and_sync_within_two_seconds ... FAILED 10,000-block collaboration phases: parse=1.639163314s, Yrs=527.974898ms, block-index=34.721714ms, first-sync=57.060574ms (616204 bytes), snapshot=52.02759ms (616199 bytes), total=2.31094809s test result: FAILED. 18 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.39s ``` This SLOW-only test exceeded its 2s limit by 0.31094809s on the shared build host. I left the existing expectation unchanged. - `bun run check`: ```text svelte-check found 0 errors and 0 warnings ``` - `bun run test`: ```text Test Files 2 failed | 115 passed (117) Tests 2 failed | 762 passed (764) ``` The two failures were `KeyboardShortcutsCard > puts the current view group first and closes with Escape` and `StatRow summary cards > toggles all four cards together and remembers the mode`; both hit the existing 5000ms timeout during the 277.36s suite run. These are SLOW-only timeouts. No test expectation was changed. - Production web build: ```text ✓ built in 1m 29s Wrote site to "build" ✔ done ``` - Adversarial probe: ```text Jobs API adversarial probe: 401/403 isolation, Unicode/double pause, malformed and oversized input, 17 concurrent controls, terminal stop conflict, per-User list/detail/cancel, audit events: PASS ``` - Cleanup: `Removed 26144 files, 16.9GiB total`; `apps/web/build` removed. **Decisions and gaps** - Admin Stop is disabled for User Home deletion/transfer. Access revocation commits before filesystem cleanup, so stopping after that point could leave the account incomplete. The job stays queued for durable retry and its detail view explains why it must finish. - `docs/DESIGN.md` §27 says “No importers for now.” I did not add an importer job kind. The shared Plugin registration API is ready for an importer when that work is designed. - The voice worker remains in parallel job #373; its registration entry point is available on this pushed branch. - The full Rust suite stopped at the SLOW collaboration limit, and the web suite had the two SLOW component timeouts above. The API adversarial probe passed; it found no non-SLOW security or consistency issue. **Post-merge production screenshots** (390, 820, and 1440 CSS px; light and dark) | Screen | 390 px | 820 px | 1440 px | | --- | --- | --- | --- | | Admin Jobs | [Light](https://git.kayg.org/attachments/0b30a547-8379-4a23-8af0-dcefd77dbdd8) · [Dark](https://git.kayg.org/attachments/eec0d572-1869-4c36-802e-726244c9186a) | [Light](https://git.kayg.org/attachments/c506be52-23f0-44bd-8b2a-8d0b13c98762) · [Dark](https://git.kayg.org/attachments/44647683-11ee-4376-8e43-7669f5d00e61) | [Light](https://git.kayg.org/attachments/2765cb1a-e028-45a3-bc5c-492f6ffdc477) · [Dark](https://git.kayg.org/attachments/1bc3223b-44e1-4947-b2a4-320f18c2436d) | | My jobs | [Light](https://git.kayg.org/attachments/f9097fb3-14f2-4686-907c-ff9527acb2d7) · [Dark](https://git.kayg.org/attachments/83b6e9eb-e56e-461c-bfdb-0ba596ed65d0) | [Light](https://git.kayg.org/attachments/19b72d5c-1473-4ef2-8a20-4aa113e9952b) · [Dark](https://git.kayg.org/attachments/caae8c51-72de-4cf9-a6be-92935902840b) | [Light](https://git.kayg.org/attachments/6dc40fd1-00c5-4979-ab6d-70000bfdb847) · [Dark](https://git.kayg.org/attachments/c4755706-6e59-45ef-a7c8-31b440b388eb) | | Admin Backups | [Light](https://git.kayg.org/attachments/cd7d4931-efaa-464b-a94a-1c60c24cdd9a) · [Dark](https://git.kayg.org/attachments/aade24da-cfed-441d-bbfb-853aa29aa96f) | [Light](https://git.kayg.org/attachments/fc25ce7a-0f85-4b25-b5fd-42a99ce6481b) · [Dark](https://git.kayg.org/attachments/7cee5bb5-a9b8-49dc-894d-4690f2116b33) | [Light](https://git.kayg.org/attachments/06f27532-51f3-40ae-b5a9-6a8fb9ea28fe) · [Dark](https://git.kayg.org/attachments/099b683e-2701-446c-943f-76f4207eebfe) |
Author
Owner

Review of job/jobs-page 30a23d91 (Claude): backend merged (86f3529a), one UI round

The queue registry and APIs are in dev now. The list does not yet do what the issue asks (Immich-style: see, pause, resume, stop at a glance):

  1. Friendly names for every queue. Rows mix "Thumbnail generation" with raw ids ("mail.sync", "notes.reconcile", "notifications.dispatch", "photos.clip.index"). Every registered kind needs a display name and a one-line description (register them with the kind); the raw id appears only in Details.
  2. Drop the raw id and zero counts from the row subtitle. Show only what matters: "3 running · 120 waiting", "Idle", "Paused", or "2 failed" (in the warning colour), plus the last run time.
  3. Pause/Resume inline: one pill per row (the shared Pill), not hidden in Details. Stop and Retry failed live in the row's ⋯ menu (shared Menu), with Copy link and Details. No free-floating link icon.
  4. Distinct icons per kind (thumbnail → image, search → search, mail → mail, backup → archive, integrity check → shield, purge → trash, notifications → bell, reconcile → refresh).
  5. Group by area (Files, Photos, Search, Mail, Notes, System) with the shared sidebar-style section header.
  6. Phone: row stacks as the Mail account row does; the actions stay on one line.

Screenshots at 390/820/1440, light and dark, with the theme asserted before capture; include a paused queue, a running one with progress and a failed one. Web gates verbatim. Merge dev first. Commit and push; no merge into dev.

## Review of job/jobs-page 30a23d91 (Claude): backend merged (86f3529a), one UI round The queue registry and APIs are in dev now. The list does not yet do what the issue asks (Immich-style: see, pause, resume, stop at a glance): 1. **Friendly names for every queue.** Rows mix "Thumbnail generation" with raw ids ("mail.sync", "notes.reconcile", "notifications.dispatch", "photos.clip.index"). Every registered kind needs a display name and a one-line description (register them with the kind); the raw id appears only in Details. 2. **Drop the raw id and zero counts from the row subtitle.** Show only what matters: "3 running · 120 waiting", "Idle", "Paused", or "2 failed" (in the warning colour), plus the last run time. 3. **Pause/Resume inline:** one pill per row (the shared Pill), not hidden in Details. Stop and Retry failed live in the row's ⋯ menu (shared Menu), with Copy link and Details. No free-floating link icon. 4. **Distinct icons per kind** (thumbnail → image, search → search, mail → mail, backup → archive, integrity check → shield, purge → trash, notifications → bell, reconcile → refresh). 5. **Group by area** (Files, Photos, Search, Mail, Notes, System) with the shared sidebar-style section header. 6. Phone: row stacks as the Mail account row does; the actions stay on one line. Screenshots at 390/820/1440, light and dark, with the theme asserted before capture; include a paused queue, a running one with progress and a failed one. Web gates verbatim. Merge dev first. Commit and push; no merge into dev.
Author
Owner

Starting the UI review round on job/jobs-page. Current branch head: 30a23d917789ac2eae042ef3e3290cad0100b22f. Merging the current dev head 86f3529aa040f999b6f0d6249fe5e4ff83faf5fc before UI work.

Starting the UI review round on `job/jobs-page`. Current branch head: `30a23d917789ac2eae042ef3e3290cad0100b22f`. Merging the current `dev` head `86f3529aa040f999b6f0d6249fe5e4ff83faf5fc` before UI work.
Author
Owner

Finding after merging dev at 86f3529a: JobKindRegistration stores only kind, label, and concurrency, so registered job kinds cannot provide the requested one-line descriptions. AdminJobQueueView rows currently put the raw kind and all zero counts in the subtitle, and JobRow uses the generic activity icon with standalone Details/Copy link actions. I am extending the registered metadata and replacing those row controls with the shared menu and inline Pill patterns.

Finding after merging `dev` at `86f3529a`: `JobKindRegistration` stores only `kind`, `label`, and `concurrency`, so registered job kinds cannot provide the requested one-line descriptions. `AdminJobQueueView` rows currently put the raw kind and all zero counts in the subtitle, and `JobRow` uses the generic activity icon with standalone Details/Copy link actions. I am extending the registered metadata and replacing those row controls with the shared menu and inline Pill patterns.
Author
Owner

Workspace gate finding: cargo test fails in calternal-plugin-notes at tests::retitle_replays_after_each_persisted_step. The panic is at crates/plugins/notes/src/lib.rs:6466 when restart recovery returns HTTP 500 (Notes operation failed). A targeted rerun of that single test reproduces the failure. This job does not change the retitle/recovery implementation or its assertion; I am recording the failure for owner triage.

Workspace gate finding: `cargo test` fails in `calternal-plugin-notes` at `tests::retitle_replays_after_each_persisted_step`. The panic is at `crates/plugins/notes/src/lib.rs:6466` when restart recovery returns HTTP 500 (`Notes operation failed`). A targeted rerun of that single test reproduces the failure. This job does not change the retitle/recovery implementation or its assertion; I am recording the failure for owner triage.
Author
Owner

Completed

Merged dev once at 86f3529a, added queue descriptions with migration 0006, and built the Admin Jobs review: friendly queue names and descriptions, compact status and last-run lines, six kind groups, distinct icons, inline Pause/Resume, and queue/job ⋯ menus. The shared job row keeps its existing Background work actions. The adversarial job probe and production screenshot interactions passed.

Commits: 99c2643e (feat(jobs): register queue descriptions) and 4aa46cc1 (feat(settings): streamline admin job queues). Head: 4aa46cc158abd4b370a6cf4c1679169823b11446. origin/job/jobs-page matches this head.

Files: crates/calternal-db/{src/jobs.rs,src/migrations.rs,src/migrations/0006_job_kind_descriptions.sql,src/worker.rs,tests/queue.rs}, crates/calternal-plugin/src/lib.rs, crates/calternal-server/src/wire.rs, crates/plugins/{mail/src/sync.rs,notes/src/lib.rs,notifications/src/lib.rs,photos/src/search.rs,video/src/transcode.rs}, contracts/openapi.json, packages/api-client/src/generated.ts; apps/web/src/lib/jobs/{api.ts,api.test.ts}, apps/web/src/routes/settings/admin/JobsGroup.svelte, apps/web/src/routes/settings/jobs/{JobDetails.svelte,JobRow.svelte}, apps/web/src/routes/settings/parts/{AccountRow.svelte,icons.ts}; tests/adversarial/{jobs-screenshots.mjs,jobs.py,run.sh}.

Screenshot attachments

All six production captures are attached here and kept in artifacts/jobs-page/:

The captures show the paused queue and running progress. The probe asserted 1 failed in each page, but the failed row sits below the Settings panel's scroll viewport and is not visible in these six captures.

Gates

cargo fmt --check: exit 0; stdout was empty.

cargo clippy --all-targets -- -D warnings:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 6m 04s

cargo test:

---- tests::retitle_replays_after_each_persisted_step stdout ----
thread 'tests::retitle_replays_after_each_persisted_step' panicked at crates/plugins/notes/src/lib.rs:6466:39:
called `Result::unwrap()` on an `Err` value: ApiError(500, ErrorEnvelope { error: ApiError { code: Internal, message: "Notes operation failed", details: None } })

test result: FAILED. 108 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 78.49s
error: test failed, to rerun pass `-p calternal-plugin-notes --lib`

A targeted rerun also failed: 0 passed; 1 failed; 0 ignored; 0 measured; 108 filtered out. The retitle/recovery implementation and its assertion were not changed; I commented this finding above.

bun run --cwd apps/web check:

$ svelte-kit sync && svelte-check --tsconfig ./tsconfig.json

Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/jobs-page/apps/web
Getting Svelte diagnostics...

svelte-check found 0 errors and 0 warnings

bun run --cwd apps/web test:

 FAIL  |unit| src/lib/themes.test.ts > rendered theme contrast contract > keeps glass materials in the shared role tokens
Error: Test timed out in 5000ms.

 Test Files  1 failed | 118 passed (119)
      Tests  1 failed | 769 passed (770)
error: script "test" exited with code 1

Adversarial output:

Admin Jobs interaction: job ⋯ menu exposed Stop; queue ⋯ Details opened the registered description and kind.
Jobs API adversarial probe: 401/403 isolation, Unicode/double pause, malformed and oversized input, 17 concurrent controls, terminal stop conflict, per-User list/detail/cancel, audit events: PASS

Decisions

The existing registration constructor uses the label as its description so older callers keep working; migration 0006 gives existing rows the same fallback. Video work is grouped under Photos because the required group list has no Video section. Queue status gives Paused priority, then failure count, then running/waiting counts; otherwise it says Idle. Last run uses the newest non-pending job in the API's 24-hour view.

Known gaps: the Notes recovery test failure above; the one timed-out web theme scan; and the failed queue is asserted but below the viewport in the six captures. Cargo target output and apps/web/build were cleaned.

## Completed Merged `dev` once at `86f3529a`, added queue descriptions with migration 0006, and built the Admin Jobs review: friendly queue names and descriptions, compact status and last-run lines, six kind groups, distinct icons, inline Pause/Resume, and queue/job `⋯` menus. The shared job row keeps its existing Background work actions. The adversarial job probe and production screenshot interactions passed. Commits: `99c2643e` (`feat(jobs): register queue descriptions`) and `4aa46cc1` (`feat(settings): streamline admin job queues`). Head: `4aa46cc158abd4b370a6cf4c1679169823b11446`. `origin/job/jobs-page` matches this head. Files: `crates/calternal-db/{src/jobs.rs,src/migrations.rs,src/migrations/0006_job_kind_descriptions.sql,src/worker.rs,tests/queue.rs}`, `crates/calternal-plugin/src/lib.rs`, `crates/calternal-server/src/wire.rs`, `crates/plugins/{mail/src/sync.rs,notes/src/lib.rs,notifications/src/lib.rs,photos/src/search.rs,video/src/transcode.rs}`, `contracts/openapi.json`, `packages/api-client/src/generated.ts`; `apps/web/src/lib/jobs/{api.ts,api.test.ts}`, `apps/web/src/routes/settings/admin/JobsGroup.svelte`, `apps/web/src/routes/settings/jobs/{JobDetails.svelte,JobRow.svelte}`, `apps/web/src/routes/settings/parts/{AccountRow.svelte,icons.ts}`; `tests/adversarial/{jobs-screenshots.mjs,jobs.py,run.sh}`. ## Screenshot attachments All six production captures are attached here and kept in `artifacts/jobs-page/`: - [390 light](https://git.kayg.org/attachments/34793958-37fa-4748-bacf-4bb3ad1a30a4) · [390 dark](https://git.kayg.org/attachments/912d5961-6689-428c-b1e0-a3a55a28b766) - [820 light](https://git.kayg.org/attachments/9a7b5a96-7898-4f43-9bf3-4d885f6f657f) · [820 dark](https://git.kayg.org/attachments/7430fdce-4bf5-4568-9fe1-df7eeb6e756d) - [1440 light](https://git.kayg.org/attachments/1fc6322b-4fff-4f8a-b8d2-4b5f7e21e979) · [1440 dark](https://git.kayg.org/attachments/b4bf4d8b-9b13-415a-b539-3dad683cbf28) The captures show the paused queue and running progress. The probe asserted `1 failed` in each page, but the failed row sits below the Settings panel's scroll viewport and is not visible in these six captures. ## Gates `cargo fmt --check`: exit 0; stdout was empty. `cargo clippy --all-targets -- -D warnings`: ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 6m 04s ``` `cargo test`: ```text ---- tests::retitle_replays_after_each_persisted_step stdout ---- thread 'tests::retitle_replays_after_each_persisted_step' panicked at crates/plugins/notes/src/lib.rs:6466:39: called `Result::unwrap()` on an `Err` value: ApiError(500, ErrorEnvelope { error: ApiError { code: Internal, message: "Notes operation failed", details: None } }) test result: FAILED. 108 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 78.49s error: test failed, to rerun pass `-p calternal-plugin-notes --lib` ``` A targeted rerun also failed: `0 passed; 1 failed; 0 ignored; 0 measured; 108 filtered out`. The retitle/recovery implementation and its assertion were not changed; I commented this finding above. `bun run --cwd apps/web check`: ```text $ svelte-kit sync && svelte-check --tsconfig ./tsconfig.json Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/jobs-page/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` `bun run --cwd apps/web test`: ```text FAIL |unit| src/lib/themes.test.ts > rendered theme contrast contract > keeps glass materials in the shared role tokens Error: Test timed out in 5000ms. Test Files 1 failed | 118 passed (119) Tests 1 failed | 769 passed (770) error: script "test" exited with code 1 ``` Adversarial output: ```text Admin Jobs interaction: job ⋯ menu exposed Stop; queue ⋯ Details opened the registered description and kind. Jobs API adversarial probe: 401/403 isolation, Unicode/double pause, malformed and oversized input, 17 concurrent controls, terminal stop conflict, per-User list/detail/cancel, audit events: PASS ``` ## Decisions The existing registration constructor uses the label as its description so older callers keep working; migration 0006 gives existing rows the same fallback. Video work is grouped under Photos because the required group list has no Video section. Queue status gives Paused priority, then failure count, then running/waiting counts; otherwise it says Idle. Last run uses the newest non-pending job in the API's 24-hour view. Known gaps: the Notes recovery test failure above; the one timed-out web theme scan; and the failed queue is asserted but below the viewport in the six captures. Cargo target output and `apps/web/build` were cleaned.
kayg closed this issue 2026-09-29 03:37:51 +00:00
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/calternal#315
No description provided.