feat(bulk): scan-file by-filter + library kebab menu with count confirmation [shot:library-scan-file-screenshot] (bookshelf-cs2zl.2) #1239

Merged
zombor merged 6 commits from bd-bookshelf-cs2zl.2 into main 2026-07-26 03:00:54 +00:00
Owner

Summary

  • By-filter handler (POST /books/bulk/filter/scan-file): triggers BulkByFilterWorkflow with Op=scan-file over all books matching the current view filter, bounded fan-out, ContinueAsNew pagination.
  • Workflow: Added BulkFilterOpScanFile constant to BulkByFilterWorkflow, bulkByFilterScanFile helper, ProviderID field carried through all ContinueAsNew epochs.
  • Library kebab menu: "Scan All Files for Metadata" button (shown when LLMVisionAvailable), with AppDialog.confirm count confirmation ("You are about to scan N files for metadata. This uses your LLM budget.") before POSTing.
  • Registry update: scan-file bulk action entry changed from filterUrl: null, filterNA: true to filterUrl: "/books/bulk/filter/scan-file".
  • Audit: ActionBulkScanFile = "BULK_SCAN_FILE" constant added.
  • 100% Go coverage; 4318 JS tests pass.

Test plan

  • make test — all Go tests pass
  • make coverage — 100% coverage, zero uncovered blocks
  • npm run test — 4318 JS tests pass (including 7 new SCAN FILES tests + registry test)
  • Workflow test: BulkByFilterWorkflow — scan-file op with ≥3 ContinueAsNew epochs verifies 10 sub-workflows spawned, ProviderID threaded through
  • Handler test: BulkScanFileByFilterHandler — 202 success, provider_id threading, 400 on bad status/format, 500 on workflow error, magic shelf check
  • Routes test: registers POST /books/bulk/filter/scan-file + permission gate entry
  • JS tests: SCAN FILES describe covers confirm dialog, POST body, cancel, success toast+navigate, server error, network error, AppDialog absent

Closes bead bookshelf-cs2zl.2 on merge.

## Summary - **By-filter handler** (`POST /books/bulk/filter/scan-file`): triggers `BulkByFilterWorkflow` with `Op=scan-file` over all books matching the current view filter, bounded fan-out, ContinueAsNew pagination. - **Workflow**: Added `BulkFilterOpScanFile` constant to `BulkByFilterWorkflow`, `bulkByFilterScanFile` helper, `ProviderID` field carried through all ContinueAsNew epochs. - **Library kebab menu**: "Scan All Files for Metadata" button (shown when `LLMVisionAvailable`), with `AppDialog.confirm` count confirmation ("You are about to scan N files for metadata. This uses your LLM budget.") before POSTing. - **Registry update**: `scan-file` bulk action entry changed from `filterUrl: null, filterNA: true` to `filterUrl: "/books/bulk/filter/scan-file"`. - **Audit**: `ActionBulkScanFile = "BULK_SCAN_FILE"` constant added. - 100% Go coverage; 4318 JS tests pass. ## Test plan - [x] `make test` — all Go tests pass - [x] `make coverage` — 100% coverage, zero uncovered blocks - [x] `npm run test` — 4318 JS tests pass (including 7 new SCAN FILES tests + registry test) - [x] Workflow test: `BulkByFilterWorkflow — scan-file op with ≥3 ContinueAsNew epochs` verifies 10 sub-workflows spawned, ProviderID threaded through - [x] Handler test: `BulkScanFileByFilterHandler` — 202 success, provider_id threading, 400 on bad status/format, 500 on workflow error, magic shelf check - [x] Routes test: `registers POST /books/bulk/filter/scan-file` + permission gate entry - [x] JS tests: `SCAN FILES` describe covers confirm dialog, POST body, cancel, success toast+navigate, server error, network error, AppDialog absent Closes bead bookshelf-cs2zl.2 on merge.
feat(bulk): scan-file by-filter + library kebab menu entry with count confirmation (bookshelf-cs2zl.2)
Some checks failed
/ E2E Browser (pull_request) Has been cancelled
/ Integration (pull_request) Has been cancelled
/ Test Race (pull_request) Has been cancelled
/ E2E API (pull_request) Has been cancelled
/ JS Unit Tests (pull_request) Has been cancelled
/ Lint (pull_request) Has been cancelled
/ Coverage (pull_request) Has been cancelled
ddb4bfbf3b
- Add BulkFilterOpScanFile to BulkByFilterWorkflow with bounded fan-out
  via bulkByFilterScanFile (mirrors bulkByFilterLLMVision pattern)
