···11+# Per-source listing progress
22+33+## Context
44+55+When tracks are processed, there are two phases: **listing** (discovering tracks from each source) and **metadata** (fetching tags/stats per track). Currently only the metadata phase reports progress. The listing phase can be slow (e.g. enumerating an S3 bucket) but appears as a black box — the UI has no visibility into it.
66+77+The goal is to show per-source progress during listing, so the user sees something like "Listing sources (2/3)..." before the metadata phase kicks in.
88+99+## Architecture challenge
1010+1111+The call chain crosses three worker boundaries:
1212+1313+```
1414+process-tracks worker →(RPC)→ input configurator worker →(RPC)→ per-scheme workers
1515+```
1616+1717+- `process-tracks/worker.js` calls `input.list(cachedTracks)` as one opaque RPC call
1818+- `configurator/input/worker.js` receives it, fans out to per-scheme workers via `Promise.all`
1919+- Each scheme worker (s3, https, opensubsonic) does the actual listing
2020+2121+The `announce`/`listen` mechanism only communicates between a worker and **its owning element**. So announcements from the input configurator worker go to the `dc-input` element, not to the process-tracks element or worker. This means progress must be surfaced through the element layer.
2222+2323+## Plan
2424+2525+### 1. Add listing progress signal to input configurator worker
2626+2727+**File:** `src/components/configurator/input/worker.js`
2828+2929+- Import `signal`, `effect`, `announce`
3030+- Add module-level signal: `const $listingProgress = signal({ processed: 0, total: 0 })`
3131+- In the `list()` function, count groups and update `$listingProgress` as each source completes:
3232+3333+```js
3434+export async function list({ data, ports }) {
3535+ const groups = await groupConsult({ data, ports });
3636+ const entries = Object.values(groups);
3737+3838+ $listingProgress.value = { processed: 0, total: entries.length };
3939+ let processed = 0;
4040+4141+ const promises = entries.map(async ({ available, scheme, tracks }) => {
4242+ if (!available) { ... }
4343+ const result = await input.list(tracks);
4444+ processed++;
4545+ $listingProgress.value = { processed, total: entries.length };
4646+ return result;
4747+ });
4848+4949+ const nested = await Promise.all(promises);
5050+ return nested.flat(1);
5151+}
5252+```
5353+5454+- In `ostiary()`, announce the signal and expose it via RPC:
5555+5656+```js
5757+ostiary((context) => {
5858+ rpc(context, { ..., listingProgress: $listingProgress.get });
5959+ effect(() => announce("listingProgress", $listingProgress.value, context));
6060+});
6161+```
6262+6363+### 2. Expose listing progress from input configurator element
6464+6565+**File:** `src/components/configurator/input/element.js`
6666+6767+- Import `signal` from `@common/signal.js` and `listen` from `@common/worker.js`
6868+- Add a `#listingProgress` signal and a public `listingProgress` getter
6969+- Add `connectedCallback()` to set up `listen("listingProgress", ...)` on `this.workerLink()`
7070+7171+### 3. Proxy listing progress through the input orchestrator
7272+7373+**File:** `src/components/orchestrator/input/element.js`
7474+7575+- Add a `listingProgress` getter that delegates to `this.input.listingProgress()`
7676+7777+### 4. Add a phase to process-tracks progress
7878+7979+**File:** `src/components/orchestrator/process-tracks/types.d.ts`
8080+8181+- Extend the `Progress` type with a `phase` field:
8282+8383+```ts
8484+export type Progress = {
8585+ phase: "listing" | "metadata";
8686+ processed: number;
8787+ total: number;
8888+};
8989+```
9090+9191+**File:** `src/components/orchestrator/process-tracks/element.js`
9292+9393+- The element already queries `input-selector` (the `do-input` element)
9494+- Add an `effect()` that watches `this.input.listingProgress()` while `isProcessing` is true, and maps it into `#progress` with `phase: "listing"`
9595+- When the worker's own metadata progress arrives (via the existing `listen("progress", ...)`), set it with `phase: "metadata"`
9696+9797+**File:** `src/components/orchestrator/process-tracks/worker.js`
9898+9999+- Update `$progress` initial value to include `phase: "metadata"` (the worker only knows about metadata)
100100+101101+### 5. Update the UI to show the phase
102102+103103+**File:** `src/themes/webamp/configurators/input/element.js`
104104+105105+- In `#renderProcessingProgress()`, read `phase` from the progress object
106106+- Show "Listing sources (2/3)..." during listing phase
107107+- Show "Gathering metadata (5/10)..." during metadata phase (current behavior)
108108+109109+## Files changed (6)
110110+111111+| File | Change | Difficulty |
112112+|------|--------|-----------|
113113+| `src/components/configurator/input/worker.js` | Add `$listingProgress` signal, update `list()`, announce in `ostiary` | Low-medium |
114114+| `src/components/configurator/input/element.js` | Add signal, `connectedCallback`, `listen`, expose getter | Low |
115115+| `src/components/orchestrator/input/element.js` | Proxy `listingProgress` getter | Trivial |
116116+| `src/components/orchestrator/process-tracks/element.js` | Watch input's listing progress, merge into phased progress | Medium |
117117+| `src/components/orchestrator/process-tracks/worker.js` | Add `phase` to progress signal | Trivial |
118118+| `src/components/orchestrator/process-tracks/types.d.ts` | Add `phase` to `Progress` type | Trivial |
119119+| `src/themes/webamp/configurators/input/element.js` | Render phase-aware progress text | Low |
120120+121121+**Overall difficulty: Medium.** Follows existing patterns (`announce`/`listen`, signals, proxied getters) throughout. The trickiest part is step 4 — having the process-tracks element watch the input element's listing progress and merge it with the worker's metadata progress into a single unified signal.
122122+123123+## Verification
124124+125125+- Add an S3 source with enough files to make listing take a visible amount of time
126126+- Open the Overview tab in the input configurator
127127+- Observe "Listing sources (X/Y)..." during listing, transitioning to "Gathering metadata (X/Y)..." during metadata extraction
128128+- Confirm the progress bar advances during both phases