Repository navigation
docs(legal): cut the legal page by half, current state only - #12538
Conversation
The privacy policy drops from 1,546 to 762 visible words and the page from 1,805 to 983. Each fact is said once, in short fact tables: what is processed, where it runs, how long each store keeps it, the three legal bases, and the data assistant's terms. The notes on log entries written before the move to europe-west4 and on older unsalted feedback hashes are gone; both conditions ended on 2026-11-02. The crawler body mirrors the same facts in 344 words instead of 370, now with all three legal bases, the rights, the telemetry retention and the no-training line. Tests pin each store's retention by table row and assert that the history notes stay gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
- The crawler body's assistant bullet names the triage row's retention next to the 180-day files, and says the input is processed under Google Cloud's terms rather than "never used for training". - "# include data.csv" stays on one line through a nowrap span instead of invisible non-breaking spaces in the source. - The site-wide facts move into the site-wide bot-page test. 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
There was a problem hiding this comment.
🟡 Changes recommended
The change still contains critical privacy misstatements about retained unsalted hashes and incomplete crawler disclosures, alongside an accessibility issue in the new fact tables.
5 open issues
Restore the legacy feedback-hash caveat in the crawler notice · New Keep the legacy-hash caveat until cleanup is verified · New Keep the legacy-hash disclosure until migration is deployed · New Include stored feedback fields in the crawler notice · New Use row headers in fact tables for screen readers · New
What changed in this PR
This pull request shortens the public legal page and updates the crawler-facing legal notice to reflect privacy, retention, legal-basis, assistant, and transparency information.
Changes:
- Replaces lengthy privacy-policy prose with reusable fact tables.
- Revises the SEO legal body and related frontend/API tests.
- Adds retention, privacy, assistant, and legal-basis assertions.
- Adds a changelog entry documenting the reduction.
The review identified unresolved privacy-disclosure accuracy, crawler-parity, retention, and table-accessibility concerns.
| File | Summary |
|---|---|
tests/unit/api/test_routers.py |
Updates crawler-page assertions for revised wording, privacy facts, retention, and removed historical disclosures. |
changelog.d/legal-page-short.md |
Documents the legal-page reduction and crawler-content changes. |
app/src/pages/LegalPage.tsx |
Introduces FactTable and condenses legal, privacy, assistant, and transparency content. |
app/src/pages/LegalPage.test.tsx |
Updates assertions for retention, privacy, legal bases, assistant disclosures, and storage details. |
api/routers/seo.py |
Rewrites the crawler-facing legal body with the shortened privacy and assistant disclosures. |
Review effort: Lite
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
…ot page - Each fact table's label cell is a row header (th scope="row"), so a screen reader announces "feedback IP hash, ten minutes" instead of an unlabeled value. - The crawler page's feedback bullet names the stored fields (message, reaction, optional contact, page, window size, user-agent, random id), as the human page does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
…sh notes - Transparency (owner's decisions): eight grouped stack rows, each claim checked against the repo; only the services the repo pins to europe-west4 carry the region. The cost table shows October 2026 as paid after commitments and credits (about $60 a month), with the note on what it leaves out. The bot page mirrors one sentence. - A fresh load of /legal#assistant scrolls to the section once the lazy route renders; a 56 px scroll margin keeps every anchored section below the sticky masthead (42 to 44 px). - The fact tables style the label with :first-child: since the label is a <th>, :first-of-type also matched the value cell and split the table in half. - The comments and docs that promised a migration for the legacy unsalted feedback IP hashes say they were cleared once by hand on 2026-11-02 after a snapshot; the repository test keeps its behaviour. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
There was a problem hiding this comment.
🟡 Changes recommended
Several active findings remain around inaccurate retention claims, incomplete crawler disclosures, and the legal page describing a publicly available assistant that is disabled in production.
4 open issues
5 resolved since last review
Review effort: Lite
…rest - The feedback IP hash is cleared by the next feedback after its ten minutes (the router calls the cleanup only inside POST /feedback), and its salt dies with the server instance; the page and the crawler body say that instead of a flat "ten minutes". - The crawler body adds the facts the human page states and it lacked: Cloudflare's use of the IP address, no accounts and no name or email unless typed, the GitHub username credit, and that browsing never reaches a model. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8
## Summary - **Notice:** the public experimental notice is one line, `# experimental AI · results can be wrong · stored only if you send feedback`, and its ⓘ button (`About the assistant`) opens a popover with which AI and where, what not to paste, the daily limits and the link to `/legal#assistant` (`ExperimentalNotice.tsx`, texts in `lane.ts`). - **Data card:** the header is `# your data ⓘ`, and the ⓘ opens the how-to (formats, steps, a fix in plain words, changes afterwards). The long label, its hint line and the how-to paragraph under the card are gone. The data box and the composer name example data, a curve and a fix in short placeholders, and typing `example data` or a curve still works as before. Both ⓘ popovers share `InfoPopover.tsx`. - **Result card:** the image, its toolbar and the theme switch show as before. The rounds grid and the "what changed" list sit behind one status line at the end of the theme row (`second try · 2 changes ⓘ`). The code sits behind `.show_code()` / `.hide_code()`, while `.copy()` and the downloads stay in view. Each card keeps its choice for the session, also across a collapse of the whole card, and a new version's card starts folded. Residual notes stay in view. - **Feedback and save:** quick feedback shows only the four reactions until one is picked, which unfolds the message box, the data box, the public consent line and `.send()`. On the admin lane, `.save()` and its `# include data.csv` box leave the header row for a "⋯" menu (`SessionMenu.tsx`). The public lane has no `.save()` and no menu (owner decision of 2026-11-02): a visitor cannot resume a saved session, and the feedback form covers "something went wrong". - **Missing parameters:** the notice now names the plot page's `.use_my_data()` (#12539) instead of `.adapt()`. - **Wider column:** the chat column grows with the window up to 1360 px (`COLUMN_MAX_WIDTH`, 960 before). The data card, the plot cards (bigger images) and the code use the full width. The thread's replies, turns and notices, and a card's text lists, keep a readable line of at most about 80 characters (`CHAT_TEXT_MAX = '80ch'` in `styles.ts`); user turns stay right-aligned. Below about 1100 px the window sets the width, as before. Both lanes share the layout. Measured at 1920 px with the BFF mock: before, column 960 and card 918 px; after, column 1360, card 1318 and reply 718 px. - **Root prompt (`agents/anyplot/prompts/root.md`, "The page's controls"):** the list now names `.show_code()` and `.hide_code()` for the code, `.expand()` and `.collapse()` for the whole card, the status line, and `.cancel()` in the feedback. It no longer names `.save()`: the prompt has no notion of the lane, and the public page has no `.save()`. This is a wording-only prompt change, so it got no harness or eval run. `test_prompt_controls.py` and all of `tests/unit/agents` pass (19,962 tests). ## Decisions (decided and documented) - **Popover, not tooltip,** for the ⓘ buttons: it opens on a tap as on a click, holds a link, traps focus until Escape or a click outside, and returns the focus to the button. The data card's ⓘ sits beside its `<label>`, not inside it, so it never joins the data box's accessible name. - **Collapsed layout:** the tries moved from the card header into the status line, so `ok` is not said twice. The header keeps `v2 · library · ● status` for collapsed earlier versions. A run with neither rounds nor changes gets a plain status line without a toggle. `.copy()` became a text button in the code row, because the code block it used to sit on is folded now. The highlighter chunk loads only when the code opens. - **`.save()` goes into a "⋯" menu on the admin lane, not the feedback area.** A save downloads the session to the user's computer and stores nothing, while feedback stores a case for the operator; one data box for both would blur what goes where. The menu stays mounted, so the box keeps its choice; a successful save closes it, and a failure keeps it open with its line, which goes when the menu closes. The admin lane keeps its ticked default (checked in the browser on `/debug/agent`). On the public lane the page renders neither the menu nor `.save()`. - **Fresh feedback form gets `.cancel()`,** which folds it back to the reactions and moves the focus to the first one; without it a mis-click could not be undone. - **Placeholders wrap at phone width** (`::placeholder` with `pre-wrap`), while a pasted table keeps its lines. ## Finding for the API (not in this PR) The public BFF route `GET /agent/session/bundle` still exists, but no page calls it now. Removing it is an API change for another PR; `docs/reference/api.md` says so for now. ## Texts to read New or changed user-facing texts: - Notice line: `# experimental AI · results can be wrong · stored only if you send feedback` - ⓘ button name: `About the assistant` - Popover heading: `# about the assistant` - Popover lines: `Claude by Anthropic, through Google Cloud Vertex AI in the EU` · `don't paste data you may not share` · `daily limits apply` · link `how the assistant works` (unchanged, now inside the popover) - Removed from the notice (the legal page covers them): `an AI does the work here: … reads what you paste and writes the plot and its code` and `nobody checks them by hand`. Say if either should come back into the popover. - Data card header: was `# your data — paste or drop CSV, TSV, JSON, plain text, Excel, ODS, Parquet, Feather or NumPy`, now `# your data` with an ⓘ (name `How the data card works`) - Data card how-to popover, heading `# how it works`, lines: - `formats: CSV, TSV, JSON, plain text, Excel, ODS, Parquet, Feather or NumPy` - `paste or drop → check the preview and the bindings → .confirm() → .create_plot()` - `the assistant fixes your data in plain words: "sum per month"` - `afterwards, ask for changes: "log scale on y", "label the outliers"` - Removed: the hint `or type "example data" here, or ask for a curve in the chat: y = sin(x), x from -pi to pi`, and the paragraph under the card (`paste your data above, or ask here for example data or a curve … it sums your data up here … "read Anteil as numbers" or "sum per month". check the preview and the bindings, confirm, then .create_plot(), with a note first if the plot should do something differently. afterwards, ask for changes to the plot: "log scale on y", "dark version", "label the outliers".`). The paragraph's "it sums your data up here", "read Anteil as numbers", "with a note first if the plot should do something differently" and "dark version" are not carried over. - Data box placeholder: was the sample `month,revenue / 2026-01,120 / 2026-02,135`, now three lines: `paste a table or drop a file (csv, excel, json …)` / `or type: example data` / `or ask for a curve: y = sin(x), x from -pi to pi` - Composer placeholder, step 1: was `ask for example data or a curve: "y = sin(x), x from -pi to pi"` (no data) and `ask about your data, or for a fix: "sum per month"` (data), now `ask for example data, a curve, or a fix to your data` for both - Composer placeholder after a plot: was `ask about this plot, or ask for a change`, now `ask for a change, e.g. log scale on y` - Missing-parameters notice: was `open this page from a plot's .adapt() button: it needs a spec and a library, such as scatter-basic in matplotlib.`, now `open it from a plot's .use_my_data(), or start with scatter-basic in matplotlib.` - Card status line: `<tries> · <n> change(s)` with ⓘ, for example `second try · 2 changes`; screen-reader name `Run details: …` - Code toggle: `.show_code()` / `.hide_code()` (names `Show code` / `Hide code`); copy button `.copy()` / `.copied` - Feedback: `.cancel()` on a fresh form (name `Cancel the feedback on version N`) - Admin lane only: menu button `⋯` (name `More actions`, tooltip `more · .save() the session as a json file`); menu heading `# save the session as a json file` (the old `.save()` tooltip's words). Public lane: `.save()` and its `# include data.csv` box are gone. - Root prompt, "The page's controls" (read by the model, not shown to users): - page header: the clause `` `.save()`, which downloads the session `` is removed; - result card: `` `.expand()` and `.collapse()` for the code `` becomes `` the status line next to the light and dark switch (the tries and the number of changes), which opens the rounds and the list of what changed, `.show_code()` and `.hide_code()` for the code, `.expand()` and `.collapse()` for the whole card ``; - feedback: `` with `.send()` `` becomes `` a click on one of which opens the message box with `.send()` and `.cancel()` ``. ## Screenshots (BFF mock, public lane, headless playwright-core) All in `/tmp/claude-1000/-home-meake-projects-anyplot/402df83f-568f-4063-805d-572b195b8887/scratchpad/chatui/shots/`, named `<before|after>-<width>-<theme>-<n>-<what>.png`, each at 1440 and 390 in light and dark. Wide screen at 1920 px, full page with two versions, a user turn and a reply, before and after: - `/tmp/claude-1000/-home-meake-projects-anyplot/402df83f-568f-4063-805d-572b195b8887/scratchpad/chatui/shots/before-1920-light-wide.png` → `/tmp/claude-1000/-home-meake-projects-anyplot/402df83f-568f-4063-805d-572b195b8887/scratchpad/chatui/shots/after-1920-light-wide.png` - `/tmp/claude-1000/-home-meake-projects-anyplot/402df83f-568f-4063-805d-572b195b8887/scratchpad/chatui/shots/before-1920-dark-wide.png` → `/tmp/claude-1000/-home-meake-projects-anyplot/402df83f-568f-4063-805d-572b195b8887/scratchpad/chatui/shots/after-1920-dark-wide.png` - also after: `after-1440-*-wide.png`, `after-2560-*-wide.png` and `after-390-*-wide.png` in the same folder (the masthead and the pinned data header appear mid-page in these full-page captures because they are sticky) Data card, before and after: - `/tmp/claude-1000/-home-meake-projects-anyplot/402df83f-568f-4063-805d-572b195b8887/scratchpad/chatui/shots/before-1440-light-8-datacard.png` → `/tmp/claude-1000/-home-meake-projects-anyplot/402df83f-568f-4063-805d-572b195b8887/scratchpad/chatui/shots/after-1440-light-8-datacard.png` - `/tmp/claude-1000/-home-meake-projects-anyplot/402df83f-568f-4063-805d-572b195b8887/scratchpad/chatui/shots/before-390-light-8-datacard.png` → `/tmp/claude-1000/-home-meake-projects-anyplot/402df83f-568f-4063-805d-572b195b8887/scratchpad/chatui/shots/after-390-light-8-datacard.png` - `/tmp/claude-1000/-home-meake-projects-anyplot/402df83f-568f-4063-805d-572b195b8887/scratchpad/chatui/shots/before-1440-dark-8-datacard.png` → `/tmp/claude-1000/-home-meake-projects-anyplot/402df83f-568f-4063-805d-572b195b8887/scratchpad/chatui/shots/after-1440-dark-8-datacard.png` - `/tmp/claude-1000/-home-meake-projects-anyplot/402df83f-568f-4063-805d-572b195b8887/scratchpad/chatui/shots/before-390-dark-8-datacard.png` → `/tmp/claude-1000/-home-meake-projects-anyplot/402df83f-568f-4063-805d-572b195b8887/scratchpad/chatui/shots/after-390-dark-8-datacard.png` - How-to open: `/tmp/claude-1000/-home-meake-projects-anyplot/402df83f-568f-4063-805d-572b195b8887/scratchpad/chatui/shots/after-1440-light-9-datacard-howto.png`, `/tmp/claude-1000/-home-meake-projects-anyplot/402df83f-568f-4063-805d-572b195b8887/scratchpad/chatui/shots/after-390-dark-9-datacard-howto.png` Notice, header, card and feedback, before: - `/tmp/claude-1000/-home-meake-projects-anyplot/402df83f-568f-4063-805d-572b195b8887/scratchpad/chatui/shots/before-1440-light-1-top.png`, `/tmp/claude-1000/-home-meake-projects-anyplot/402df83f-568f-4063-805d-572b195b8887/scratchpad/chatui/shots/before-390-dark-1-top.png` - `/tmp/claude-1000/-home-meake-projects-anyplot/402df83f-568f-4063-805d-572b195b8887/scratchpad/chatui/shots/before-1440-light-4-card-folded.png` (the whole card as it was), `/tmp/claude-1000/-home-meake-projects-anyplot/402df83f-568f-4063-805d-572b195b8887/scratchpad/chatui/shots/before-390-dark-4-card-folded.png` - `/tmp/claude-1000/-home-meake-projects-anyplot/402df83f-568f-4063-805d-572b195b8887/scratchpad/chatui/shots/before-1440-light-6-feedback.png` After: - `/tmp/claude-1000/-home-meake-projects-anyplot/402df83f-568f-4063-805d-572b195b8887/scratchpad/chatui/shots/after-1440-light-1-top.png`: notice and header - `/tmp/claude-1000/-home-meake-projects-anyplot/402df83f-568f-4063-805d-572b195b8887/scratchpad/chatui/shots/after-1440-light-2-notice-open.png`, `/tmp/claude-1000/-home-meake-projects-anyplot/402df83f-568f-4063-805d-572b195b8887/scratchpad/chatui/shots/after-390-dark-2-notice-open.png`: notice popover open - `/tmp/claude-1000/-home-meake-projects-anyplot/402df83f-568f-4063-805d-572b195b8887/scratchpad/chatui/shots/after-1440-light-admin-3-menu-open.png`, `/tmp/claude-1000/-home-meake-projects-anyplot/402df83f-568f-4063-805d-572b195b8887/scratchpad/chatui/shots/after-390-dark-admin-3-menu-open.png`: the "⋯" menu on the admin lane (the public header in `after-1440-light-1-top.png` has none) - `/tmp/claude-1000/-home-meake-projects-anyplot/402df83f-568f-4063-805d-572b195b8887/scratchpad/chatui/shots/after-1440-light-4-card-folded.png`, `/tmp/claude-1000/-home-meake-projects-anyplot/402df83f-568f-4063-805d-572b195b8887/scratchpad/chatui/shots/after-390-dark-4-card-folded.png`: card folded - `/tmp/claude-1000/-home-meake-projects-anyplot/402df83f-568f-4063-805d-572b195b8887/scratchpad/chatui/shots/after-1440-light-5-card-open.png`, `/tmp/claude-1000/-home-meake-projects-anyplot/402df83f-568f-4063-805d-572b195b8887/scratchpad/chatui/shots/after-1440-dark-5-card-open.png`: card with details and code open - `/tmp/claude-1000/-home-meake-projects-anyplot/402df83f-568f-4063-805d-572b195b8887/scratchpad/chatui/shots/after-1440-light-6-feedback.png` / `/tmp/claude-1000/-home-meake-projects-anyplot/402df83f-568f-4063-805d-572b195b8887/scratchpad/chatui/shots/after-1440-light-7-feedback-open.png`, `/tmp/claude-1000/-home-meake-projects-anyplot/402df83f-568f-4063-805d-572b195b8887/scratchpad/chatui/shots/after-390-light-7-feedback-open.png`: feedback before and after a click ## Plan N/A (owner decisions of 2026-11-02, relayed by the lead) ## Test plan - [x] `yarn lint`, `yarn fm:check`, `yarn type-check`, `yarn test` (149 files, 1561 tests), `yarn build`, on the head after the merges of `origin/main` (#12539, #12538) and the width change - [x] Width: 1440, 1920 and 2560 px, light and dark, plus 390 px: no sideways scroll; the column is 1248, 1360 and 1360 px wide, and 358 px at 390 as before; the reply is capped at 718 px; the card is full width - [x] `uv run --extra test --extra agents pytest tests/unit/agents` (19,962 passed, 36 skipped) after the `root.md` change - [x] New and updated tests: `SessionMenu.test.tsx` (new); `ResultCard.test.tsx` (status line, rounds, plain line, code fold, failed code, state across a collapse, new version folded); `ResultFeedback.test.tsx` (folded form, `.cancel()` focus, consent before send); `SaveButton.test.tsx` (`onSaved`); `DataPanel.test.tsx` (one-line label, placeholder, the how-to popover with Escape and focus return); page tests for the notice popover, the menu, the save flow, the placeholders and the `.use_my_data()` notice - [x] `uv run pytest tests/unit/agents/test_prompt_controls.py` (3 passed) - [x] Browser: public lane at 1440 and 390, light and dark, against the BFF mock: notice popover, menu, card folded and open, feedback before and after a click, the data card and its how-to popover, the placeholder wrapping in the box at 390, no "⋯" menu on the public lane, no sideways scroll, no console errors from the page (the only 403 is the masthead's GitHub release lookup, rate-limited); admin lane at 1440 light and 390 dark: no notice, box ticked, save downloads and closes the menu with focus back on the button - [x] Gemini 3.8 Flash pre-PR check: 4 findings. Fixed: a stale failure line in the kept-mounted menu, and a missing Escape test. Skipped: a static name for the code toggle, which would break label-in-name for `.show_code()`; and a "missing" edit-cancel test that already exists. https://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8 --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>



Visible words on
/legal: 1,805 → 1,072 (−41 %). Privacy policy 1,546 → 775 (−50 %), legal notice 96 → 67, transparency 163 → 230 (the owner's complete stack table and cost note; 154 before that commit). Crawler page/seo-proxy/legal: 370 → 450 words, because it now carries every fact the human page states. (Counted with a headless browser: theinnerTextof#legal-notice,#privacyand#transparency, tokens that contain a letter or digit.)Summary
FactTable): what is processed, where it runs, how long it stays, the legal bases, and the data assistant._LEGAL_BOT_BODYinapi/routers/seo.pymirrors the same facts: it drops the history note and gains what it lacked (the (b) and (f) bases, the rights, the telemetry retention, the no-training line, the admin login cookie, the triage row's retention).^server logs\s*30 days$), every qualifier the old tests guarded is still pinned in the new wording, and new negative assertions keep the history notes out of both pages. Anchors#legal-notice,#privacy,#assistantand#transparencyare unchanged./legal#assistant(the chat's notice opens one in a new tab) stayed at the top of the page. The page now scrolls to the fragment once it renders, and a 56 pxscroll-margin-topkeeps every anchored section below the sticky masthead (42 to 44 px). Measured on fresh loads: every anchor lands 56 px from the top at 1440 and 390 px; before,scrollYstayed 0.core/database/models.py,api/routers/feedback.py,core/database/repositories.py,docs/concepts/agent-network.md,docs/reference/database.md, plus the docstring of the unchanged behaviour test intests/integration/test_repositories.py.Texts to read
The new human page, exactly as it renders. Tables are shown as
label: factbullets.legal notice (
#legal-notice)operator Markus Neusinger, Visp, Switzerland
contact email: admin@anyplot.ai · linkedin: markus-neusinger · x: @MarkusNeusinger · github: MarkusNeusinger
disclaimer
a personal portfolio project: plot examples generated and maintained by ai workflows, meant as a starting point for your own data. the code is provided "as is" under the MIT License; review it before production use.
other projects kurrentschrift.ink — read and write old german cursive · cite-citadel — llm-maintained, fully-cited personal wiki
privacy policy (
#privacy)controller
Markus Neusinger (see above). swiss data protection law applies, and the GDPR for visitors from the EU.
what is processed
where it runs
Google Cloud and Cloudflare process data under their data processing terms, including their safeguards for transfers outside Switzerland and the EU.
how long it stays
legal bases (GDPR)
what we do not do
only if you use the data assistant (experimental) (
#assistant)if you only browse the catalogue, none of it touches you.
your rights
you can ask what is stored about you and have it corrected, erased or restricted, object to the processing on grounds relating to your particular situation, and complain to a supervisory authority. a few lines to admin@anyplot.ai are enough, also to have a feedback case deleted early.
transparency (
#transparency)open source, down to what it costs to run.
technology stack
source code (unchanged)
the entire codebase is publicly available under the MIT License: github.com/MarkusNeusinger/anyplot
bundled third-party fonts keep their own licences — JetBrains Mono is under the SIL Open Font License 1.1.
monthly running costs (October 2026)
after three-year commitments and promotional credits; the assistant's model cost grows with use. development tools and subscriptions are not included. all costs are covered privately.
The crawler page (
_LEGAL_BOT_BODY), in fullLegal notice
Operator: Markus Neusinger (GitHub, X). Full legal notice with contact details on the interactive page.
Privacy
Data assistant (experimental)
Transparency
The whole stack — specs, pipeline, API and frontend — is open source at github.com/MarkusNeusinger/anyplot; the catalogue content is MIT-licensed. Running costs about $60 a month (October 2026).
Decisions made in this PR
anyplot_plot_votesin localStorage,app/src/utils/feedback.ts:13), which the old text left out; the line now lists what the browser keeps instead of omitting a store.POST /feedback, so the row says "cleared by the next feedback after ten minutes" and adds that the per-process salt dies with the server instance. Adding a timed cleanup would change behaviour, which is outside this PR.Plan
N/A (delegated task: shorten the legal page, keep every required fact).
Test plan
yarn lint,yarn fm:check,yarn type-check,yarn test(148 files, 1,542 tests),yarn builduv run ruff check .,ruff format --check .,uv run pytest tests/unit/api tests/unit/core(2,488 passed), theclear_ip_hashesrepository test,mypy api core agents/legal#assistant,#privacy,#transparency,#legal-noticeat 1440 and 390 px: each lands 56 px from the top, below the 42 to 44 px mastheaduv run python -m tools.changelog check --base origin/main/verify-frontendon/legalwith a headless playwright-core probe: 1440×900 and 390×844, light and dark; no horizontal overflow, no console errors besides the local environment's (font bucket CORS allows only localhost:3000; no local API); screenshots readhttps://claude.ai/code/session_01H7HSkGyea1k2v293XiAMo8