---
convoy: scanner-desktop-layout
brief_number: 5
depends_on: []
recommended_model: composer-2.5-fast
model_tier: fast
files:
  - lib/scanner-batch-identify.js
  - test/lib/scanner-batch-identify.test.js
cross_brief_commitments:
  - brief: 6
    description: |
      Brief 6 calls `runSequentialGalleryIdentify(files, identifyFn, options)`
      from the Batch Scan multi-file picker. On per-file failure Brief 6 enqueues
      a queue row with `identifyFailed: true` and `identifyError` message; cancel
      via `cancelRef.current = true` keeps completed rows (IA-locked).
---

# Brief 5: Sequential batch identify helper

## Goal (1 sentence)

Add a small library helper that runs `identifyFromGalleryFile` sequentially over multiple files with progress, cancel, and per-file failure continuation.

## Files in scope (do not edit anything else)

- `lib/scanner-batch-identify.js`
- `test/lib/scanner-batch-identify.test.js`

## Conventions to follow

- **No new API routes** — caller passes `identifyFn` (Brief 6 binds
  `identification.identifyFromGalleryFile`).
- **Sequential only** — one `await identifyFn(file)` at a time; no `Promise.all`.
- Respect identify rate limits implicitly (sequential pacing).
- Pure JS module — no React.

## Implementation shape

```js
/**
 * @typedef {{ current: number, total: number, file: File }} BatchProgress
 */

export async function runSequentialGalleryIdentify(files, identifyFn, options = {}) {
  const {
    onProgress,
    onFileSuccess,
    onFileError,
    cancelRef = { current: false },
  } = options;

  const list = Array.from(files || []);
  const total = list.length;
  const results = [];

  for (let i = 0; i < list.length; i++) {
    if (cancelRef.current) break;
    const file = list[i];
    onProgress?.({ current: i + 1, total, file });

    try {
      await identifyFn(file);
      results.push({ file, ok: true });
      onFileSuccess?.({ file, index: i });
    } catch (error) {
      results.push({ file, ok: false, error });
      onFileError?.({ file, index: i, error });
      // continue — IA forbids stopping the batch
    }
  }

  return { results, cancelled: cancelRef.current };
}
```

`identifyFn` may not throw today — helper still catches for forward-compat.
If `identifyFromGalleryFile` only reports via console, Brief 6 may wrap it to
throw on hard failures.

## Acceptance criteria

- [ ] Processes files one-at-a-time in order
- [ ] `onProgress` fires before each file with `{ current, total, file }`
- [ ] `cancelRef.current = true` stops remaining files but returns partial `results`
- [ ] Per-file errors invoke `onFileError` and continue loop
- [ ] tests cover success path, mid-batch cancel, and error continuation
- [ ] no scope expansion (do not edit files outside `files:` above)

## Rationale (≤3 sentences)

Batch Scan (C1) needs orchestration without touching `use-scanner-identification.js` identify logic. A 60-line pure helper is testable and keeps the page brief focused on UI wiring. Sequential execution avoids Gemini rate-limit storms explicitly forbidden in scope.