- Carry ProviderID through ContinueAsNew epochs
- Add POST /books/bulk/filter/scan-file handler (BulkScanFileByFilterHandler)
- Update bulk-action registry: scan-file entry gains filterUrl, drops filterNA
- Library kebab menu: add scanFileUrl + bookCount values; add scanFiles() method
  that shows count confirmation dialog (LLM budget warning) then POSTs filter op
- Template: wire scan-file-url + book-count data attrs; add "Scan All Files for
  Metadata" button (conditional on LLMVisionAvailable)
- Add ActionBulkScanFile audit constant
- 100% Go coverage; 4318 JS tests pass

Closes bead bookshelf-cs2zl.2 on merge.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
test(e2e/browser): journey — library kebab "Scan All Files" count confirmation
Some checks failed
/ JS Unit Tests (pull_request) Failing after 2m11s
/ E2E API (pull_request) Successful in 2m59s
/ Test Race (pull_request) Successful in 3m53s
/ Lint (pull_request) Successful in 5m14s
/ Coverage (pull_request) Successful in 4m38s
/ Integration (pull_request) Has been cancelled
/ E2E Browser (pull_request) Has been cancelled
a04846eaab
Browser journey proves the Chromium-level interaction:
- sidebar kebab renders "Scan All Files for Metadata" when LLM vision is configured
- clicking the button shows the AppDialog.confirm() modal (.apd-overlay)
- dialog text references the scan action; screenshot posted to PR

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(js): remove redundant AppDialog guards in scanFiles — outer guard already ensures presence
Some checks failed
/ JS Unit Tests (pull_request) Successful in 1m19s
/ Test Race (pull_request) Successful in 3m16s
/ E2E API (pull_request) Successful in 2m1s
/ Coverage (pull_request) Successful in 3m36s
/ Lint (pull_request) Successful in 5m17s
/ Integration (pull_request) Successful in 5m56s
/ E2E Browser (pull_request) Failing after 6m31s
2aab70c224
The outer `if (!dialog) { return; }` guard at the start of scanFiles()
establishes that `dialog` (window.AppDialog) is non-null before the
fetch runs. Re-checking `if (window.AppDialog)` inside .then()/.catch()
created dead-code branches (always truthy at that point) that the V8
coverage tool flagged as uncovered, dropping branch coverage to 97.11%.

Replace the inner window.AppDialog re-checks with direct `dialog.toast(...)`
calls using the captured `dialog` variable, which is proven non-null.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(e2e/browser): collapse two-It library scan-file journey to one to avoid menu state dependency
Some checks failed
/ JS Unit Tests (pull_request) Successful in 1m47s
/ Test Race (pull_request) Successful in 3m25s
/ Lint (pull_request) Successful in 5m31s
/ E2E API (pull_request) Successful in 3m16s
/ Coverage (pull_request) Successful in 4m0s
/ E2E Browser (pull_request) Failing after 5m5s
/ Integration (pull_request) Successful in 6m2s
046debf3c6
The first It left the kebab menu open; the second It's toggle.MustClick()
closed it before MustElement([data-action*="scanFiles"]) ran, causing a
panic. Merge the open-kebab + click-button + assert-dialog steps into one
It — multi-Expect is explicitly permitted in browser e2e journey Its per
policy. The screenshot It remains a separate step so it always captures
the open-modal state from the previous step.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(e2e/browser): use page.Eval (non-Must) in Eventually + DeferCleanup pattern
All checks were successful
/ JS Unit Tests (pull_request) Successful in 2m3s
/ Test Race (pull_request) Successful in 3m8s
/ E2E API (pull_request) Successful in 2m44s
/ Lint (pull_request) Successful in 5m19s
/ Coverage (pull_request) Successful in 3m45s
/ Integration (pull_request) Successful in 5m53s
/ E2E Browser (pull_request) Successful in 5m55s
92f44da729
MustElement/MustEval panic when element not found or page context fails.
Inside Eventually, these panics propagate uncaught, crashing proc 1 with
exit status 2 and no Ginkgo output.

