{"_id":"@emergence-engineering/prosemirror-block-runner","_rev":"2-12b944a507250e5f8c4259674fa89ecf","name":"@emergence-engineering/prosemirror-block-runner","dist-tags":{"latest":"0.2.0"},"versions":{"0.1.0":{"name":"@emergence-engineering/prosemirror-block-runner","version":"0.1.0","keywords":["ProseMirror","block-runner","task-queue","processor","decorations"],"author":{"name":"Emergence Engineering"},"license":"MIT","_id":"@emergence-engineering/prosemirror-block-runner@0.1.0","maintainers":[{"name":"matejcsok","email":"istvan.matejcsok@emergence-engineering.com"},{"name":"viktorvacziee","email":"viktor.vaczi@emergence-engineering.com"},{"name":"tg44","email":"torcsvari.gergo@gmail.com"},{"name":"cskata","email":"katalin.csillag@emergence-engineering.com"}],"homepage":"https://github.com/emergence-engineering/emergence-tools/tree/main/packages/prosemirror-block-runner#readme","bugs":{"url":"https://github.com/emergence-engineering/emergence-tools/issues"},"dist":{"shasum":"54a0be4f127307e4be1d563d374ba46071985b61","tarball":"https://registry.npmjs.org/@emergence-engineering/prosemirror-block-runner/-/prosemirror-block-runner-0.1.0.tgz","fileCount":62,"integrity":"sha512-MP3b1e3LRBNl5iUY9Qs92rULGtQAQe0xvHpjvUkh9NtFIZbrtTX2Dj6xEdT0f7HEVDdMH/DSYaYrVAdQPoj7KQ==","signatures":[{"sig":"MEQCICO+L8VMd4W0u8vAGKDyY5lNATnA0qknBgPIwpyo6W9zAiBRLyd+gA8ja5TMH6lhEa+gYb6BkkaasHAHYv9vxw+3Wg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":92455},"main":"dist/index.js","_from":"file:emergence-engineering-prosemirror-block-runner-0.1.0.tgz","types":"dist/src/index.d.ts","module":"dist/index.es.js","engines":{"node":">=16.0.0"},"scripts":{"dev":"rollup -c -w --bundleConfigAsCjs","e2e":"playwright test","lint":"tsc --noEmit","test":"pnpm test:unit && pnpm test:e2e","build":"rollup -c --bundleConfigAsCjs","format":"eslint src --ext .ts --fix","prebuild":"rimraf dist","test:e2e":"playwright test","test:unit":"jest","e2e:server":"vite --config e2e/vite.config.ts"},"_npmUser":{"name":"tg44","email":"torcsvari.gergo@gmail.com"},"_resolved":"/private/var/folders/vd/vvmzq4kn6vq7qwcgg92p1dnh0000gn/T/266d27e1aec8275add27abf83cf36f58/emergence-engineering-prosemirror-block-runner-0.1.0.tgz","_integrity":"sha512-MP3b1e3LRBNl5iUY9Qs92rULGtQAQe0xvHpjvUkh9NtFIZbrtTX2Dj6xEdT0f7HEVDdMH/DSYaYrVAdQPoj7KQ==","repository":{"url":"git+https://github.com/emergence-engineering/emergence-tools.git","type":"git","directory":"packages/prosemirror-block-runner"},"_npmVersion":"11.6.4","description":"Generic task queue processor for ProseMirror — process document blocks through configurable parallel workers with state management, retry logic, and visual feedback decorations","directories":{},"_nodeVersion":"24.11.1","dependencies":{"@emergence-engineering/prosemirror-text-map":"0.1.5"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.5.0","vite":"^5.0.0","eslint":"^8.49.0","rimraf":"^5.0.0","rollup":"^3.29.2","ts-jest":"^29.1.0","prettier":"^2.8.1","typescript":"^5.2.2","@types/jest":"^29.5.1","@playwright/test":"^1.41.0","prosemirror-view":"^1.31.4","prosemirror-model":"^1.19.3","prosemirror-state":"^1.4.3","eslint-plugin-react":"^7.33.2","eslint-plugin-import":"^2.28.1","prosemirror-transform":"^1.7.3","eslint-config-prettier":"^8.5.0","eslint-plugin-jsx-a11y":"^6.7.1","eslint-plugin-prettier":"^4.2.1","prosemirror-schema-basic":"^1.2.2","prosemirror-test-builder":"^1.1.1","@typescript-eslint/parser":"^6.7.2","prosemirror-example-setup":"^1.2.2","rollup-plugin-typescript2":"^0.35.0","rollup-plugin-minification":"^0.2.0","eslint-config-airbnb-typescript":"^17.1.0","@typescript-eslint/eslint-plugin":"^6.7.2"},"peerDependencies":{"prosemirror-view":"^1.31.4","prosemirror-model":"^1.19.3","prosemirror-state":"^1.4.3","prosemirror-transform":"^1.7.3"},"_npmOperationalInternal":{"tmp":"tmp/prosemirror-block-runner_0.1.0_1773044147837_0.9508099507822594","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@emergence-engineering/prosemirror-block-runner","version":"0.2.0","description":"Generic task queue processor for ProseMirror — process document blocks through configurable parallel workers with state management, retry logic, and visual feedback decorations","source":"src/index.ts","main":"dist/index.js","module":"dist/index.es.js","types":"dist/src/index.d.ts","engines":{"node":">=16.0.0"},"repository":{"type":"git","url":"git+https://github.com/emergence-engineering/emergence-tools.git","directory":"packages/prosemirror-block-runner"},"author":{"name":"Emergence Engineering"},"keywords":["ProseMirror","block-runner","task-queue","processor","decorations"],"license":"MIT","bugs":{"url":"https://github.com/emergence-engineering/emergence-tools/issues"},"homepage":"https://github.com/emergence-engineering/emergence-tools/tree/main/packages/prosemirror-block-runner#readme","publishConfig":{"access":"public"},"dependencies":{"@emergence-engineering/prosemirror-text-map":"0.1.5"},"peerDependencies":{"prosemirror-model":"^1.19.3","prosemirror-state":"^1.4.3","prosemirror-transform":"^1.7.3","prosemirror-view":"^1.31.4"},"devDependencies":{"@playwright/test":"^1.41.0","@types/jest":"^29.5.1","@typescript-eslint/eslint-plugin":"^6.7.2","@typescript-eslint/parser":"^6.7.2","eslint":"^8.49.0","eslint-config-airbnb-typescript":"^17.1.0","eslint-config-prettier":"^8.5.0","eslint-plugin-import":"^2.28.1","eslint-plugin-jsx-a11y":"^6.7.1","eslint-plugin-prettier":"^4.2.1","eslint-plugin-react":"^7.33.2","jest":"^29.5.0","prettier":"^2.8.1","prosemirror-example-setup":"^1.2.2","prosemirror-model":"^1.19.3","prosemirror-schema-basic":"^1.2.2","prosemirror-state":"^1.4.3","prosemirror-test-builder":"^1.1.1","prosemirror-transform":"^1.7.3","prosemirror-view":"^1.31.4","rimraf":"^5.0.0","rollup":"^3.29.2","rollup-plugin-minification":"^0.2.0","rollup-plugin-typescript2":"^0.35.0","ts-jest":"^29.1.0","typescript":"^5.2.2","vite":"^5.0.0"},"scripts":{"prebuild":"rimraf dist","build":"rollup -c --bundleConfigAsCjs","dev":"rollup -c -w --bundleConfigAsCjs","format":"eslint src --ext .ts --fix","lint":"tsc --noEmit","test":"pnpm test:unit && pnpm test:e2e","test:unit":"jest","test:e2e":"playwright test","e2e":"playwright test","e2e:server":"vite --config e2e/vite.config.ts"},"_id":"@emergence-engineering/prosemirror-block-runner@0.2.0","_integrity":"sha512-79S7eoSnjeut51iF0HBZQLZKQUr48ICgSvA+ie6IJfdb5UH6J3vd4+86+TUWdNMLnrkgxlDDrivYVWmolU/z5g==","_resolved":"/private/var/folders/vd/vvmzq4kn6vq7qwcgg92p1dnh0000gn/T/222c35c545c0db90293f3c00fcb18a52/emergence-engineering-prosemirror-block-runner-0.2.0.tgz","_from":"file:emergence-engineering-prosemirror-block-runner-0.2.0.tgz","_nodeVersion":"24.11.1","_npmVersion":"11.6.4","dist":{"integrity":"sha512-79S7eoSnjeut51iF0HBZQLZKQUr48ICgSvA+ie6IJfdb5UH6J3vd4+86+TUWdNMLnrkgxlDDrivYVWmolU/z5g==","shasum":"65b33d8d3a4f555ea565ab3ef9a30358ee6f2d63","tarball":"https://registry.npmjs.org/@emergence-engineering/prosemirror-block-runner/-/prosemirror-block-runner-0.2.0.tgz","fileCount":62,"unpackedSize":92563,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDSyMG9xTacUjhGhu9aDzNWoxJ9Bj6IwsymeyWkXGK3eAIgF3G8STMNZ5czniKdvzqzM7WW2+M86O/Azb6j7Z0R5NA="}]},"_npmUser":{"name":"tg44","email":"torcsvari.gergo@gmail.com"},"directories":{},"maintainers":[{"name":"matejcsok","email":"istvan.matejcsok@emergence-engineering.com"},{"name":"viktorvacziee","email":"viktor.vaczi@emergence-engineering.com"},{"name":"tg44","email":"torcsvari.gergo@gmail.com"},{"name":"cskata","email":"katalin.csillag@emergence-engineering.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/prosemirror-block-runner_0.2.0_1773056030772_0.3235112063835175"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-09T08:15:47.734Z","modified":"2026-03-09T11:33:51.082Z","0.1.0":"2026-03-09T08:15:47.992Z","0.2.0":"2026-03-09T11:33:50.910Z"},"bugs":{"url":"https://github.com/emergence-engineering/emergence-tools/issues"},"author":{"name":"Emergence Engineering"},"license":"MIT","homepage":"https://github.com/emergence-engineering/emergence-tools/tree/main/packages/prosemirror-block-runner#readme","keywords":["ProseMirror","block-runner","task-queue","processor","decorations"],"repository":{"type":"git","url":"git+https://github.com/emergence-engineering/emergence-tools.git","directory":"packages/prosemirror-block-runner"},"description":"Generic task queue processor for ProseMirror — process document blocks through configurable parallel workers with state management, retry logic, and visual feedback decorations","maintainers":[{"name":"matejcsok","email":"istvan.matejcsok@emergence-engineering.com"},{"name":"viktorvacziee","email":"viktor.vaczi@emergence-engineering.com"},{"name":"tg44","email":"torcsvari.gergo@gmail.com"},{"name":"cskata","email":"katalin.csillag@emergence-engineering.com"}],"readme":"# @emergence-engineering/prosemirror-block-runner\n\n[![logo](https://emergence-engineering.com/ee-logo.svg)](https://emergence-engineering.com)\n\n[**Made by Emergence Engineering**](https://emergence-engineering.com/)\n\n> Generic task queue processor for ProseMirror — process document blocks through configurable parallel workers with state management, retry logic, and visual feedback decorations.\n\n## Features\n\n- Parallel batch processing with configurable concurrency (`batchSize`)\n- Automatic retry with exponential backoff on processor errors\n- Decoration and widget factories for visual feedback\n- Dirty tracking with debounced re-processing on document edits\n- Accept/decline decorations (select, remove, deselect actions)\n- Pause/resume support\n- Position remapping as the document changes\n- Includes four example processors (link detector, word complexity, sentence length, random)\n\n## Installation\n\n```bash\nnpm install @emergence-engineering/prosemirror-block-runner\n```\n\n### Peer dependencies\n\n```bash\nnpm install prosemirror-model prosemirror-state prosemirror-transform prosemirror-view\n```\n\n## Quick Start\n\n```ts\nimport {\n  blockRunnerPlugin,\n  createBlockRunnerKey,\n  ActionType,\n  dispatchAction,\n} from \"@emergence-engineering/prosemirror-block-runner\";\nimport type {\n  ProcessingUnit,\n  UnitProcessorResult,\n} from \"@emergence-engineering/prosemirror-block-runner\";\nimport { Decoration } from \"prosemirror-view\";\nimport { EditorState } from \"prosemirror-state\";\nimport { EditorView } from \"prosemirror-view\";\n\n// 1. Create a plugin key\nconst myKey = createBlockRunnerKey(\"my-processor\");\n\n// 2. Define a processor function\nasync function myProcessor(view, unit: ProcessingUnit): Promise<UnitProcessorResult<string>> {\n  const result = await analyzeText(unit.text);\n  return { data: result };\n}\n\n// 3. Define a decoration factory\nfunction myDecorationFactory(response, unit) {\n  return [\n    Decoration.inline(unit.from, unit.to, { class: \"highlight\" }, {\n      id: {},\n      unitId: unit.id,\n      originalText: unit.text,\n      response,\n    }),\n  ];\n}\n\n// 4. Add the plugin to your editor\nconst state = EditorState.create({\n  schema,\n  plugins: [\n    blockRunnerPlugin({\n      pluginKey: myKey,\n      unitProcessor: myProcessor,\n      decorationFactory: myDecorationFactory,\n      initialContextState: {},\n      options: { batchSize: 2, maxRetries: 3 },\n    }),\n  ],\n});\n\nconst view = new EditorView(document.getElementById(\"editor\")!, { state });\n\n// 5. Start processing\ndispatchAction(view, myKey, { type: ActionType.INIT, metadata: { single: {} } });\n```\n\n## Options\n\n`BlockRunnerPluginConfig` fields:\n\n| Option | Type | Default | Description |\n|---|---|---|---|\n| `pluginKey` | `PluginKey` | _(required)_ | Unique key created via `createBlockRunnerKey()` |\n| `unitProcessor` | `UnitProcessor<R, M>` | _(required)_ | Async function that processes a single unit |\n| `decorationFactory` | `DecorationFactory<R, M, C>` | _(required)_ | Converts processor response to decorations |\n| `initialContextState` | `C` | _(required)_ | Initial context state shared across units |\n| `decorationTransformer` | `DecorationTransformer<R, C, M>` | `undefined` | Post-process decorations (e.g. highlight selected) |\n| `widgetFactory` | `WidgetFactory<M>` | `undefined` | Creates loading/error widgets for in-progress units |\n| `handleKeyDown` | `(view, event) => boolean \\| void` | `undefined` | Optional keyboard handler |\n| `options.nodeTypes` | `string \\| string[]` | `\"paragraph\"` | Node types to process |\n| `options.batchSize` | `number` | `4` | Parallel worker count |\n| `options.maxRetries` | `number` | `3` | Max retry attempts per unit |\n| `options.backoffBase` | `number` | `1000` | Initial backoff delay in ms |\n| `options.dirtyHandling.shouldRecalculate` | `boolean` | `true` | Re-process when text changes |\n| `options.dirtyHandling.debounceDelay` | `number` | `2000` | Debounce delay before re-processing (ms) |\n| `options.dirtyHandling.skipDirtyOnSelfChange` | `boolean` | `true` | Skip dirty marking for plugin's own changes |\n\n## API\n\n| Export | Type | Description |\n|---|---|---|\n| `blockRunnerPlugin` | `function` | Creates the ProseMirror plugin |\n| `createBlockRunnerKey` | `function` | Creates a typed `PluginKey` for the runner |\n| `dispatchAction` | `function` | Dispatches an action to a runner plugin |\n| `pauseRunner` | `function` | Pauses the runner (dispatches `FINISH`) |\n| `resumeRunner` | `function` | Resumes a paused runner |\n| `canResume` | `function` | Checks if a runner can be resumed |\n| `getProgress` | `function` | Returns `{ completed, total, decorations }` |\n| `ActionType` | `enum` | `INIT`, `FINISH`, `CLEAR`, `RESUME`, `UNIT_STARTED`, `UNIT_SUCCESS`, `UNIT_ERROR`, etc. |\n| `RunnerStatus` | `enum` | `IDLE`, `ACTIVE` |\n| `UnitStatus` | `enum` | `DIRTY`, `WAITING`, `QUEUED`, `PROCESSING`, `DONE`, `BACKOFF`, `ERROR` |\n| `defaultRunnerOptions` | `object` | Default option values |\n| `mergeOptions` | `function` | Merges partial options with defaults |\n| `createUnitsFromDocument` | `function` | Extracts processing units from a document |\n| `getUnitsInRange` | `function` | Gets units within a document range |\n| `textPosToDocPos` | `function` | Maps text position to document position |\n| `remapPositions` | `function` | Remaps unit/decoration positions after doc changes |\n| `calculateBackoff` | `function` | Computes retry delay for a given attempt |\n| `executeParallel` | `function` | Core parallel execution loop |\n| `handleAction` | `function` | State reducer for runner actions |\n| `createInitialState` | `function` | Creates the initial runner state |\n\n## How It Works\n\n1. **INIT** — The document is scanned for matching node types (default: paragraphs). Each node becomes a `ProcessingUnit` with text, position range, and text-to-doc position mapping.\n2. **Parallel execution** — Units are picked up in batches (`batchSize`). Each unit is passed to the `unitProcessor`. On success, the `decorationFactory` converts the response into ProseMirror decorations.\n3. **Retry/backoff** — If a processor returns `{ error }`, the unit enters `BACKOFF` status and is retried after an exponentially increasing delay (`backoffBase * 2^retryCount`), up to `maxRetries`.\n4. **Dirty tracking** — When the document changes, affected units are marked `DIRTY` and re-processed after a debounce delay. Position mappings are updated automatically via ProseMirror's transaction mapping.\n5. **Accept/decline** — Users interact with decorations via `SELECT_DECORATION` (highlight one), `REMOVE_DECORATION` (accept or decline), and `DESELECT_DECORATION` (clear selection). To accept a suggestion, apply the change to the document and remove the decoration in a single transaction. To decline, just remove the decoration without modifying the document.\n6. **FINISH/CLEAR** — Processing completes automatically when all units are done. Dispatch `CLEAR` to remove all decorations and reset.\n\n## Playground\n\nSee the [interactive demo](https://emergence-engineering.github.io/emergence-tools/#blockRunner) in the monorepo playground.\n\n## License\n\nMIT\n","readmeFilename":"README.md"}