From 4847d8daa4e16df74ea6031bb839d8726a26df6d Mon Sep 17 00:00:00 2001 From: Csaba Tuncsik Date: Wed, 13 May 2026 10:09:01 +0200 Subject: [PATCH 1/7] docs: add implementation plan for AI sample-data pinner --- .claude/plans/PLAN.md | 652 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 652 insertions(+) create mode 100644 .claude/plans/PLAN.md diff --git a/.claude/plans/PLAN.md b/.claude/plans/PLAN.md new file mode 100644 index 00000000000..e5efa79af64 --- /dev/null +++ b/.claude/plans/PLAN.md @@ -0,0 +1,652 @@ +# "Generate Sample" Pinner Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add a one-click "Generate sample" button in the NDV that asks the AI assistant to produce realistic mock input data for the current node and pins it via the existing `pinData` plumbing, so a builder can test downstream nodes without firing the real upstream trigger. + +**Architecture:** +- Frontend-only feature for a 1-day MVP. No backend changes — we reuse the existing `POST /ai/ask-ai` endpoint that the Code/AI Transform nodes already call (returns `{ code: string }`). The "code" string is reinterpreted as a JSON array of items. +- A new presentational button component (`RunDataGenerateSampleButton.vue`) sits next to `RunDataPinButton.vue` in `RunData.vue`'s header. Visibility is gated by the same `canPinData` / read-only / redaction checks the pin button already uses, plus `settingsStore.isAskAiEnabled`. +- A new composable (`useGenerateSampleData.ts`) owns the request lifecycle: build payload, call `generateCodeForPrompt`, validate the response with a Zod schema, normalise to `INodeExecutionData[]`, then delegate to the existing `usePinnedData().setData(...)` to actually pin and emit telemetry. +- Toasts via the existing `useToast()` composable. All copy via `@n8n/i18n` (`ndv.pinData.generate.*`). No new design-system components; only `N8nIconButton` + `N8nTooltip` already in use. +- Validation: Zod parses the model output as `z.array(z.record(z.string(), z.unknown())).min(1).max(10)`. Each item is wrapped to `{ json: item }` and length-capped to 3 items for the MVP. + +**Tech Stack:** Vue 3 SFC (` + + +``` + +> If `sparkles` is not an available icon name in `@n8n/design-system`, fall back to `magic-wand` or `wand-sparkles`. Check available icons with: +> ```bash +> grep -rn "sparkles\|magic-wand\|wand-sparkles" packages/frontend/@n8n/design-system/src/components/N8nIcon 2>&1 | head +> ``` +> Pick the first one that exists. If none, use `'zap'` (always available). + +Re-run the test: + +```bash +cd packages/frontend/editor-ui && pnpm vitest run src/features/ndv/runData/components/RunDataGenerateSampleButton.test.ts 2>&1 | tail -20 +``` + +Expected: all 5 tests pass. + +#### - [ ] Step 2.4: Typecheck and commit + +```bash +cd packages/frontend/editor-ui && pnpm typecheck 2>&1 | tail -20 +``` + +Expected: clean. + +```bash +cd /Users/csaba/conductor/workspaces/n8n/hackmation-sample-data-pinner && git add packages/frontend/editor-ui/src/features/ndv/runData/components/RunDataGenerateSampleButton.vue packages/frontend/editor-ui/src/features/ndv/runData/components/RunDataGenerateSampleButton.test.ts packages/frontend/@n8n/i18n/src/locales/en.json && git commit -m "feat(editor): add RunDataGenerateSampleButton component and i18n strings" +``` + +--- + +### Task 3 — Wire the button into `RunData.vue` + +**Files:** +- Modify: `packages/frontend/editor-ui/src/features/ndv/runData/components/RunData.vue` + +#### - [ ] Step 3.1: Import composable, component, and settings store + +In the ` + + From bf142f96390545dc77d256c31ad5dafbcec8ab42 Mon Sep 17 00:00:00 2001 From: Csaba Tuncsik Date: Wed, 13 May 2026 11:07:33 +0200 Subject: [PATCH 4/7] feat(editor): wire Generate sample data button into NDV RunData header Gates visibility behind isAskAiEnabled. The RunData.test.ts helper does not support per-test settings overrides, so the integration assertion was skipped; RunDataGenerateSampleButton and useGenerateSampleData unit tests provide coverage for the MVP. --- .../ndv/runData/components/RunData.vue | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/packages/frontend/editor-ui/src/features/ndv/runData/components/RunData.vue b/packages/frontend/editor-ui/src/features/ndv/runData/components/RunData.vue index 13125b0f46c..eb8aa12eac8 100644 --- a/packages/frontend/editor-ui/src/features/ndv/runData/components/RunData.vue +++ b/packages/frontend/editor-ui/src/features/ndv/runData/components/RunData.vue @@ -48,6 +48,9 @@ import JsonEditor from '@/features/shared/editors/components/JsonEditor/JsonEdit import { useInjectWorkflowId } from '@/app/composables/useInjectWorkflowId'; import { useRunWorkflow } from '@/app/composables/useRunWorkflow'; import RunDataPinButton from './RunDataPinButton.vue'; +import RunDataGenerateSampleButton from './RunDataGenerateSampleButton.vue'; +import { useGenerateSampleData } from '@/features/ndv/runData/composables/useGenerateSampleData'; +import { useSettingsStore } from '@/app/stores/settings.store'; import { useExternalHooks } from '@/app/composables/useExternalHooks'; import { useI18n } from '@n8n/i18n'; import { useNodeHelpers } from '@/app/composables/useNodeHelpers'; @@ -256,6 +259,8 @@ const pinnedData = usePinnedData(node, { runIndex: props.runIndex, displayMode: props.displayMode, }); +const settingsStore = useSettingsStore(); +const sampleGenerator = useGenerateSampleData(node); const { isSubNodeType } = useNodeType({ node, }); @@ -636,6 +641,18 @@ const pinButtonDisabled = computed( () => hasNoData.value || hasBinaryData.value || isReadOnly.value, ); +const showGenerateSampleButton = computed( + () => + showPinButton.value && + settingsStore.isAskAiEnabled && + !isReadOnlyRoute.value && + !readOnlyEnv.value && + !isExecutionRedacted.value && + sampleGenerator.canGenerate.value, +); + +const generateSampleDisabled = computed(() => pinButtonDisabled.value || pinnedData.hasData.value); + const activeTaskMetadata = computed((): ITaskMetadata | null => { if (!node.value) return null; const errorMetadata = parseErrorMetadata(workflowRunErrorAsNodeError.value); @@ -1572,6 +1589,13 @@ defineExpose({ enterEditMode }); @click="enterEditMode({ origin: 'editIconButton' })" /> + + Date: Wed, 13 May 2026 12:03:59 +0200 Subject: [PATCH 5/7] test(editor): cover code-fence stripping, concurrent guard, AI payload --- .../composables/useGenerateSampleData.test.ts | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/packages/frontend/editor-ui/src/features/ndv/runData/composables/useGenerateSampleData.test.ts b/packages/frontend/editor-ui/src/features/ndv/runData/composables/useGenerateSampleData.test.ts index c062547ded0..c5937dd3676 100644 --- a/packages/frontend/editor-ui/src/features/ndv/runData/composables/useGenerateSampleData.test.ts +++ b/packages/frontend/editor-ui/src/features/ndv/runData/composables/useGenerateSampleData.test.ts @@ -113,4 +113,49 @@ describe('useGenerateSampleData', () => { await useGenerateSampleData(empty).generate(); expect(generateCodeForPromptMock).not.toHaveBeenCalled(); }); + + it('strips markdown code fences before parsing', async () => { + generateCodeForPromptMock.mockResolvedValue({ + code: '```json\n[{"id":"a"},{"id":"b"}]\n```', + }); + await useGenerateSampleData(node).generate(); + expect(setDataMock).toHaveBeenCalledWith( + [{ json: { id: 'a' } }, { json: { id: 'b' } }], + 'pin-icon-click', + ); + expect(showErrorMock).not.toHaveBeenCalled(); + }); + + it('ignores a second generate() while one is in flight', async () => { + let resolveFirst: (value: { code: string }) => void = () => {}; + generateCodeForPromptMock.mockImplementationOnce( + () => + new Promise<{ code: string }>((resolve) => { + resolveFirst = resolve; + }), + ); + + const composable = useGenerateSampleData(node); + const first = composable.generate(); + const second = composable.generate(); + await second; + expect(generateCodeForPromptMock).toHaveBeenCalledTimes(1); + + resolveFirst({ code: '[{"x":1}]' }); + await first; + expect(generateCodeForPromptMock).toHaveBeenCalledTimes(1); + }); + + it('sends a payload that includes the node type, parameters, and forNode=transform', async () => { + generateCodeForPromptMock.mockResolvedValue({ code: '[{"ok":true}]' }); + await useGenerateSampleData(node).generate(); + + expect(generateCodeForPromptMock).toHaveBeenCalledTimes(1); + const [, payload] = generateCodeForPromptMock.mock.calls[0]; + expect(payload.forNode).toBe('transform'); + expect(payload.context.inputSchema.nodeName).toBe('Slack'); + expect(payload.question).toContain('n8n-nodes-base.slack'); + expect(payload.question).toContain('"resource": "message"'); + expect(payload.question).toContain('JSON array'); + }); }); From 3723bf1843b0a22ec5063fabb31956b02bb7a003 Mon Sep 17 00:00:00 2001 From: Csaba Tuncsik Date: Thu, 14 May 2026 05:43:47 +0200 Subject: [PATCH 6/7] chore: stop tracking local plan file --- .claude/plans/PLAN.md | 652 ------------------------------------------ 1 file changed, 652 deletions(-) delete mode 100644 .claude/plans/PLAN.md diff --git a/.claude/plans/PLAN.md b/.claude/plans/PLAN.md deleted file mode 100644 index e5efa79af64..00000000000 --- a/.claude/plans/PLAN.md +++ /dev/null @@ -1,652 +0,0 @@ -# "Generate Sample" Pinner Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Add a one-click "Generate sample" button in the NDV that asks the AI assistant to produce realistic mock input data for the current node and pins it via the existing `pinData` plumbing, so a builder can test downstream nodes without firing the real upstream trigger. - -**Architecture:** -- Frontend-only feature for a 1-day MVP. No backend changes — we reuse the existing `POST /ai/ask-ai` endpoint that the Code/AI Transform nodes already call (returns `{ code: string }`). The "code" string is reinterpreted as a JSON array of items. -- A new presentational button component (`RunDataGenerateSampleButton.vue`) sits next to `RunDataPinButton.vue` in `RunData.vue`'s header. Visibility is gated by the same `canPinData` / read-only / redaction checks the pin button already uses, plus `settingsStore.isAskAiEnabled`. -- A new composable (`useGenerateSampleData.ts`) owns the request lifecycle: build payload, call `generateCodeForPrompt`, validate the response with a Zod schema, normalise to `INodeExecutionData[]`, then delegate to the existing `usePinnedData().setData(...)` to actually pin and emit telemetry. -- Toasts via the existing `useToast()` composable. All copy via `@n8n/i18n` (`ndv.pinData.generate.*`). No new design-system components; only `N8nIconButton` + `N8nTooltip` already in use. -- Validation: Zod parses the model output as `z.array(z.record(z.string(), z.unknown())).min(1).max(10)`. Each item is wrapped to `{ json: item }` and length-capped to 3 items for the MVP. - -**Tech Stack:** Vue 3 SFC (` - - -``` - -> If `sparkles` is not an available icon name in `@n8n/design-system`, fall back to `magic-wand` or `wand-sparkles`. Check available icons with: -> ```bash -> grep -rn "sparkles\|magic-wand\|wand-sparkles" packages/frontend/@n8n/design-system/src/components/N8nIcon 2>&1 | head -> ``` -> Pick the first one that exists. If none, use `'zap'` (always available). - -Re-run the test: - -```bash -cd packages/frontend/editor-ui && pnpm vitest run src/features/ndv/runData/components/RunDataGenerateSampleButton.test.ts 2>&1 | tail -20 -``` - -Expected: all 5 tests pass. - -#### - [ ] Step 2.4: Typecheck and commit - -```bash -cd packages/frontend/editor-ui && pnpm typecheck 2>&1 | tail -20 -``` - -Expected: clean. - -```bash -cd /Users/csaba/conductor/workspaces/n8n/hackmation-sample-data-pinner && git add packages/frontend/editor-ui/src/features/ndv/runData/components/RunDataGenerateSampleButton.vue packages/frontend/editor-ui/src/features/ndv/runData/components/RunDataGenerateSampleButton.test.ts packages/frontend/@n8n/i18n/src/locales/en.json && git commit -m "feat(editor): add RunDataGenerateSampleButton component and i18n strings" -``` - ---- - -### Task 3 — Wire the button into `RunData.vue` - -**Files:** -- Modify: `packages/frontend/editor-ui/src/features/ndv/runData/components/RunData.vue` - -#### - [ ] Step 3.1: Import composable, component, and settings store - -In the `