Adopt the pattern from journey_bulk_scan_file_test.go:
- Use page.Eval (returns error) inside Eventually so transient failures
  are retried as false, not panicked
- Use page.Element (returns error, checked with Expect) for actual clicks
- Use DeferCleanup instead of explicit AfterAll for seedLLMVisionConfig
  and page cleanup — same pattern as bulk_scan_file journey

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
zombor force-pushed bd-bookshelf-cs2zl.2 from 92f44da729
All checks were successful
/ JS Unit Tests (pull_request) Successful in 2m3s
/ Test Race (pull_request) Successful in 3m8s
/ E2E API (pull_request) Successful in 2m44s
/ Lint (pull_request) Successful in 5m19s
/ Coverage (pull_request) Successful in 3m45s
/ Integration (pull_request) Successful in 5m53s
/ E2E Browser (pull_request) Successful in 5m55s
to 14a877d576
All checks were successful
/ JS Unit Tests (pull_request) Successful in 1m41s
/ E2E API (pull_request) Successful in 2m46s
/ Test Race (pull_request) Successful in 3m51s
/ Lint (pull_request) Successful in 4m28s
/ Coverage (pull_request) Successful in 3m53s
/ Integration (pull_request) Successful in 4m20s
/ E2E Browser (pull_request) Successful in 5m42s
2026-07-25 01:40:22 +00:00
Compare
Author
Owner

Security review — PR #1239 (bd-bookshelf-cs2zl.2)

New POST /books/bulk/filter/scan-file (LLM scan-file over all filter-matching / whole-library books) + library kebab trigger.

Multi-user scoping — PASS (fail-closed). bulkScanFileFilterRequest reuses the shared bulkFilterHandler: userID is taken from the session (userIDFromRequest(r)), never the body; library_id/shelf_id/status are re-scoped to that userID (ShelfUserID/StatusUserID); magic_shelf_id is ownership-checked via checkMagicShelfAccess (404 on miss). Book-ID resolution re-resolves userLibraryIDs per ContinueAsNew epoch in buildListFilteredIDsPageFn and passes them to ListFilteredBookIDsPage, whose predicate is fail-closed (internal/books/filter_predicates.go:104library_id IN (...); non-nil empty → 1=0). users.GetUserLibraryIDs normalizes nil → []int64{}, so a zero-library user takes the 1=0 branch — a body-supplied library_id the user can't access yields no rows, not a cross-user leak.

Auth — PASS. Route is gated g.BulkScanFile(...)BookBulkScanFileRequiredusers.PermissionRequired(..., PermissionBulkAutoFetchMetadata) (internal/app/app.go:355), same real permission as the sibling by-IDs endpoint. Not "any logged-in user."

Resource-exhaustion / cost DoS — PASS. Fan-out is bounded single-digit (defaultFanOutConcurrency = 4, internal/wfengine/fanout.go:19) and sub-workflows route to the LLM queue (scanFileFanOutOptions) so vision activities respect the GPU/concurrency cap; the kebab entry is gated behind {{if $.LLMVisionAvailable}} and a dialog.confirm count prompt.

Injection — PASS. All SQL is sqlc/parameterized; view_query validated via ParseViewQueryFilter at the boundary; status/format/metadata filters allowlist-validated; audit action is a constant.

Workflow versioning — SAFE (no gate needed). The new case BulkFilterOpScanFile in bulkByFilterApplyOp is selected by the per-instance-immutable input.Op; in-flight instances carry a different Op and keep their original command sequence, so replay does not diverge.

Findings

