Repository navigation
feat(agent-chat): .create_plot() confirms and plots in one click - #12557
Merged
Merged
Conversation
…open in step 2 A follow-up test on today's data box, which stays live while the composer changes: confirmed data with a table read from text waiting, a dropped file fills the box and discards the table, and the card stays open once the page is back at step 2. It fails with onExpand as a no-op. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
LoaderSpinner gains size="inline" (40 x 8 px, a span that sits in a line of text) for the agent chat's working lines. Each size names its own keyframes (trfLoader-inline, ballMoveX-inline): emotion writes @Keyframes under their literal names, so two sizes on one page shared the last block. The loader is aria-hidden, keeps a fixed box, and its dots stand still under prefers-reduced-motion. The gallery's large and small sizes render as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
…_OPEN_MS The BFF mock's dataset route classifies a paste before any parser, as the service's classify_paste does: code at any length answers 422 code, a one-line request for data 422 data_request (the formula guard stays where it was). A chat turn that holds such text answers with the spec's made-up table, as run_data_code would. MOCK_OPEN_MS delays POST /sessions, so the page's opening state, where the public lane's bot check runs, stays visible. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
The step line, the pause note, .example_data(), the message box with its buttons, the notice before the confirmation and the counter move from AgentChatPage.tsx into Composer.tsx, with the same output: the page keeps every decision (what a button does and when it waits) and passes it in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
…m-first gate Step 1's .confirm() moves from the data card's foot to the composer's right end as the filled CTA, after .send(), with the step line above the box as its description: what it waits for (a table read from text, a required role without a column), else what to check, and after a lost confirmation what lost it. It waits beside .stop() while the summary runs, and a refused confirmation shows under the composer. .create_plot(), .start_over(), the carry after a switch and .plot_all() exist only in step 2, so the notice that said "confirm the data first" goes with its .show() and status line. The card collapses when the user's confirmation starts, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
parseData forwards 422 formula and 422 code within 2,000 characters to the chat as the user's turn, as it does a request for data, and empties the data box: the card's offer with its .send() goes, and so do the hook's formula, formulaKind, dataRequest and sendFormula. Code over the cap is a failure whose line asks to shorten it. parseData resolves to a ParseResult (stored, transcribed, forwarded, failed, or skipped when nothing started), with how the turn it started ended: runTurn records done, error, refused or stopped. agent_data_parsed reports formula and code instead of error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
The LoaderSpinner dots, inline, take the place of the pulsing ellipsis where the chat waits for seconds: the session's opening (where the public lane's bot check runs), the thread's in-progress line, beside the progress timeline of a run (above it on a phone), and the data box's overlay while a file is read or the data is parsed, whose foot bar goes with it. .example_data() steps aside while its turn runs, so "making example data" shows once, in the thread with the loader. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
… the docs root.md places .confirm() beside the message box wherever it named the data panel for it, drops .confirm() and the formula's .send() from the data panel bullet, and lists the composer's step 1 and step 2 actions (with the admin's .plot_all()); the session block's line says the same. A drift test now checks every composer button against the composer bullet, as the data card's are, and that no sentence sends the user to the data panel for .confirm(). DATA_SUMMARY_ID moves to steps.ts, so the composer's components are its own. The style guide (one filled CTA per step, the site's loader in the chat), agent-network.md, api.md, plausible.md (agent_data_parsed gains formula and code) and the changelog follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
…second-opinion fixes A question or a summary waiting in the queue no longer mounts the progress grid beside its in-progress line: the grid shows for a plot run and for a turn that reached a pipeline step, so a queued wait has one loader and one queue position. The parse hint and the bindings' wait line lose their ellipsis, so the overlay's loader is the parse's one moving indicator, and the .example_data() line keeps its room while its turn runs, so the composer does not jump. A refused confirmation gives the keyboard back to .confirm(); the composer's buttons stay at its right end when they wrap at phone width; the opening line is a polite status. A turn the page unmounted under reports no end instead of done. Tests close breaks that stayed green: the forward reaches the box before the turn's request, a turn that never started reports no earlier end, the step line's "step 1 again" with an open role, the small loader's keyframes, and the drift tests compare the data panel and the composer bullets with their buttons in both directions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
The loader's resting pair takes the warm gray of muted text (--ink-muted, which follows the theme) and its runner the brand green while it travels and grows, gray again at the loop's ends where it joins the pair. Under reduced motion the still runner stays green, so the loader keeps reading as working. The plots gallery changes with it; colors.accent stays for the idea reaction. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
The loader beside a plot run's progress sat in a fixed 24 px box, about 2 px below the queue line or the grid's first row, and 1.5 px below the active step's dot. Its box is now one text line of the run's font, which both columns inherit from the timeline: the loader centres on the line the way the step dots centre in their cells, for the queue line and the first row alike. Measured at 1440 px: 0.0 px from the active step's dot, 0.5 px from the text's middle in both states (2.0 before). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
…lick The separate .confirm() click is gone. From the moment data is stored, .create_plot() is the composer's one filled CTA: it confirms the data and its bindings as the card shows them, then starts the plot in the same click (the box's text as the plot's request when there is any). A refused confirmation starts nothing and shows under the composer. .plot_all() and a switch's carry offer chain the same confirmation; Enter and .send() never confirm, and .send() shows only while the box holds text. The confirm route and the data_unconfirmed gate stay as they are. The root prompt, the session block, the drift tests, the BFF mock replies, the style guide, plausible.md and agent-network.md follow the one click. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
The page lost .confirm(), and the prompt no longer names it. The check stays on the old name and does not move to .create_plot(): after a turn that shipped a plot and then changed the bindings or the data, .create_plot() is the right next step, and only the language-dependent claim around the name tells the two apart. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
A visitor who asks for a violin plot, or which plots would suit the data, now hears in one or two statements that switching the plot type in the chat is planned but not possible yet, that this page adapts this plot, and that the catalogue at anyplot.ai/plots has the others, each with its own chat where the data is pasted again. The root may name a kind of plot that fits the data, never that the catalogue has a particular one, and offers no other change in its place. The scope judge gets an in-scope example line; the scope eval and the prep eval get four plot_type cases each, in English and German. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
…rn keeps the note Two findings of the pre-PR check: - The draft is cleared before the note's turn starts. The turn puts the text in the thread before its request goes, so a turn refused at once (a limit, the capacity, the network) shows it there with the error: a comment says so, and a page test pins it with a 429. - The page tests now cover a refused confirmation on both paths: .create_plot() with a 422 and text, and with a 500 and an empty box, sends no message and starts no plot; .plot_all() with a 503 opens no panel and copies nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
With `all` pressed, step 1's line above the composer said "then .create_plot()" over a composer whose action is `.plot_all()`, and no `.create_plot()` shows there. The line, also after a lost confirmation or with an open role, now names the button the click is on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Contributor
There was a problem hiding this comment.
🔵 Needs a closer look
The change spans confirmation sequencing, SSE completion, admin fan-out, agent safety, BFF mocking, and shared responsive composer behavior, while browser, live-service, deployment-level, and accessibility validation remain unavailable.
No open issues 🎉
What changed in this PR
This PR unifies data confirmation and plot creation into composer actions, adds automatic forwarding for formulas and code, standardizes loading indicators, and updates prompts, documentation, mocks, and tests for the revised agent-chat flow.
Changes:
- Replaces the separate
.confirm()action with.create_plot(),.plot_all(), and carry-action confirmation flows. - Forwards supported formulas, code, and data requests to chat, with length handling and updated telemetry.
- Adds shared inline loaders across session, chat, plotting, and data parsing states.
- Adds guidance and evaluation coverage for alternative plot-type requests.
- Updates related UI, agent prompts, documentation, fixtures, and test coverage.
This pull request requires changes.
| File | Description |
|---|---|
tests/unit/agents/test_prompt_controls.py |
Validates prompt controls and composer button documentation. |
tests/unit/agents/runtime/test_policy.py |
Updates scope-rubric example coverage. |
tests/unit/agents/evals/test_scope.py |
Tests plot-type requests as in scope. |
tests/unit/agents/evals/test_prep.py |
Adds plot-type preparation fixture checks. |
docs/reference/style-guide.md |
Documents the updated CTA and loader styling. |
docs/reference/plausible.md |
Updates analytics event statuses and confirmation behavior. |
docs/reference/api.md |
Documents automatic forwarding; nit (1 vote): the example-data paragraph still instructs users to confirm data instead of checking it and pressing .create_plot(). |
changelog.d/agent-chat-cta-loader.md |
Records the user-facing agent-chat and loader changes. |
app/src/sections/agent-chat/styles.ts |
Updates CTA documentation for the composer. |
app/src/sections/agent-chat/steps.ts |
Adds step-line generation and timeline visibility logic. |
app/src/sections/agent-chat/steps.test.ts |
Tests step messaging and wait states. |
app/src/sections/agent-chat/StepHeader.tsx |
Updates step-flow documentation. |
app/src/sections/agent-chat/ProgressTimeline.tsx |
Adds the shared loader to plot progress. |
app/src/sections/agent-chat/ProgressTimeline.test.tsx |
Verifies loader placement and accessibility. |
app/src/sections/agent-chat/PinnedData.tsx |
Updates pinned confirmation-loss text and references. |
app/src/sections/agent-chat/messages.ts |
Adds revised confirmation, forwarding, and failure messages. |
app/src/sections/agent-chat/ExampleData.tsx |
Keeps the example-data row stable while work runs. |
app/src/sections/agent-chat/DataPanel.tsx |
Removes card confirmation controls and supports forwarded input. |
app/src/sections/agent-chat/dataCardParts.tsx |
Removes card-level confirmation controls and ellipsis usage. |
app/src/sections/agent-chat/DataCard.tsx |
Removes the confirmation footer and formula/code send offer. |
app/src/sections/agent-chat/Composer.tsx |
Implements the unified CTA, send behavior, carry flow, and confirmation errors. Moderate (1 vote): conditionally retain start-over help only when applicable; moderate (1 vote): include the all-mode instruction in the screen-reader description. |
app/src/sections/agent-chat/ChatThread.tsx |
Adds loaders to in-progress assistant messages. |
app/src/sections/agent-chat/ChatThread.test.tsx |
Updates pending-message and confirmation-error assertions. |
app/src/sections/agent-chat/BoxWorking.tsx |
Uses the shared loader for parsing and file-read overlays. |
app/src/sections/agent-chat/BoxWorking.test.tsx |
Tests loader rendering and elapsed-time text. |
app/src/hooks/useAgentSession.ts |
Chains confirmation with plotting and adds parse outcomes and forwarding. |
app/src/hooks/useAgentSession.test.ts |
Tests confirmation chaining, forwarding, parse results, and turn endings. |
app/src/hooks/useAgentSession.save.test.ts |
Adjusts save-flow parse typing for the new return value. |
app/src/components/LoaderSpinner.tsx |
Adds inline sizing, theme-aware colors, and reduced-motion behavior. Nit (1 vote): update .design-sync/config.json so the published LoaderSpinner type includes inline. |
app/src/components/LoaderSpinner.test.tsx |
Tests loader geometry, animation isolation, and reduced-motion behavior. |
app/scripts/agent-bff-mock.mjs |
Models updated classification, forwarding, and delayed session opening. |
agents/evals/scope.evalset.json |
Adds English and German plot-type scope cases. |
agents/evals/fixtures/prep/en-plot-type-violin.json |
Adds an English violin-plot preparation case. |
agents/evals/fixtures/prep/en-plot-type-suggest.json |
Adds an English plot-suggestion preparation case. |
agents/evals/fixtures/prep/de-plot-type-vorschlag.json |
Adds a German plot-suggestion preparation case. |
agents/evals/fixtures/prep/de-plot-type-violin.json |
Adds a German violin-plot preparation case. |
agents/anyplot/settings.py |
Updates confirmation terminology in settings documentation. |
agents/anyplot/session_state.py |
Updates confirmation-route documentation. |
agents/anyplot/prompts/scope_judge.md |
Adds a plot-type scope example. |
agents/anyplot/prompts/root.md |
Adds plot-type guidance and updates composer instructions. |
agents/anyplot/plugins/shipped_reply.py |
Retains the legacy confirmation scrubber. Moderate (1 vote): extend the shipped-reply guard to handle .create_plot() wording while preserving legitimate post-data-change behavior, with coverage. |
agents/anyplot/data/bounds.py |
Updates confirmation terminology in bounds documentation. |
agents/anyplot/agent.py |
Updates the session context to describe .create_plot(). |
.design-sync/previews/LoaderSpinner.tsx |
Updates the loader preview description. |
Review effort: Lite
MarkusNeusinger
added a commit
that referenced
this pull request
Nov 4, 2026
The one conflict was the prep fixture count in test_prep.py: 71 cases on the old main, plus the four plot-type cases of #12557 and the three date_number cases of this branch, is 78. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
8 tasks done
MarkusNeusinger
added a commit
that referenced
this pull request
Nov 5, 2026
…#12559) ## Summary Package E of the portfolio framing, plus B-03. It shows what the human behind anyplot does, in the owner's own wording, and backs every claim with a link to code or docs. - **README.** A "How I build anyplot" section on the first screen (the owner's approved text, verbatim). "Engineering highlights" lists seven decisions, each with its reason and the code that carries it out. "Architecture" has two Mermaid diagrams: the services, and the pipeline. - **Docs page `docs/concepts/ai-workflow.md`, "How anyplot is built with AI agents".** It covers: - who does what; - what the owner brings, built on facts confirmed from his CV; - the path of a change; - how a repeated mistake becomes a rule, with three dated cases from `.claude/guardrails.md`; - the #12557 timeline; - the limits. It's registered in all three parts of `docs/index.md`, and the README links it. - **Site.** The About page gets one short section each on the review, "Use with my data" and MCP, linked to `/stats`, `/plots` and `/mcp`. The bot HTML of `/about` (`_ABOUT_BOT_BODY`) carries the same sections. The landing page's plot of the day gets `.use_my_data()` beside `github.open()`. It shows only when `useAgentEligibility` names a lane for its pair, with one request per view. The chat records the new source `agent_open{source: plot_of_the_day}`: one line in `OPEN_SOURCES`, documented in `plausible.md` and in the design doc's entry points. ## Plan The draft the owner reviewed lives outside the repository. Where the build departs from it: - **Two diagrams instead of one.** On GitHub the single diagram came out 1,674 px tall in a frame capped at 1,383 px, which cut off `anyplot-renderer`. GitHub's Mermaid ignores a subgraph's direction once its nodes link outside it, and it wraps long labels. The services and the pipeline share only the storage, so each gets its own diagram. Both now fit their frames at scale 1 with 16 px text, measured on the pushed branch. - **The edge `api -- … --> chat` targets the subgraph on purpose.** An edge to the node `agents` would make GitHub ignore the subgraph's left-to-right direction, and that inherited direction is what stacked the boxes into the tall, clipped layout. A pre-PR check noted that some older Mermaid renderers reject edges to a subgraph. GitHub renders it, and the README is read there. - **"the stats page shows the score distribution per library".** The draft said "every score is on the stats page", but the page shows the average, the distribution per library and the top 8, not every score. - **869 merged pull requests**, re-counted on 2026-11-04 (`gh pr list --state merged --author MarkusNeusinger`): 866 into `main` and 3 into other branches. The approved text said 864. - **`plot_of_the_day` is a new source** rather than a reuse of `landing`, which is documented as the 2026-11-02 hero line. - **On a phone, the plot of the day's two actions take their own line** below the filename, right-aligned, so the filename keeps its width. From 600 px up, and whenever the entry is absent, the header stays on one line as before. ## Texts to read The README's "How I build anyplot" section is the owner's approved text, verbatim, with 869 in place of 864. Everything else this PR adds that a visitor or reader sees follows. **About page (`AboutPage.tsx`, house style)** - `❯ review`: every implementation is scored before it ships. automated checks come first: the code runs, the image isn't blank, the canvas has its size. then an ai reviewer scores the render on a 100-point rubric of 24 criteria — visual quality, design, spec compliance, data quality, code quality, and library mastery — blind to any earlier score. a first review needs 90; below that, ai repairs the defects the review named and the plot is reviewed again, up to four times. the [stats page] shows the score distribution per library. - `❯ use with my data`: experimental, open to everyone since 2 november 2026. [pick any plot], choose `.use_my_data()`, and paste your data. an agent adapts that plot's code to your columns, renders it in a sandbox with no network access, reviews the render against the catalogue's rubric, repairs it once if needed, and hands back the image and the code. your chat and data live only in server memory and are deleted after an hour idle, unless you send feedback. - `❯ mcp`: the same catalogue, served to ai coding assistants over the model context protocol. six tools search specs by tag, return a spec with its implementations, and fetch one library's code, so your assistant starts from code that already rendered and passed review. setup on the [mcp page]. - `❯ origin`, changed: built in Visp, a small town in the Swiss Alps, by a physicist and data analyst looking for one place to find the right plot for the data in front of him — and a way to explore what each library does best. (It said "by a data scientist". The new wording matches the README's "physicist" and the style guide's "data analyst" in §3.8. The bot HTML has no origin section, so there's nothing to mirror.) **Bot HTML of `/about` (`api/routers/seo.py`)**: the same three new paragraphs in sentence case, under `<h2>Review</h2>`, `<h2>Use with my data</h2>` and `<h2>MCP server</h2>`. **Plot of the day (`PlotOfTheDayTerminal.tsx`)**: the visible label `.use_my_data()` and the accessible name `Use with my data` (the existing `USE_MY_DATA_LABEL`). **README "Engineering highlights" and "Architecture"; `docs/concepts/ai-workflow.md`**: as in the draft the owner reviewed, with the deltas listed under Plan. "What I bring" uses only the CV facts the owner confirmed: - an apprenticeship as an energy electronics technician; - a physics Diplom from Heidelberg University (2012) and its thesis; - data analyst, then senior data analyst, in steering system development since 2013; - defichain-data (2022 to 2024), built and run largely without AI. It names no employer and claims no security experience. ## Test plan - [x] App: `yarn lint`, `fm:check`, `type-check`, `test` (1,654 passed), `build`. - [x] Backend: `ruff check .`, `ruff format --check .`, `mypy api core`, and `pytest tests/unit tests/integration -n auto` (26,834 passed, 176 skipped). - [x] `uv run python -m tools.changelog check --base origin/main` - [x] Browser, headless playwright-core on a local dev server against the public API, with the chat build flag on. `/agent/lane` and `/agent/eligibility` were mocked, and every `/agent/sessions` request was aborted, so no session opened. - At 1440 and 390 px, light and dark: the plot of the day's link is 12 px MonoLisa, the same colour as `github.open()`, with no page overflow. The click opens `/agent?spec=…&library=…&language=…&source=plot_of_the_day`. The About sections render with their three router links. - Console: only the known font-CORS noise from a localhost origin. No 4xx or 5xx. - [x] The plot of the day's header, measured with the entry shown: - 390 px: the actions on their own line, with 274 of the filename's 307 px visible (before, it shrank to `plot…`); - 360 px: 244 px visible; - 600 px and 1440 px: one line; - without the entry at 390 px: one line, as before. - [x] GitHub rendering on the pushed branch: the headings are in order, the `#engineering-highlights` anchor resolves, and both Mermaid frames fit (899 and 712 px, scale 1, 16 px text). - [x] Unit tests: - the plot of the day shows no entry without a lane; on the public lane it links through the router, on the admin lane with a full page load; - the About page's three links go through the router; - the bot page carries the three sections and their links. - [x] Pre-PR Gemini 3.8 Flash check on `10b83612`: nothing blocking. Its one low finding, the subgraph edge target, is kept on purpose (see Plan). https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8 --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
PR α of "4b: one chat field for everything". The agent chat's composer holds the page's one call to action, and that action ends step 1 and starts the plot in one click.
.confirm()is gone. From the moment data is stored,.create_plot()is the filled button at the composer's right end. Its click confirms the data and its bindings as the card shows them, then starts the plot. When the box holds text, the text goes as the plot's request. A refused confirmation starts nothing and shows under the composer..plot_all()(admin,allpressed) and the carry offer after a library switch confirm the same way..send()shows only while the box holds text, and Enter never confirms.422 formulaor422 code, the page sends the text to the chat as the user's message and empties the data box, as it already did for422 data_request. Code over 2,000 characters stays in the box with a line that asks to shorten it.LoaderSpinnergets an inline size and replaces the pulsing ellipsis in four places: the session opening, the thread's in-progress line, a plot run's progress, and the data box's overlay. Each wait has one moving indicator. The loader, in the plots gallery too, drops the ochre: a gray resting pair and a runner that turns green.root.md, the session block, the drift tests, the BFF mock, the style guide,plausible.mdandagent-network.mdnow say.create_plot()where they said.confirm(). The confirm route and the server'sdata_unconfirmedgate are unchanged.Plan
Plan v2 of "4b: one chat field for everything", PR α (steps α0 to α7). The plan lives outside the repository; where this PR departs from it is listed below.
Deviations from plan v2
.confirm()button. From the moment data is stored,.create_plot()is the composer's one filled CTA, and its click confirms the data and bindings as shown, then starts the plot..plot_all()and the carry offer chain the same confirmation..create_plot()is filled whenever the data is unconfirmed, also when the library already has a plot (the click makes a new version), and while the confirmed data has no plot yet. Once the confirmed data has a plot, the quiet.start_over()takes its place..send()renders only while the box holds text. Enter and.send()never confirm..create_plot()or.show()) is dropped, since there is no separate confirm to hand off from. A keyboard press on a composer CTA moves the focus into the message box, so a second press never stops the run.! check againafter a lost confirmation.allpressed, step 1's line names.plot_all()instead of.create_plot().agent_data_confirmednow fires inside the.create_plot()click (plausible.md)..confirm()as a backstop, and is not moved to.create_plot(). After a turn that shipped a plot and then changed the bindings or the data,.create_plot()is the right next step, and only the language-dependent claim around the name tells the two apart.Decisions where the CTA was not obvious
.send()only, and only while the box holds text.create_plot();.send()before it with text.create_plot(): an empty box makes a new version from the catalogue code with the remembered decisions; text goes as a turn after the confirm.create_plot().start_over(),.send()with text.stop(), and beside it the filled CTA (disabled) where one was showing; only.stop()when the data is confirmed and has a plot.create_plot()(ignores the box text), then the filled carry, which takes the textallpressed, step 1.plot_all(); the message box stays usableallpressed, step 2.plot_all(); the message box waitsThe switch the user asks for in the chat carries the plot over by itself only on confirmed data, and never confirms by itself. On unconfirmed data, the composer offers the carry, and that click confirms.
Texts to read
Every user-facing string this PR adds or changes, verbatim.
Step line above the composer (step 1; the CTA's description)
# step 1 — check the preview and the bindings above, then .create_plot()# step 1 — pick a column for y, then .create_plot()(the open roles, comma-separated)# step 1 again — the data changed · check it above, then .create_plot()# step 1 again — bindings changed · check them above, then .create_plot()# step 1 again — not confirmed on the server · check the data, then .create_plot()allpressed, the same lines end in.plot_all()instead of.create_plot(), for example# step 1 — check the preview and the bindings above, then .plot_all().# step 1 — decide on the table first: .use() or .discard(), and the step 2 lines.Message box placeholders
ask about your data, or say what the first plot should do(step 1, the library has no plot)ask about your data, or say what the next plot should do(step 1, the library has a plot)ask for example data, a curve, or a fix to your data,optional: what the first plot should do differently,ask for a change, e.g. log scale on y,all libraries: the table goes as it is · turn all off to chatBusy labels (while the confirmation runs)
.create_plot() ….plot_all() …<the carry text> …, for exampleplot in seaborn with your 2 decisions ….stop() …Pinned data header
! check again · N rows · M columns · version V(was! confirm again)# step 1 again: check the data, then .create_plot() below# a change here takes the data back to step 1, where you check it and .create_plot() againThread notices after a lost confirmation
bindings changed · check them, then .create_plot()the data changed · check it, then .create_plot()not confirmed on the server · check the data, then .create_plot()Other texts
the data is not confirmed: check the preview and the bindings, then .create_plot().plot_all()refused for unconfirmed data:the data is not confirmed any more; check it, then .plot_all() againpaste or drop → check the preview and the bindings → .create_plot()# bindings wait while the data is parsedand# bindings wait while the assistant works(was# bindings and .confirm() wait while …, with a pulsing ellipsis)step 1: check the data, then create the plotlooks like code that makes data: shorten it to 2,000 characters, keeping only the part that computes the data, and the chat runs itlooks like a formula: shorten it to 2,000 characters, and the chat computes the table from itasks for data: shorten it to 2,000 characters, and the chat makes the data.send()offer for a formula or code (looks like a formula: send it to the chat? …,looks like code that makes data: send it to the chat? …) and the noticeconfirm the data first · .confirm() sits at the foot of the data card.Loader-related lines (the words stay; the pulsing ellipsis gives way to the loader)
opening a session(wasopening a session…)summarizing your data,making example data,working on your message,stopping# parsing your data,# reading "<file>" on the server(and# reading the sheet <sheet> of "<file>" on the server), with· N sfrom the second second on# making example databelow the composer is gone; the thread's in-progress line says it once.Assistant's prompt (
agents/anyplot/prompts/root.md), the new paragraph in "What you help with"Session block (
agents/anyplot/agent.py)- Data: not confirmed yet; the user's .create_plot() beside the message box confirms it as shown and starts the plotScope judge (
agents/anyplot/prompts/scope_judge.md), the new example| Would a box plot suit my data better than this one? | in_scope |The
.confirm()sentences ofroot.mdnow name.create_plot()beside the message box, "which takes the data and the bindings as shown"; the held-request wording "the next.create_plot()draws it" stays.Test plan
ruff check,ruff format --check,mypy api core agents,pytest tests/unit tests/integration -n auto(26,800 passed, 209 skipped). After the plot-type commit:tests/unit/agentsagain, with the scope judge's example cap raised from 20 to 21.yarn lint,fm:check,type-check,test(1,650 passed),build.uv run python -m tools.changelog check --base origin/main.create_plot()(14 px, ink background) and the step line; text in the box brings.send()before it; one click sends one confirm request, the pinned header appears and the run starts with.stop()beside the disabled.create_plot(). Console: only font CORS noise from the127.0.0.1origin.euthrough ADC, in process (nothing went to api.anyplot.ai), $0.081 in total:--tags plot_type,other_library_contrast: 8 of 8 cases with the expected verdict,plot_type4 of 4in_scope, $0.005.--tags plot_type(scope judge, then the root): 7 runs of the 4 phrasings (2 English, 2 German, one of them "mach daraus einen Violin-Plot"), every turn without a data call and no reply closing with a question, $0.076. Runs 3 and 4 showed a promise that the catalogue has a violin plot and a closing offer; the prompt was tightened for both, and the two runs after that were clean.app/; each turns at least one page test red.startPlotstartPlotignores the resulthandleSendconfirms.send()absent with an empty box.create_plot()filled again after a change, with a plotctaPlot = dataset && !hasPlot.plot_all()chains the confirmhandlePlotAll.plot_all()panelhandlePlotAllignores the result.plot_all()withallpressedNot covered by a loop: the live public lane (no session opened there), and the BFF mock's new reply texts on
:8010until it restarts.Known gap, admin only: with
allpressed, the thread's notices after a lost confirmation (bindings changed · check them, then .create_plot()and the other two) still name.create_plot(), which that mode replaces with.plot_all(). The notice is written by the session reducer, which does not know whetherallis pressed.https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8