Batch signing: multi-file queue in UI (AutoFirma multi-file/folder mode) #42
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
EduarD3V/AutoFirme#42
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Goal
AutoFirma signs many files in one pass. Add a batch queue to the web app.
Scope (OWNERSHIP: this issue owns the queue store + App-level list UI)
Ownership boundaries
Do NOT touch SignPanel options (visible-signature issue owns those) or certstore (its own issue). Interface: queue calls existing sign functions per file.
Gates
pnpm test green (queue store + ZIP integration tests), build clean.
bullet: coder launched on branch
issue/42-batch-signing-multi-file-queue(modelnan/deepseek-v4-flash).Claimed — branch
issue/42-batch-signing-multi-file-queue. Doing: the queue store, UI, ZIP path and progress already exist onbullet-developfrom #9; the one requirement of this issue with no implementation anywhere is cancel (0 grep hits). Implementing cancel insrc/lib/batch.ts+BatchQueue.svelte+ i18n + tests, and re-verifying the rest of the scope.Plan — #42 Batch signing: multi-file queue in UI (owner: queue store + App-level list UI)
Restated goal
The issue asks for the AutoFirma "multi-file/folder" flow in the web app: drop N documents
(PDF/XML/binary), per-file status (
pending/signing/signed/error), one certificate for thewhole batch, sequential signing, progress UI, cancel, and a "download all" ZIP.
Finding before planning (verified, not assumed): most of that already exists on this branch's
base (
bullet-develop@4c9f6c31), landed by #9:src/lib/batch.ts(createBatchQueue,DocumentStatus,run)App.svelte:70,371,387(fileslist,FileDropappends,FileCard onremove)run()awaits one document at a time;App.sveltemakeSigner(cert, format)BatchQueue.svelte:62(Firmados: n/N)verify:zippathbuildZip+package.json scripts["verify:zip"]+scripts/verify-batch-zip.mjsgrep -rn "cancel|abort" src/lib/{batch.ts,i18n.ts,components/BatchQueue.svelte,App.svelte}→ 0 hitsSo "done" for #42 = cancel works end to end (store + UI + i18n + tests), with the rest of the
scope re-verified by the existing suite rather than re-implemented. Re-implementing a working queue
would be churn against a reviewed design.
Scope
Files I will change (5 + plan/report outside the repo):
src/lib/batch.ts—phaseunion gains'cancelled';BatchQueue.cancel(); the run loop stopsbefore starting the next document once cancelled and publishes
phase: 'cancelled'.src/lib/components/BatchQueue.svelte— a Cancel button, shown only whilephase === 'running'.src/lib/i18n.ts—queueCancel+queueCancelled(ES + EN;Record<Lang, Copy>forces both).src/lib/batch.test.ts— store-level cancel test (gated engine) + UI test (button appears whilerunning, click cancels, remaining documents stay
pendingand the ZIP still holds what signed).README.md— one bullet in the existing "Batch signing queue — #9" section.Files I will not touch (ownership boundaries from the issue + out of scope):
src/lib/sign.ts— SignPanel options (visible-signature issue owns them).src/lib/certstore.ts— its own issue.src/lib/cms.ts,xades.ts,pades.ts,zip.ts,verify/**— engines/verification, no changeneeded: the queue calls them through
BatchSigner.src/lib/App.svelte— no change needed; it already rendersBatchQueueand owns the file list,certificate and format. Adding cancel needs nothing from it.
component s ui/*,App.test.ts— untouched.Ordered tasks
Each task: one verifying command, commit when green. Commits carry
Refs #42.pnpm test(record pass count) andpnpm check.batch.ts:phase: 'idle' | 'running' | 'done' | 'cancelled',cancel(): voidonBatchQueue,let cancelledflag cleared at the top ofrun, checked at thetop of each loop iteration and before the final phase write.
Verify:
pnpm vitest run src/lib/batch.test.ts -t 'cancel'.batch.test.ts: 3 documents, signer gated on a promise; callcancel()whilea.txtis in flight; assertsigned === ['a.txt'],phase === 'cancelled',b.txt/c.txtstill
pendingwithresult: NOT_STARTED; then a furtherrun()resumes atb.txtand endsdone. Verify: same command.running, wire
queue.cancel().Verify:
pnpm vitest run src/lib/batch.test.ts(new UI case: button present while running andgone after; clicking it leaves the batch cancelled, i.e. the progress line stops at the cancelled
count and the run button is enabled again).
## Batch signing queue — #9.Verify:
pnpm test && pnpm check.pnpm test,pnpm check,pnpm build,pnpm verify:zip,git diff --check.Open questions (assumptions I am proceeding on)
abort a digest/sign mid-call, and throwing away a finished signature loses work; so the document
being signed when cancel lands finishes and stays
signed, and cancel prevents the next onefrom starting. Documented in the README bullet.
phasevalue vs a boolean flag? Assumption: widen the union with'cancelled'. It iswhat the UI needs to distinguish "stopped by the user" from "never started" (
idle) and"finished" (
done), andrun()already skipssigneddocuments, so a re-run after a cancelresumes. Only three existing assertions touch
phase(batch.test.ts:135,146,256) and nonebreaks.
"sequential signing (WebCrypto is fine sequential)"; per-document granularity is the contract.
Risk
DocumentStatus/phasebreaks a consumer. Checked:phaseis read inBatchQueue.svelte:62,81,85and the three test assertions above;DocumentStatusis unchanged.Fallback if something unexpected breaks: keep
phaseas-is and add acancelled: booleanon thestate instead (same UI effect, no union change).
a microtask to release). Fallback: keep the deterministic store-level cancel test as the real
proof and reduce the UI test to "the Cancel button exists while running and triggers
queue.cancel()", spying on the store rather than racing it.pnpm testruntime — the suite already shells out toopenssland builds real CAdES fixtures(~1 min). No new external tool, so no new flake source;
verify:zipneedsunzip/sha256sum,both present (used by #9's gate).
outcome is a report saying #42 is already satisfied by #9 and a no-op diff — but the greps above
say cancel is genuinely missing, so I proceed with tasks 2–5.
Refs #42
Issue #42 — Batch signing: multi-file queue in UI — coder report
Outcome: DONE. Branch
issue/42-batch-signing-multi-file-queue, 4 commits on top of4c9f6c31(bullet-develop HEAD), tree clean, nothing pushed.Headline finding
Almost the whole scope of #42 already existed on the base: #9 (merged before this branch point)
shipped
src/lib/batch.ts(queue state machine, per-document status, sequential run, manifest),BatchQueue.svelte(progress UI + ZIP download), theApp.sveltemulti-file list with per-fileremove, and
scripts/verify-batch-zip.mjsbehindpnpm verify:zip. Re-implementing that wouldhave been churn against a reviewed design.
The one requirement in the issue body with no implementation anywhere was cancel
(
grep -rn "cancel\|abort" src/lib/{batch.ts,i18n.ts,components/BatchQueue.svelte,App.svelte}→0 hits before this change). That is what this branch adds. Every other bullet of the issue is
re-verified by the existing suite, listed under "scope audit" below.
Commits
54bed2d9web: stop a batch run at the next document when cancelled—phase: 'cancelled',BatchQueue.cancel(), loop guard, store test7ed3271bweb: cancel the batch from the queue UI— primary button becomes Cancel while running, phase label, i18n ES/EN, UI testb2e39737test: read the xmlsec1 1.3.12 'Verification status:' verdict— see "out-of-scope note"833ba9ebdocs: the queue is cancellable— one README bulletGates (all exit 0, run on
833ba9eb)pnpm testwas red on the untouched base (5–8 failures inxades.test.ts+ one XAdES case inApp.test.ts): this box'sxmlsec1 1.3.12 (openssl)printsVerification status: OK/Verification status: FAILED, a third verdict shape the helper did not know, sosrc/test/fixtures.ts:164threwxmlsec1 printed no verdict (exit 0)before any assertion ran.One regex line now accepts it; no assertion changed. Proven: the full suite is green after it, and
the tampered-document case now genuinely asserts
FAILinstead of erroring.Behavioural evidence for the new feature (not just unit assertions):
cancel()called whilea.txtis inflight:
signed === ['a.txt'],phase === 'cancelled', the two behind it staypending/NOT_STARTED,a.txt's output is verified byopenssl cms -verify, and the nextrun()resumes at
b.txtand endsdone.BatchQueue.sveltemounted in jsdom with the real CAdES engine: the run buttonbecomes
Cancelarwhile running, clicking it leavesLote cancelado: 1/3, the button reverts toFirmar, and the queue state is['signed','pending','pending'].pnpm verify:zipcross-checks the ZIP path this issue names, unchanged and green.the change is one button and one store flag, and both paths are exercised above.
Scope audit — every issue bullet, against the tree
App.sveltefileslist,FileDrop(appends),FileCard onremoveApp.test.tssign-workflow suitebatch.tsDocumentStatus,patch()batch.test.ts(all 7)verify:zippathbuildZip+scripts/verify-batch-zip.mjspnpm verify:zip, ZIP testsBatchQueue.svelteprogressLabel+n/N,aria-liveawaitper document inrun()running === 1App.svelte makeSigner(cert, format)App.test.tsFiles changed
src/lib/batch.ts(+25/-1),src/lib/components/BatchQueue.svelte(+30/-15),src/lib/i18n.ts(+6),src/lib/batch.test.ts(+100),README.md(+3),src/test/fixtures.ts(+4/-4, out-of-scope note below).Not touched, per the issue's ownership boundaries:
src/lib/sign.ts(SignPanel options),src/lib/certstore.ts, the engines (cms.ts,xades.ts,pades.ts),zip.ts,verify/**, andApp.svelte(it already rendersBatchQueueand needed nothing for cancel).What a human must decide
only cancel. Either merge it as the small delta it is, or close #42 as "already satisfied by #9
plus
54bed2d9/7ed3271b".phase: 'cancelled'is a new store state — any future consumer ofBatchState.phasemusthandle it (
BatchQueue.svelteand the tests do).DocumentStatusis unchanged, so the manifestand ZIP formats did not move.
discarding a finished signature loses work; the queue stops before starting the next document.
If the desired semantics are "cancel discards the in-flight document too", that is a two-line
change plus a new message in the manifest (
ERROR_PREvsNOT_STARTEDfor an aborted document).App.sveltethe queue isrebuilt from the file list on any change (
$effect(() => queue.set(files, format))), sodropping an extra file — or removing one — after a run resets every result and the ZIP contents
to the new list, even though the signatures were kept in the browser store by
keepSignatures.Fixing it needs a queue
add()/remove()API with stable per-document ids (today ids arepositions) and touches the #9 integration test "abandons a run whose document list was
replaced". Out of this issue's budget; worth its own issue.
src/test/fixtures.tsis outside the stated scope — say the word if you would rather thesuite stay red on this box (xmlsec1 version) and have that fix land in the issue that owns the
XML oracle (#7/#39).
Refs #42
bullet: coder session died — relaunched from committed state (branch
issue/42-batch-signing-multi-file-queue).DONE — branch
issue/42-batch-signing-multi-file-queue, 5 commits on4c9f6c31, tree clean, nothing pushed.Commits:
54bed2d9(store: stop at the next document when cancelled) ·7ed3271b(UI: cancel button + phase label + i18n) ·b2e39737(xmlsec1 1.3.12 verdict) ·833ba9eb(README) ·5b5e294b(test headroom under load).What the issue still lacked was cancel: the queue store, per-file status, sequential run, progress UI, ZIP +
verify:zipand the App-level multi-file list all shipped with #9 and are re-verified, not re-implemented.Gates on
5b5e294b, all exit 0:pnpm test→ 142/142 (16 files;src/lib/batch.test.ts7/7)pnpm check→ 1324 files, 0 errors, 0 warningspnpm build→ adapter-static wrotebuild/pnpm verify:zip→unzip -tOK + sha256sum matches the manifestgit diff --check→ cleanReal-browser proof (production build on
:4173, generated PKCS#12, 5 documents, CPU-throttled so the run lasts): queue'sFirmarbecomesCancelarwhile running; clicking it givesLote cancelado: 1/5with the first documentFirmadaand the other fourPendiente; re-running resumes toFirmados: 5/5; the downloaded ZIP holds the five.csig+manifest.json, and all five verify withopenssl cms -verify -binary.Full report:
.bullet/meta/issue-42.report.md. Open questions for the human are on it (thephase: 'cancelled'state, cancel keeping the in-flight signature, the pre-existing "list change resets results" limitation, and whether the5b5e294btimeout headroom is wanted).Refs #42
bullet: coder session died — relaunched from committed state (branch
issue/42-batch-signing-multi-file-queue).bullet: implementation finished (DONE: — branch
issue/42-batch-signing-multi-file-queue, 5 commits on4c9f6c31, tree clean, nothing pushed.) — moving to review.bullet: reviewer launched (model
9router/reviewer, independent of coder).REVIEW: FIXED 1 — commit
f7eed73d: findings: plan-fidelity gap — plan promised UI cancel test asserts the ZIP after cancel holds only what signed, original test at5b5e294bstopped at statuses; fixed inf7eed73d; UNFIXED: nonebullet: fixer launched (round 1/2).
FIXED: applied 1 —
f7eed73d: UI cancel test now asserts the ZIP after a cancel holds exactly a.txt.csig + manifest.json (plan line 34 fidelity); UNFIXED: noneRe-verified at head
f7eed73d(fixer round 1): pnpm vitest run src/lib/batch.test.ts 7/7; pnpm test 16 files 142/142; pnpm check 1324 files 0 errors 0 warnings; pnpm build clean; pnpm verify:zip 3 entries unzip+sha256sum OK; git diff --check clean. Mutation check: disabling the cancel guard (if (cancelled) break;) makes the batch tests fail 2 — the new assertion genuinely pins the behaviour, not a tautology. No code change needed; no new commits.Refs #42
bullet: fixer round done — fresh reviewer re-checking.
REVIEW: CLEAN — commit
f7eed73d: findings none; UNFIXED: nonebullet: review CLEAN — ready to publish.
bullet: PR #56 open →
bullet-develop(auto-merge follows).bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into
bullet-develop; PR auto-merges next tick.bullet: merge conflicts with
bullet-develop— conflict-solver agent owns the worktree until it pushes.bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into
bullet-develop; PR auto-merges next tick.bullet: merge conflicts with
bullet-develop— conflict-solver agent owns the worktree until it pushes.bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into
bullet-develop; PR auto-merges next tick.bullet: merge conflicts with
bullet-develop— conflict-solver agent owns the worktree until it pushes.bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into
bullet-develop; PR auto-merges next tick.bullet: merge conflicts with
bullet-develop— conflict-solver agent owns the worktree until it pushes.bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into
bullet-develop; PR auto-merges next tick.bullet: merge conflicts with
bullet-develop— conflict-solver agent owns the worktree until it pushes.bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into
bullet-develop; PR auto-merges next tick.bullet: merge conflicts with
bullet-develop— conflict-solver agent owns the worktree until it pushes.bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into
bullet-develop; PR auto-merges next tick.bullet: merge conflicts with
bullet-develop— conflict-solver agent owns the worktree until it pushes.bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into
bullet-develop; PR auto-merges next tick.Claimed — branch
bullet-develop. Doing: reconcile trunk (origin/main) into bullet-develop for the rolling PR.bullet: merged into
bullet-develop— integration gates green.bullet: PR #56 closed — work already merged into
bullet-develop.