[MINOR] templates/layouts/base.html:200 — count-confirmation shows 0 for the largest libraries
data-...-book-count-value="{{if .HasCount}}{{.Count}}{{else}}0{{end}}" falls back to 0 when the count is unavailable (HasCount false) — which per the Scale convention is exactly the large/unfiltered libraries where the "you are about to scan N files … uses your LLM budget" confirmation matters most. The most expensive case shows the least alarming number, weakening the secondary cost guard. Authorization/scoping/permission gates still fully protect the operation, so this is UX-quality, not a vulnerability. Suggest showing an indeterminate message ("all files in this library") when HasCount is false rather than "0 files".

REVIEW VERDICT: 0 blocker, 0 major, 1 minor

## Security review — PR #1239 (`bd-bookshelf-cs2zl.2`) New `POST /books/bulk/filter/scan-file` (LLM scan-file over all filter-matching / whole-library books) + library kebab trigger. **Multi-user scoping — PASS (fail-closed).** `bulkScanFileFilterRequest` reuses the shared `bulkFilterHandler`: `userID` is taken from the session (`userIDFromRequest(r)`), never the body; `library_id`/`shelf_id`/`status` are re-scoped to that userID (`ShelfUserID`/`StatusUserID`); `magic_shelf_id` is ownership-checked via `checkMagicShelfAccess` (404 on miss). Book-ID resolution re-resolves `userLibraryIDs` **per ContinueAsNew epoch** in `buildListFilteredIDsPageFn` and passes them to `ListFilteredBookIDsPage`, whose predicate is fail-closed (`internal/books/filter_predicates.go:104` → `library_id IN (...)`; non-nil empty → `1=0`). `users.GetUserLibraryIDs` normalizes `nil → []int64{}`, so a zero-library user takes the `1=0` branch — a body-supplied `library_id` the user can't access yields no rows, not a cross-user leak. **Auth — PASS.** Route is gated `g.BulkScanFile(...)` → `BookBulkScanFileRequired` → `users.PermissionRequired(..., PermissionBulkAutoFetchMetadata)` (`internal/app/app.go:355`), same real permission as the sibling by-IDs endpoint. Not "any logged-in user." **Resource-exhaustion / cost DoS — PASS.** Fan-out is bounded single-digit (`defaultFanOutConcurrency = 4`, `internal/wfengine/fanout.go:19`) and sub-workflows route to the LLM queue (`scanFileFanOutOptions`) so vision activities respect the GPU/concurrency cap; the kebab entry is gated behind `{{if $.LLMVisionAvailable}}` and a `dialog.confirm` count prompt. **Injection — PASS.** All SQL is sqlc/parameterized; `view_query` validated via `ParseViewQueryFilter` at the boundary; status/format/metadata filters allowlist-validated; audit action is a constant. **Workflow versioning — SAFE (no gate needed).** The new `case BulkFilterOpScanFile` in `bulkByFilterApplyOp` is selected by the per-instance-immutable `input.Op`; in-flight instances carry a different `Op` and keep their original command sequence, so replay does not diverge. ### Findings [MINOR] templates/layouts/base.html:200 — count-confirmation shows 0 for the largest libraries `data-...-book-count-value="{{if .HasCount}}{{.Count}}{{else}}0{{end}}"` falls back to 0 when the count is unavailable (`HasCount` false) — which per the Scale convention is exactly the large/unfiltered libraries where the "you are about to scan N files … uses your LLM budget" confirmation matters most. The most expensive case shows the least alarming number, weakening the secondary cost guard. Authorization/scoping/permission gates still fully protect the operation, so this is UX-quality, not a vulnerability. Suggest showing an indeterminate message ("all files in this library") when `HasCount` is false rather than "0 files". REVIEW VERDICT: 0 blocker, 0 major, 1 minor
Author
Owner

"[BLOCKER] internal/wfengine/bulk_by_filter_workflow.go:325, line 308 — undefined FanOut options variables
bulkByFilterScanFile() calls BoundedFanOutScanFileInput() passing scanFileFanOutOptions (line 325), and bulkByFilterLLMVision() passes llmSweepFanOutOptions (line 308). These variables are referenced but not defined in fanout.go, causing compilation failure. Fix: add variable definitions to fanout.go:

