From fcbd2cb94ef738930cb97ffa1245c316938861e7 Mon Sep 17 00:00:00 2001 From: taly Date: Wed, 1 Jul 2026 11:43:10 +0300 Subject: [PATCH 1/2] fix: skip unknown step types / transform ops with a warning instead of crashing Cross-app parity with the flowrunner-cli fix. The JS engine had the same two forward-compat failures: - flowRunner.js threw on an unknown step type -> the whole run halted. - transformOps.js silently downgraded an unknown transform op to base64_decode -> the wrong operation executed. Both now degrade gracefully (skip-with-machine-readable-warning, continue): - Unknown step type -> {status:'skipped', unsupported:true} + a user warning. - Unknown transform op -> recorded in executeTransformOps' warnings[] (TRANSFORM_OP_UNSUPPORTED marker), surfaced by the transform step; the op's output var is left unset. normalizeTransformOp is left tolerant because the editor (flowStepComponents.js) also calls it. Also fixes the Jest harness: setup.js require()d the vendored UMD drawflow.min.js, unloadable under the app's "type":"module", so npm test was fully broken (CI never runs it). Added a CommonJS marker package.json in the vendored dir. This unblocked the suite (122 passing) and revealed 4 pre-existing flowVisualizer.test.js failures (tracked separately). Adds 2 unit tests (RED->GREEN). Bible updated (changelog + gotchas). Co-Authored-By: Claude Opus 4.8 --- __tests__/flowRunner.test.js | 15 +++++++++++++++ __tests__/transformOps.test.js | 17 +++++++++++++++++ assets/vendor/drawflow/package.json | 4 ++++ changelog.md | 2 ++ flowRunner.js | 20 +++++++++++++++++++- gotchas.md | 6 ++++++ transformOps.js | 17 +++++++++++++++++ 7 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 assets/vendor/drawflow/package.json diff --git a/__tests__/flowRunner.test.js b/__tests__/flowRunner.test.js index fe016aa..5f03ce8 100644 --- a/__tests__/flowRunner.test.js +++ b/__tests__/flowRunner.test.js @@ -156,6 +156,21 @@ describe('FlowRunner', () => { global.clearTimeout = originalGlobalClearTimeout; }); + describe('graceful degradation (unknown step types)', () => { + test('unknown step type is skipped with a warning, not thrown or stopped', async () => { + const step = { id: 'u1', name: 'Future Step', type: 'future_unknown_type' }; + await runner._executeSingleStepLogic(step, {}); + const last = runner.state.results[runner.state.results.length - 1]; + expect(last.status).toBe('skipped'); + expect(last.unsupported).toBe(true); + // the flow is NOT halted + expect(runner.state.stopRequested).toBe(false); + expect(mockOnError).not.toHaveBeenCalled(); + // a warning was surfaced to the user + expect(mockOnMessage).toHaveBeenCalled(); + }); + }); + describe('state management', () => { it('should initialize with isRunning and isStepping false', () => { expect(runner.isRunning()).toBe(false); diff --git a/__tests__/transformOps.test.js b/__tests__/transformOps.test.js index d42cc45..6e62d71 100644 --- a/__tests__/transformOps.test.js +++ b/__tests__/transformOps.test.js @@ -36,4 +36,21 @@ describe('transformOps', () => { expect(context.decoded.payload.exp).toBe(123); expect(context.decoded.header.alg).toBe('none'); }); + + test('unknown transform op is skipped with a warning (not downgraded to base64_decode)', async () => { + const context = {}; + const ops = [ + // "SGVsbG8" base64url-decodes to "Hello"; the old bug set decoded = "Hello". + { op: 'totally_unknown_future_op', set: 'decoded', args: ['SGVsbG8'], options: {} }, + { op: 'base64_encode', set: 'enc', args: ['hi'], options: { base64: 'url' } }, + ]; + const output = await executeTransformOps(ops, context, { evaluatePath: simpleEvaluatePath }); + expect(context.decoded).toBeUndefined(); // unknown op skipped, NOT base64-decoded + expect(context.enc).toBeDefined(); // subsequent known op still ran + expect(output.updatedVars).toEqual(['enc']); + expect(output.warnings).toHaveLength(1); + expect(output.warnings[0].op).toBe('totally_unknown_future_op'); + expect(output.warnings[0].set).toBe('decoded'); + expect(output.warnings[0].status).toBe('skipped'); + }); }); diff --git a/assets/vendor/drawflow/package.json b/assets/vendor/drawflow/package.json new file mode 100644 index 0000000..c409e42 --- /dev/null +++ b/assets/vendor/drawflow/package.json @@ -0,0 +1,4 @@ +{ + "type": "commonjs", + "//": "Vendored UMD build of Drawflow. The app's root package.json is \"type\": \"module\", which would make Node treat this .js as ESM and break the Jest setup's require() of it. This marker keeps only this vendored dir CommonJS so it stays require-able in Node/Jest. The browser loads drawflow.min.js via a plain