Manifest
Stable file key, caption, optional location, and crop intent make each item versionable.
Interactive demo · zero external writes
Replay validation, preparation, pacing, and resumable state without opening Chrome, contacting Instagram, or sharing content.
60-second deterministic replay
The versioned fixture goes through the same batch contract as live mode, but a local adapter replaces every browser action.
Problem
A long media batch must survive interruption, respect pacing, prove that captions landed, and stop when the platform signals a restriction. Retrying blindly can duplicate posts or compound an account block.
The design moves sequencing and resume state into a pure core. Volatile Instagram selectors live behind one adapter, and the public demo cannot even load that adapter.
Architecture
Dependency injection makes the safe path visible: demo and live share orchestration semantics, not capabilities.
Stable file key, caption, optional location, and crop intent make each item versionable.
Owns ordering, stable-key deduplication, spacing, failure stops, events, and completed keys.
The mock emits deterministic steps. The live adapter alone imports Playwright and CDP.
Demo reports and live state record item outcomes so an interrupted run can resume.
Workflow
Each transition has a concrete success signal; a failure stops the batch instead of allowing unsafe continuation.
The Lexical field is read back after keystrokes and a mismatch raises CAPTION_NOT_SET. Optional location matching is best-effort and records locationSet rather than blocking the Reel.
Restriction copy raises ACTION_BLOCKED; the batch terminates and tells the operator not to retry that day.
Completed filenames are skipped before adapter execution, preventing accidental repeats after interruption.
Real, linked code
These excerpts come from the public repository. Links open the complete implementation.
const got = String(await cap.innerText())
.replace(/\s+/g, ' ').trim();
const probe = caption.replace(/\s+/g, ' ')
.trim().slice(0, 25);
if (probe && (!got || !got.includes(probe))) {
throw new Error('CAPTION_NOT_SET');
}The tool refuses to share when visible editor state does not match the requested caption.
if (doneSet.has(key)) {
results.push({ key, status: 'skipped', steps: [] });
onEvent({ type: 'skipped', key, done: [...doneSet] });
continue;
}The core checks durable completion before the side-effect adapter can run.
if (/Action Blocked|Try Again Later|
restrict certain activity/i.test(body)) {
throw new Error('ACTION_BLOCKED');
}
if (/has been shared/i.test(body)) {
posted = true;
break;
}Platform feedback is treated as a hard stop, while a positive completion signal advances state.
Security boundary
The offline route does not rely on flags inside a loaded live client; it never imports that client.
npm run demonpm run batchQuality and limitations
Run locally
The fixture and output directory are versioned and configurable.
npm install
npm run demo
npm test
npm run test:site
# equivalent explicit CLI
node src/demo.mjs \
--fixture manifest.example.json \
--output site