var scanFileFanOutOptions = gowf.SubWorkflowOptions{
Queue: QueueDefault,
RetryOptions: gowf.RetryOptions{MaxAttempts: 2},
}
var llmSweepFanOutOptions = gowf.SubWorkflowOptions{
Queue: QueueDefault,
RetryOptions: gowf.RetryOptions{MaxAttempts: 2},
}

Verify correct queue values against project queue constants.

[MAJOR] internal/wfengine/bulk_by_filter_workflow.go — go-workflows workflow command-sequence versioning gate
The PR adds a new BulkFilterOpScanFile operation to the switch in bulkByFilterApplyOp() (line 292). This branch emits a new CreateSubWorkflowInstance command. Existing in-flight bulk-by-filter instances will continue to replay their original op path (e.g., Op == 'enrich' stays in enrich branch) and will NOT emit the new command — so this change is replay-safe. However, this is a critical go-workflows pattern. Add an explicit comment: 'Command-sequence change verified safe: new BulkFilterOpScanFile branch is opt-in via input.Op; existing instances retain their original op value and replay unaffected.' This clarifies the safety for future reviewers and operators at 3am during an outage.

[MINOR] internal/books/routes_test.go:792–800 — test structure
The test 'registers POST /books/bulk/filter/scan-file' is functionally correct with proper 202 response check. No issues.

REVIEW VERDICT: 1 blocker, 1 major, 0 minor"

"[BLOCKER] internal/wfengine/bulk_by_filter_workflow.go:325, line 308 — undefined FanOut options variables `bulkByFilterScanFile()` calls `BoundedFanOutScanFileInput()` passing `scanFileFanOutOptions` (line 325), and `bulkByFilterLLMVision()` passes `llmSweepFanOutOptions` (line 308). These variables are referenced but not defined in fanout.go, causing compilation failure. Fix: add variable definitions to fanout.go: var scanFileFanOutOptions = gowf.SubWorkflowOptions{ Queue: QueueDefault, RetryOptions: gowf.RetryOptions{MaxAttempts: 2}, } var llmSweepFanOutOptions = gowf.SubWorkflowOptions{ Queue: QueueDefault, RetryOptions: gowf.RetryOptions{MaxAttempts: 2}, } Verify correct queue values against project queue constants. [MAJOR] internal/wfengine/bulk_by_filter_workflow.go — go-workflows workflow command-sequence versioning gate The PR adds a new `BulkFilterOpScanFile` operation to the switch in `bulkByFilterApplyOp()` (line 292). This branch emits a new `CreateSubWorkflowInstance` command. Existing in-flight bulk-by-filter instances will continue to replay their original op path (e.g., Op == 'enrich' stays in enrich branch) and will NOT emit the new command — so this change is replay-safe. However, this is a critical go-workflows pattern. Add an explicit comment: 'Command-sequence change verified safe: new BulkFilterOpScanFile branch is opt-in via input.Op; existing instances retain their original op value and replay unaffected.' This clarifies the safety for future reviewers and operators at 3am during an outage. [MINOR] internal/books/routes_test.go:792–800 — test structure The test 'registers POST /books/bulk/filter/scan-file' is functionally correct with proper 202 response check. No issues. REVIEW VERDICT: 1 blocker, 1 major, 0 minor"
Author
Owner

Security Review — PR #1234

Reviewed the diff (removes an inert main:has(.book-show){max-width:none} rule + corrects a stale CSS comment in static/css/main.css).

Verification performed:

  • Confirmed the removed rule is a true no-op — no max-width is set on main anywhere else in main.css, and max-width:none is the CSS initial value, so removal changes nothing rendered.
  • Confirmed max-width is a pure layout property — not contain, overflow, visibility, clip, or any containment/isolation control. No security-relevant affordance is dropped.
  • No CSP / inline-style surface touched (stylesheet file only; no inline styles introduced).
  • Comment fix is factually accurate: .reader-viewer carries max-width:700px; the previously-named .reader-column has no max-width (flex:1). The corrected reference points at the element that actually constrains width.
  • No secrets/PII, no auth/scoping, no injection/SSRF, no fan-out surface — cosmetic CSS-only change.

