IG/batch View source ↗

Interactive demo · zero external writes

A Reel batch,
rehearsed safely.

Replay validation, preparation, pacing, and resumable state without opening Chrome, contacting Instagram, or sharing content.

60-second deterministic replay

Follow every fixture item.

The versioned fixture goes through the same batch contract as live mode, but a local adapter replaces every browser action.

Problem

Browser automation is easy to start and hard to make safe.

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

Pure batch core. Explicit side-effect boundary.

Dependency injection makes the safe path visible: demo and live share orchestration semantics, not capabilities.

Manifest

Stable file key, caption, optional location, and crop intent make each item versionable.

Batch core

Owns ordering, stable-key deduplication, spacing, failure stops, events, and completed keys.

Adapters

The mock emits deterministic steps. The live adapter alone imports Playwright and CDP.

Evidence

Demo reports and live state record item outcomes so an interrupted run can resume.

Workflow

Six guarded transitions before the next Reel.

Each transition has a concrete success signal; a failure stops the batch instead of allowing unsafe continuation.

  1. 01ValidateResolve media and stable key.
  2. 02AttachInject the file through CDP.
  3. 03PrepareSet crop, caption, and location.
  4. 04VerifyRead the caption field back.
  5. 05ShareLive adapter only; wait for completion.
  6. 06PersistWrite completed key, then pace.
Caption + location evidence

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.

Action-block stop

Restriction copy raises ACTION_BLOCKED; the batch terminates and tells the operator not to retry that day.

Resumable by key

Completed filenames are skipped before adapter execution, preventing accidental repeats after interruption.

Real, linked code

Safety is executable, not aspirational.

These excerpts come from the public repository. Links open the complete implementation.

CAPTION READBACKpost.mjs ↗
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.

IDEMPOTENT RESUMEbatch-core.mjs ↗
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.

ACTION-BLOCK GUARDpost.mjs ↗
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

Demo and live mode have different dependency graphs.

The offline route does not rely on flags inside a loaded live client; it never imports that client.

DEMO

npm run demo

  • Synthetic local fixture
  • Mock adapter
  • Static JSON report
  • No Playwright, CDP, login, or network
LIVE

npm run batch

  • Operator-owned media
  • Attached Chrome profile
  • Immediate Instagram sharing
  • Explicit, consequential command

Quality and limitations

What is proven — and what remains volatile.

0demo external writes
5offline core tests
3synthetic fixture items
1explicit live boundary

Run locally

Replay without credentials.

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