diff --git a/README.md b/README.md index 938b362..591968d 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,9 @@ nobody hosts it yet: run it yourself, or hand over the `.flow` file or the brief - **Frames.** A frame (from the library) is a named region behind the shapes inside it: dragging it carries them along, in one undo step. From its menu, copy just that frame for AI or export it on its own. The brief groups shapes by frame, and Mermaid draws frames as subgraphs. +- **Draft with your agent.** Describe a diagram in words, and isketch writes a prompt that teaches + the `.flow` format: copy it, or open it straight in Claude or ChatGPT, then paste the answer back + and it is drawn, undoably. Your own agent does the drafting; isketch sends nothing anywhere. - **Quick to type.** On a blank diagram, just start typing: the first shape takes the words. Double-click empty canvas for a shape there. `Tab` adds the next step below the selected shape, connected and ready to name, as in Whimsical. `Ctrl+K` finds any action or shape by name. diff --git a/backlog/tasks/fl-90 - Diagram-from-words.md b/backlog/tasks/fl-90 - Diagram-from-words.md index 355cc63..4010161 100644 --- a/backlog/tasks/fl-90 - Diagram-from-words.md +++ b/backlog/tasks/fl-90 - Diagram-from-words.md @@ -1,9 +1,11 @@ --- id: FL-90 title: Diagram from words -status: To Do -assignee: [] +status: In Progress +assignee: + - '@raj-khan' created_date: '2026-09-25 17:39' +updated_date: '2026-09-27 14:40' labels: - ai milestone: m-3 @@ -24,7 +26,37 @@ Describe a system in a sentence and get a .flow diagram, built on the text forma -- [ ] #1 A Draft with your agent prompt, copyable from the app, that makes any agent return valid .flow +- [x] #1 A Draft with your agent prompt, copyable from the app, that makes any agent return valid .flow - [ ] #2 A built-in version is decided by the owner (cost of an API key or server) + +## Implementation Plan + + + +1. src/domain/draft.js: draftPrompt(description) teaches the .flow format (rules, every shape from the registry, a valid example) and asks for one fenced block; readDraft(answer) takes the first fenced block or the whole answer, with line errors; AGENT_LINKS opens the prompt filled in at Claude and ChatGPT. +2. DraftDialog: describe, copy or open the prompt, paste the answer (live status), Use this diagram as one undoable change. In the menu and the command palette. +3. The built-in version (AC #2) is the owners decision on cost; asked, not built. + + + +## Implementation Notes + + + +Verified: draft.spec.js (6: description, every shape and the rules in the prompt; its example parses; agent links; fenced and unfenced answers; errors by line; an answer with no shapes); e2e/draft.spec.js (copy, link, a wrong answer blocked with its line, a real-looking answer drawn, undo). Tried on a real model: claude -p --model haiku with the prompt for a URL shortener returned valid .flow at once (8 shapes, 7 connections, every part named). vitest and e2e green. + + +## Comments + + + +author: @claude +created: 2026-09-27 14:40 +--- + +For the owner: a built-in "Describe and draw" needs either the persons own API key in the browser (no cost to you, a little setup for them) or a call through the isketch server with your key (simplest for them, costs you per diagram, needs rate limits). Which, if either? +--- + + diff --git a/e2e/draft.spec.js b/e2e/draft.spec.js new file mode 100644 index 0000000..403d4d6 --- /dev/null +++ b/e2e/draft.spec.js @@ -0,0 +1,64 @@ +import { expect, test } from '@playwright/test' + +import { fromMenu, history } from './helpers.js' + +const shapes = (page) => page.locator('.vue-flow__node') + +const ANSWER = `Here is your diagram: + +\`\`\`flow +title: Checkout +note: Failed payments retry + +web = screen "Web app" -- /checkout +pay = process "Payments API" +stripe = terminal "Stripe" +db = database "Orders" + +web -> pay : HTTPS +pay -> stripe : charge +pay -> db : write order +pay --> web : retry +\`\`\` + +Tell me if you want the refunds flow too.` + +test('drafts a diagram with your agent: copy the prompt, paste the answer, undo', async ({ + page, + context, +}) => { + await context.grantPermissions(['clipboard-read', 'clipboard-write']) + await page.goto('/new') + await expect(shapes(page)).toHaveCount(5) + + await fromMenu(page, 'Draft with your agent') + const dialog = page.getByRole('dialog', { name: 'Draft with your agent' }) + const copy = dialog.getByRole('button', { name: 'Copy prompt' }) + await expect(copy).toBeDisabled() + + await dialog + .getByLabel('1. Describe the diagram') + .fill('A checkout that charges Stripe and writes orders.') + await copy.click() + const prompt = await page.evaluate(() => navigator.clipboard.readText()) + expect(prompt).toContain('A checkout that charges Stripe and writes orders.') + expect(prompt).toContain('Reply with only the diagram') + await expect(dialog.getByRole('link', { name: 'Open in Claude' })).toHaveAttribute( + 'href', + /^https:\/\/claude\.ai\/new\?q=Draw%20this/, + ) + + const answer = dialog.getByLabel('2. Paste the answer') + await answer.fill('```flow\ntitle: X\na = blob "A"\n```') + await expect(dialog.getByRole('status')).toContainText('Line 2:') + await expect(dialog.getByRole('button', { name: 'Use this diagram' })).toBeDisabled() + + await answer.fill(ANSWER) + await expect(dialog.getByRole('status')).toHaveText('4 shapes and 4 connections, ready.') + await dialog.getByRole('button', { name: 'Use this diagram' }).click() + + await expect(shapes(page)).toHaveCount(4) + await expect(page.locator('.vue-flow__node[data-id="stripe"]')).toContainText('Stripe') + await history(page).getByRole('button', { name: 'Undo' }).click() + await expect(shapes(page)).toHaveCount(5) +}) diff --git a/src/components/draft/DraftDialog.vue b/src/components/draft/DraftDialog.vue new file mode 100644 index 0000000..d72ad1f --- /dev/null +++ b/src/components/draft/DraftDialog.vue @@ -0,0 +1,144 @@ + + +