No findings.

REVIEW VERDICT: 0 blocker, 0 major, 0 minor

## Security Review — PR #1234 Reviewed the diff (removes an inert `main:has(.book-show){max-width:none}` rule + corrects a stale CSS comment in `static/css/main.css`). **Verification performed:** - Confirmed the removed rule is a true no-op — no `max-width` is set on `main` anywhere else in `main.css`, and `max-width:none` is the CSS initial value, so removal changes nothing rendered. - Confirmed `max-width` is a pure layout property — not `contain`, `overflow`, visibility, `clip`, or any containment/isolation control. No security-relevant affordance is dropped. - No CSP / inline-style surface touched (stylesheet file only; no inline styles introduced). - Comment fix is factually accurate: `.reader-viewer` carries `max-width:700px`; the previously-named `.reader-column` has no max-width (`flex:1`). The corrected reference points at the element that actually constrains width. - No secrets/PII, no auth/scoping, no injection/SSRF, no fan-out surface — cosmetic CSS-only change. No findings. REVIEW VERDICT: 0 blocker, 0 major, 0 minor
Author
Owner

Library scan-file count confirmation screenshot (library-scan-file-confirm)

library-scan-file-confirm

**Library scan-file count confirmation screenshot** (library-scan-file-confirm) ![library-scan-file-confirm](/attachments/a0a1d0da-a70d-46c9-877b-71022d725f47)
Author
Owner

Orchestrator note — the [BLOCKER] above is a false positive.

scanFileFanOutOptions and llmSweepFanOutOptions ARE defined, in sibling files of the same wfengine package (bulk_scan_file_workflow.go:19 and bulk_llm_workflow.go:54), and BoundedFanOutScanFileInput/BoundedFanOutLLMSweepInput in fanout.go:329/346. Same-package scope, so the code compiles — confirmed by CI being fully green (Go CI cannot pass with an undefined symbol). The reviewer expected them in fanout.go and did not grep the package. No compilation issue; nothing to fix here.

The [MAJOR] (add a replay-safety comment before case BulkFilterOpScanFile) and the security [MINOR] (count-confirmation shows 0 when count is unavailable) are valid and will be folded in.

**Orchestrator note — the [BLOCKER] above is a false positive.** `scanFileFanOutOptions` and `llmSweepFanOutOptions` ARE defined, in sibling files of the same `wfengine` package (`bulk_scan_file_workflow.go:19` and `bulk_llm_workflow.go:54`), and `BoundedFanOutScanFileInput`/`BoundedFanOutLLMSweepInput` in `fanout.go:329/346`. Same-package scope, so the code compiles — confirmed by CI being fully green (Go CI cannot pass with an undefined symbol). The reviewer expected them in `fanout.go` and did not grep the package. No compilation issue; nothing to fix here. The [MAJOR] (add a replay-safety comment before `case BulkFilterOpScanFile`) and the security [MINOR] (count-confirmation shows 0 when count is unavailable) are valid and will be folded in.
zombor changed title from feat(bulk): scan-file by-filter + library kebab menu with count confirmation (bookshelf-cs2zl.2) to feat(bulk): scan-file by-filter + library kebab menu with count confirmation [shot:library-scan-file-screenshot] (bookshelf-cs2zl.2) 2026-07-25 02:00:48 +00:00
fix(review): replay-safety comment, count-confirmation indeterminate message, better screenshot
All checks were successful
/ JS Unit Tests (pull_request) Successful in 1m4s
/ E2E API (pull_request) Successful in 1m55s
/ Test Race (pull_request) Successful in 3m39s
/ Lint (pull_request) Successful in 4m17s
/ Coverage (pull_request) Successful in 4m16s
/ Integration (pull_request) Successful in 4m49s
/ E2E Browser (pull_request) Successful in 4m33s
ac9ee68ad1
- Add replay-safety comment before BulkFilterOpScanFile case in
  bulkByFilterApplyOp explaining why no gowf.Version gate is needed
  (op type is immutable in workflow input; in-flight instances replay
  their original op and command sequence unchanged)

- Fix count-confirmation message to say "all files in this library"
  instead of "0 files" when the book count is unavailable (HasCount=false).
  Changes: base.html emits empty attribute (not "0"); bookCount Stimulus
  value type changed from Number to String so empty string is detectable;
  scanFiles JS uses falsy check to produce the indeterminate phrase.
  New Vitest case covers the no-count branch (null argument → empty attr).

- Improve library-scan-file e2e screenshot: add 300ms settle delay after
  menu opens (captures menu FULLY OPEN), add a second frame after dialog
  appears (300ms settle), and upload the open-menu shot as an additional
  attachment so both states are represented in the PR.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Library scan-file count confirmation screenshot (library-scan-file-menu-open)

library-scan-file-menu-open

**Library scan-file count confirmation screenshot** (library-scan-file-menu-open) ![library-scan-file-menu-open](/attachments/cca90507-3639-4980-b66b-2d25f9e47279)

Library scan-file count confirmation screenshot (library-scan-file-confirm)

library-scan-file-confirm

**Library scan-file count confirmation screenshot** (library-scan-file-confirm) ![library-scan-file-confirm](/attachments/e2499515-9f64-4b8c-9f16-b0e7a9d9b0ad)
Author
Owner

Updated screenshots (supersede attachment a0a1d0da from comment #15306)

The earlier screenshot caught the modal mid-fade transition. These replacements add a 300ms CSS-settle delay after menu open and after dialog render so both states are fully visible:

1. Kebab menu FULLY OPEN — showing "Scan All Files for Metadata":
library-scan-file-menu-open

2. Count confirmation dialog FULLY rendered:
library-scan-file-confirm

**Updated screenshots** (supersede attachment a0a1d0da from comment #15306) The earlier screenshot caught the modal mid-fade transition. These replacements add a 300ms CSS-settle delay after menu open and after dialog render so both states are fully visible: **1. Kebab menu FULLY OPEN** — showing "Scan All Files for Metadata": ![library-scan-file-menu-open](/attachments/cca90507-3639-4980-b66b-2d25f9e47279) **2. Count confirmation dialog FULLY rendered:** ![library-scan-file-confirm](/attachments/e2499515-9f64-4b8c-9f16-b0e7a9d9b0ad)
zombor force-pushed bd-bookshelf-cs2zl.2 from ac9ee68ad1
All checks were successful
/ JS Unit Tests (pull_request) Successful in 1m4s
/ E2E API (pull_request) Successful in 1m55s
/ Test Race (pull_request) Successful in 3m39s
/ Lint (pull_request) Successful in 4m17s
/ Coverage (pull_request) Successful in 4m16s
/ Integration (pull_request) Successful in 4m49s
/ E2E Browser (pull_request) Successful in 4m33s
to f4b7750def
All checks were successful
/ Test Race (pull_request) Successful in 3m47s
/ E2E API (pull_request) Successful in 2m46s
/ JS Unit Tests (pull_request) Successful in 1m53s
/ Coverage (pull_request) Successful in 4m17s
/ Lint (pull_request) Successful in 6m21s
/ Integration (pull_request) Successful in 5m21s
/ E2E Browser (pull_request) Successful in 5m52s
2026-07-26 02:50:48 +00:00
Compare

Library scan-file count confirmation screenshot (library-scan-file-menu-open)

library-scan-file-menu-open

**Library scan-file count confirmation screenshot** (library-scan-file-menu-open) ![library-scan-file-menu-open](/attachments/d44f0984-77c7-4b28-9feb-b8c427a23f18)

Library scan-file count confirmation screenshot (library-scan-file-confirm)

library-scan-file-confirm

**Library scan-file count confirmation screenshot** (library-scan-file-confirm) ![library-scan-file-confirm](/attachments/f1e70435-04c8-488e-bff1-79d0386447d1)
zombor merged commit eeee5c232a into main 2026-07-26 03:00:54 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
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
zombor/pergamum!1239
No description provided.