Changelog
The beta at beta.plune.ai ships continuously, so entries are dated, not
versioned: an entry here is on the beta. Only what you can see or call is listed — screens,
endpoints, rules. The road in — @plune-ai/cli and the @plune-ai/playwright reporter — is
versioned and keeps its own changelog.
2026-10-05 — Your language and theme follow your account, and a save no longer overwrites a newer one
Section titled “2026-10-05 — Your language and theme follow your account, and a save no longer overwrites a newer one”- The language and the theme are kept with your account. Choose them on Account → Profile and
signing in on another browser opens in them. If your account holds none yet, the choice your browser
already has is saved to it at your next sign-in; with no choice anywhere, Plune speaks your
browser’s language if it is English or Українська (English otherwise), in the dark theme.
GET /v1/mereturnslocale(enoruk) andtheme(darkorlight), andPATCH /v1/metakes them next toname— any of the three, at least one;nulltakes a choice back. Account. - A save no longer overwrites a newer one. When two people save the same case within moments,
the second save is now made on top of what the first wrote, where it used to put its older copy
back.
PATCH /v1/test-cases/{id}and a bulk action read the case again and retry, up to three times; a case that keeps changing answers409withthe case changed while it was being saved — reload and retry, and in a bulk action its id lands inrefusedwhile the other ids run. Accepting fields of a pending draft (PATCH /v1/test-cases/{id}/draft) answers409for a third reason,the case kept changing while the draft was being accepted, and the draft stays waiting. A Markdown import refuses such a block, by line, and applies the rest.
2026-09-30 — A result’s panel, JSON and text files, and a run’s commit and branch
Section titled “2026-09-30 — A result’s panel, JSON and text files, and a run’s commit and branch”- A result opens in a panel beside the list. A click on a result’s row on the run page opens its
panel — where it ran, what happened, its screenshots and its files — instead of the row that used to
open in place (since 2026-09-29).
#result-<id>in the address opens a result, so it can be shared; ↑ ↓ step between results, and the panel’s width is yours to change. Up to 1024 px it is a sheet over the list, and on a phone the whole screen. A result in a run. - A test’s JSON, logs and text files are kept with its result, and open in the panel.
POST /v1/results/{id}/filestakesapplication/jsonandtext/plain— a log istext/plain— as UTF-8 without NUL bytes, up to 512 KiB a file and 10 a result, apart from the screenshots’ 20. A text is cleaned of colour codes and credentials before it is kept, andGET /v1/files/{id}returns it astext/plain; charset=utf-8(both since 2026-09-29). In the panel a file Plune keeps says Open where it said in the CI run ↗, and opens in a window of its own — a JSON indented and coloured, a log or a text with its line numbers. JSON and text files. @plune-ai/playwright0.5.0, andplune run importin@plune-ai/cli0.17.0, send them. What a test attaches asapplication/jsonortext/plain, with abodyor apath, goes to its result — a passed test’s too — and what is left behind is counted in the run’s summary. Until a client sends them, no text file arrives. JSON and text files.- A run names the commit and the branch it ran on.
@plune-ai/playwright0.4.0, andplune run importin@plune-ai/cli0.16.0, open a run with both, read from the CI it ran on — GitHub Actions, GitLab CI or Jenkins. A result’s panel on the run page lists them under Where it ran: Branch, and Commit by its first seven characters, a row each when the run has the value. A run from an older client, from a developer’s machine or from another CI has neither. A run replayed from.plune/pending-results.jsonlkeeps them too (plune run report0.16.0). The commit and the branch. GET /v1/runs/{id}/summaryhands them out.run.branch,run.sha— the whole commit — andrun.runner, the tool that reported the run (playwright, or the report formatplune run importread; not the CI’s machine), eachnullwhen the run holds none. Live since 2026-09-29, and empty until a client sends them, which the clients above now do.
2026-09-29 — A run’s screenshots, the Analytics page, and companies
Section titled “2026-09-29 — A run’s screenshots, the Analytics page, and companies”- A Playwright run’s screenshots reach Plune with their results.
@plune-ai/playwright0.3.0, andplune run importof a Playwright JSON report in@plune-ai/cli0.15.0, send the pictures each test kept as files to its result — a passed test’s as well as a failed one’s: PNG, JPEG or WebP, up to 2 MB each and 20 a result. Screenshots. - And the run page shows them. A result with screenshots says how many beside its status, and its row opens — a passed one’s too — to Screenshots · N above the error text. A picture opens large, ← → move through the result’s pictures and Esc closes; on a phone it takes the whole screen. The run’s line now says how long the run took, and names the key the run was imported under — from CI, Run ID 36552637315, the CI run’s own id, rather than a bare number. Screenshots.
- Analytics, over every project you are in. Analytics in the bar outside a project
(
/analytics) counts over all your projects at once — or the ones you pick — for a period: the pass rate, test cases, runs, manual, automated and coverage; the cases by project and type, the latest run of each project and the results by priority; and a row per project, sortable, each leading to its runs. Date, Projects and a drawer of Filters — run labels, case tags, environment, executed by, and a suite or folder of one project — narrow it, and the address keeps the filters, so a link opens the same view. Analytics. GET /v1/analyticsanswers over every project you are in, or the ones named inprojects, for a period and filters: the pass rate, test cases by type, runs, coverage, the latest run’s statuses, results by priority, a row per project, and the values to filter by. A project you are not in answers404, the same as one that does not exist, and a token reads its own project only.- Companies, through the API. A company holds projects and the people who work on them, with
roles of its own — owner, admin, member, reader (
/v1/companies). A company role gives the right to join the company’s projects and the role you join with; a project’s data stays open to its members only.POST /v1/projects/{id}/transfermoves a personal project into a company for good, and deleting a company takes its projects to the Bin for six months.GET /v1/projectsnames each project’scompany. - Companies in the dashboard. Companies in the bar outside a project (
/companies) lists the companies you are in, with your role, their projects and people counted; New company makes one, you its owner. A company’s page has three tabs. Projects: every project of the company by name, Join as … on one you are not in, and — for its owners and admins — Move a project here, which says the move is one-way before it happens. People: invite by address with a role, change a role, remove, leave, and withdraw an invitation still waiting. Settings, for owners and admins: the name, and for the owner Delete company, confirmed by typing the company’s name. Companies. - Projects in groups, by company. Personal first — every project in no company — then each company by name, with how many people it has, your role in it and Open company →. A company where you are in none of its projects yet says so, with the way to its page; search narrows inside the groups. With no company, the page is one list as before. The Projects page.
- The company in the bar and the switcher. Inside a company’s project the bar names the company before the project — for its people a link to its page — and below 1024 px wide the section gives way to it. The project switcher groups its list the same way, and opens under the name at every width. Inside a project.
- A project holds up to 20 000 test cases, not 5 000: a suite past 5 000 tests no longer stops becoming cases halfway through. A big suite.
- The top bar keeps one line on a phone. The project’s name at its left is a name and a chevron, not a bordered button; a name longer than the room ends in «…», and the switcher’s list shows it whole. On a phone Search is an icon beside ?. On a phone.
2026-09-28 — Projects, your account, search, and a light theme
Section titled “2026-09-28 — Projects, your account, search, and a light theme”- A page of your projects. Projects (
/projects) lists every project you are in, as cards or a table — the browser remembers which — each with your role and its last run: when, and how much of it passed. Search narrows the list by name; New project makes one. The bare address opens your project straight away when you are in one, and Projects when you are in more.GET /v1/projectsgives each project itslastRun—{ at, total, passed }, ornullbefore the first run. - One bar, a help menu and an account menu. The ? holds Documentation, API reference, What’s new and Send feedback, each in a new tab; the avatar holds who is signed in, Account and Sign out. Outside a project the bar carries the Plune mark and Projects; inside, the project switcher, whose list now opens under its name. Navigation.
- Your account, outside every project. Your name, sign-in address, role, language and theme moved
from a project’s Settings → General to Account → Profile (
/account) — none of them belonged to a project. Account. - API tokens are the account’s. Account → API tokens lists every token you hold, from every
project; a token is minted for the project picked beside Generate a token, and a project you
only read is not offered. A project’s Settings → General, its Getting started and an empty runs list
link there with that project picked.
POST /v1/tokensdocuments its403(you only read the project) and404(you are not in it). CLI 0.14.3 says the same:plune loginand its messages about a missing or rejected token send you to Account → API tokens. - Ctrl K opens search on any page (⌘K on a Mac), and so does Search in the bar. Inside a project it finds the project’s test cases, runs and plans as you type, up to five of each, and your projects by name; outside a project, your projects. The arrows and Enter open a result; See all opens the list narrowed by the same letters. Navigation.
- The runs and plans lists narrow by name.
/runs?q=…and/plans?q=…show the runs or plans whose name holds the letters, say so, and link back to all of them.GET /v1/runstakesq— part of the run’s name, any case. - A result can keep its screenshots.
POST /v1/results/{id}/files?name=…takes a PNG, JPEG or WebP of up to 2 MB as the request body, at most 20 per result, while its run is open;GET /v1/files/{id}returns the file, andGET /v1/runs/{id}/resultslists a result’sfiles. A project keeps up to 1 GiB of them; past that, the oldest go first. - A light theme. Dark or Light on Account → Profile, for this browser; dark until you choose.
- A moment’s outage is not a sign-out. When the API fails to say who you are — for a few seconds while the beta updates, say — the page shows its own error with a way to try again, instead of sending you to sign in.
2026-09-24 — What failed, on the run page
Section titled “2026-09-24 — What failed, on the run page”- A failed automated result says what failed. The row shows the error’s first line (on a test
timeout, the action that was still waiting), the declared
test.steps down to the one that failed, where it failed and the names of what the runner kept as files; clicking it opens every error of the attempt in place./runs/<run>#result-<result>opens a run at one result, and each line of a case’s Result history links there. Failed automated results. failureon a result.POST /v1/runs/{id}/resultstakes an optionalfailure { headline, steps, location, artifacts, ciUrl }, andGET /v1/runs/{id}/resultsreturns it — ornull. Alocationthat is not a path from the repository root or whoselineis not a positive integer, or aciUrlthat is not http(s), refuses the batch and names the field. Over-long parts are shortened and marked, never refused: 20 steps keep the 10 outer and the 10 inner, the artifacts stop at 50. AciUrlover 2048 characters is left out.- The CI run.
meta.ciUrlonPOST /v1/runsmust be an http or https address; the summary carries it asrun.ciUrl, and the run’s header shows CI run ↗. - No credentials in the text.
errorContextis cleaned on the way in and again on the way out, so results stored before today also read without colour codes and with credential-looking values replaced by[redacted:<kind>]. On the way in, a text over 256 KB is kept as its start and end around…[truncated N bytes]…, anderrorContextMeta { redacted, truncated }says what was done then; the cleaning on the way out does not change it. - A whole failed run in one go. The results route takes up to 10 MiB a request, up from 512 KiB.
- Later the same day, after a review. A
Name: valueline whose name merely containskey,token,secret,passwordorsessionkeeps its value when the name ends inErrororException(TokenExpiredError: jwt expired), or the value is a JSON number,true,falseornullafter a quoted key ("tokenCount": 3,) — a number in a logged header still goes. In a sentence, only the runs that look like a credential are replaced. A session cookie printed as an object —{ name: 'plune_session', value: '…' }in a diff or a saved login, both values of a diff where it changed, a value in quotes — is replaced too, and every text of a storedfailureis cleaned again on the way out. A NUL in the text, or a text of several megabytes, no longer fails the batch;…[truncated N bytes]…counts only the bytes left out; and alocationwhose file is no longer a path from the repository root once cleaned is left out. On the run page,#result-<result>also moves the keyboard focus to that result, and a failure’s head stacks on a narrow screen. - Sent by
@plune-ai/playwright0.2.6 and byplune run import0.14.0 of a Playwright JSON report; 0.2.7 and 0.14.1, out the same day, carry the fixes from the review, and 0.2.8 and 0.14.2, out the same day too, those from its second pass. Older clients report as before — the text alone, which the row shows as its first line.
2026-09-18 — Comments on a case
Section titled “2026-09-18 — Comments on a case”- Comments.
GETandPOST /v1/test-cases/{id}/comments,DELETE /v1/comments/{id}— plain text with its line breaks, at most 8 192 bytes, oldest first, the author as auserId. Deleting is soft and the author’s, or an owner’s on anyone’s; a reader reads and writes nothing. Both writes are on the case’s audit feed astest_case/comment. The case page ends with Comments · N — a box and Comment for a member, Delete on your own. Cases → Comments. - With this the team programme’s first wave is complete: members and roles, projects, names, who did what, who runs what, and what people said. Next: notifications and attachments — not yet.
2026-09-18 — Who runs what
Section titled “2026-09-18 — Who runs what”- Assignee on a case.
PATCH /v1/test-cases/{id} { "assigneeId" }— a member of the project, or400 user '…' is not a member of this project;nulltakes the case back; never set on create. Leaving or being removed from a project clears the person off every case they held there. Cases → Assignee. - Filter by it.
GET /v1/test-cases?assignee=me|none|<userId>— the eighth parameter, repeatable;meis whoever asks. The filter bar’s Assignee facet: Me · Unassigned · the members. - Assign… on the selection bar.
POST /v1/test-cases/bulk { "action": { "kind": "assign", "assigneeId" } }— a stranger is refused once, before anything moves. - Prefer case assignee at launch.
assignment: "prefer-assignee"onPOST /v1/runsand onPOST /v1/plans/{id}/launchcopies the cases’ assignees intoconfiguration.assignees— a snapshot. The run page shows the person on each «To run» row and offers Mine · n / All · n; the summary carriesrun.assignees(and nowrun.createdBy). Manual runs → Who runs what.
2026-09-18 — Names, and who did what
Section titled “2026-09-18 — Names, and who did what”- Your name.
PATCH /v1/me { "name" }— Settings → General, «Your name»;GET /v1/mecarries it, and so does every row ofGET /v1/members. The avatar’s initials come from the name once there is one. Members → Names. - Who wrote it, who launched it, who marked it. A case carries
createdBy(the caller ofPOST /v1/test-casesor a duplicate), a run carriescreatedBy(the first shard that created it, on every road in), asource: manualresult carriesexecutedBy(whoever submitted or last corrected it). The case page says created by …, the run page Launched by …, a mark on «To run» by … · 2 minutes ago. Rows from before say nothing rather than guess. - Not yet (then): assignees and comments — assignees shipped the same day, above.
2026-09-18 — Projects and members
Section titled “2026-09-18 — Projects and members”- Every record belongs to a project, and every request acts in one. The tenant boundary is
membership now, not ownership: you read a case because you are a member of its project. Every
account keeps its default project, and nothing changes for a client that names none —
X-Plune-Project: <id>names another; a project you are not in answers404like an invented id.GET /v1/projectslists yours with your role;POST /v1/projectsmakes one with you as its owner;PATCH /v1/projects/{id}renames it. The dashboard shows the project at the left of the top bar on every screen, with a switcher and New project…; Settings → General renames it. Projects. - A CLI token is bound to the project it was minted in. Every request under it lands there, whatever header a client sends; the token list says which. Tokens from before write to your default project.
- Members, three roles, invitation by address.
owner · member · reader—GET/POST /v1/members,PATCH/DELETE /v1/members/{userId},DELETE /v1/invitations/{id}. An address with an account joins at once; any other gets a mail, passes the beta door on the strength of the invitation, and joins the moment it signs in. Areaderis answered403 read-only memberon every write, and the dashboard says so in a banner on every screen. The last owner cannot be demoted or removed (409). Settings → Members is the screen: the Invite form, a⋯menu per row, the pending list with Revoke. Membership changes are in the project’s audit feed. Members. - Not yet (then): display names, «created by» and «executed by» — shipped the same day, above; assignees and comments — the next waves of the same programme.
2026-09-17 — A plan as the runner’s --grep
Section titled “2026-09-17 — A plan as the runner’s --grep”- The road for CI is one command.
plune plan grep <id>(CLI 0.13.0) readsGET /v1/plans/{id}/casesand prints the plan as one--greppattern —npx playwright test --grep "$(plune plan grep <id>)",npx vitest run -t "$(…)"— so the platform decides which tests run and CI runs only those. One pattern fits Playwright, jest, mocha and vitest 4 and 5, whichever way they spell the boundary between adescribeand a title. Thecurl | jqroad the plans page used to show is retired. Test plans. - A case in the bin is never detached. The full-run rule of 2026-09-15 looked at every case with a result from the source, deleted ones included; it now looks at live cases only, as every other read does.
2026-09-17 — Manual runs
Section titled “2026-09-17 — Manual runs”- A person walks a manual or mixed run in the dashboard. While a run of kind
manualormixedis open, its page has a To run section: every expected case as a row with its steps (a toggle per step — passed, failed, skipped), a message and Passed · Failed · Skipped, where the button is the request — one result per case throughPOST /v1/runs/{id}/results, keyedmanual:<caseId>withsource: manualand the newstepsfield. A mark is corrected in place with Change —PATCH /v1/results/{id}, for a manual result of an open run only; a reporter’s result stays append-only. Finish run asks about the cases left; a closed run names itsnotRunover the results. In a mixed run the automated cases wait for the reporter below and share the counts. On Runs, New run ▾ › Manual run… starts one without a plan — all manual cases or a filtered selection, automated as manual, a title and an environment. Manual runs.
2026-09-17 — Test plans
Section titled “2026-09-17 — Test plans”- A plan is a collection of case filters.
POST /v1/planswith a title, a kind (manual,automated,mixed) and one to twenty collections, each a query string of the cases list — narrowing together inside a collection, adding up between them, an empty one is every case. Nothing is copied:GET /v1/plans/{id}/casesresolves the plan when asked, by the kind’s rule, andPOST /v1/plans/{id}/launchstarts a run of it withconfiguration.expectedset to what it collects at that moment andmeta.planIdfor provenance;GET /v1/runs?planId=lists the runs of one plan. On the dashboard: Plans in the sidebar — the list with a live count, a form with the cases screen’s filter bar per collection and a live «Matched cases», the plan’s page with Launch, and New run ▾ › From a plan… on Runs. Test plans.
2026-09-17 — Steps Database and snippets
Section titled “2026-09-17 — Steps Database and snippets”- Every step of a manual case, in one place. Each action becomes a row of the project’s Steps
Database on save, with how many live cases carry it;
GET /v1/settings/stepslists them (?q=for a prefix). Rename one —PATCH /v1/settings/steps/{id}— and every case that carries it is rewritten in one transaction, a new version and an audit entry each; merge several withPOST /v1/settings/steps/merge; add one by hand; delete only what nothing carries, or all of those at once withDELETE /v1/settings/steps?unused=true. The dashboard’s case form suggests the project’s steps as you type, and Settings → Steps is the list with rename, merge, notes and Delete unused. Steps and snippets. - Snippets. Titled Markdown inserted into a case as a copy —
/v1/snippets, with atemplateflag for the ones a new case may start from. In the form: Insert snippet ▾ (at the caret on the Markdown tab; as steps or description on the form tab) and Start from ▾ on a new case; Settings → Snippets to write them.
2026-09-17 — Case parameters
Section titled “2026-09-17 — Case parameters”- One table, a row per iteration. A case of any type carries a parameters table the way
Testomat has dynamic parameters: column names and rows,
${Name}(or{{Name}}) in the steps, description, preconditions and prompt,${Name}only in the title, every row one iteration. On the APIexamplesonPOST/PATCH /v1/test-cases(nullclears), and each result inGET /v1/test-cases/{id}/resultsnow carries theparamsit ran. In the Markdown document the<!-- example -->table is stored rather than warned about. On the dashboard: a Parameters section in the form, the table with a row per iteration on the case page, and the row a result ran as chips on its history line. The fields of a case.
2026-09-17 — Cases as Markdown
Section titled “2026-09-17 — Cases as Markdown”- The tree is a file, and the file is the tree. Every case — or one suite, or one case — as a
Markdown document in Testomat’s classical format:
<!-- suite -->/<!-- test -->blocks, the title as a heading with its @tags,## Stepswith Expected: under each step, plus Plune’s own columns (type: eval,spec:,## Preconditions,## Prompt/## Assertions). Edit it in any editor, hand it to an agent, and push it back: matched by id, a draft for a block without one, refusals by line, nothing deleted, a report before anything is written. On the API:GET /v1/test-cases/markdown(?suiteId=,?state=),GET /v1/test-cases/{id}/markdown,GET /v1/suites/{id}/markdown,POST /v1/test-cases/markdown?dryRun=true. On the dashboard: the form’s Markdown tab with a preview, Copy as Markdown on cases and nodes, Markdown ▾ › Export .md / Import .md… over the tree with the dry-run report first. Cases as Markdown.
2026-09-16 — One tree for cases and suites; the API explained, with a collection to try it; the bin
Section titled “2026-09-16 — One tree for cases and suites; the API explained, with a collection to try it; the bin”- Test cases is one tree. Folders › suites › cases on one screen, with the case open in a
panel beside it — click a name and read it there, Open for its page. The Suites section is
gone:
/suitesforwards, a link to a node (/cases?open=<id>) unfolds the path to it. The filter narrows the tree to the matches and their ancestors; the keys walk it (↑↓→←SpaceEsc); below 1024 px the case is a drawer over the tree. One selection covers cases and nodes — a suite’s checkbox takes its cases along — and the toolbar in the filter bar’s place offers Move to…, Priority, Retire, Delete, Copy IDs and Duplicate for all of them. Organizing cases → One tree. - Rows drag. Lift any row of the selection and the whole selection travels: a slot between
cases or a frame around a suite or folder says where; a folder refuses a case — and a suite a
suite — with the rule drawn before you let go, and takes no drop. Alt copies instead of
moving. On the API:
POST /v1/test-cases/bulkmovetakes aposition, andPOST /v1/test-cases/{id}/duplicatetakes an optional{ suiteId, position }— without a body the copy now lands right under its original, and the dashboard opens it with the name selected to rename. A case’s⋯gained Move up / Move down. Organizing cases → Drag and drop. - The side panel is yours to fold. It opened as icons alone on Test cases and in full
everywhere else, which the first tester read as a broken menu. Now it is full on every section
until you fold it — a click on its empty space or the
«above Sign out — and it stays the way you left it in that browser. It is also pinned to the window: a tree of three thousand rows scrolls under it, Sign out does not leave the first screen. And a long case name in a deep folder no longer pushes its tags and state onto a second line — it ends in an ellipsis, the whole name on hover. - A selection on the case list, and one action over it. Tick rows (Shift+click for a range),
and the bar over the list offers Move to…, Priority, Retire and Delete for all of them in one
dialog — which stays open with the answer: how many were done and, by name, what was refused. On
the API:
POST /v1/test-cases/bulkwith up to 200 ids and oneaction, answered200withdoneandrefused. Duplicate in the row’s⋯and on the case page (POST /v1/test-cases/{id}/duplicate) makes a draft copy without the original’s keys; an automated case is refused — its identity is the spec. Organizing cases → Many cases at once. The suite tree got the same: tick folders and suites, then Move to… or Delete for all of them, each picked root walking the single node’s road. - The bar over the content names the section — «Test cases», not
/cases. And the edit form’s Type badge says why it is a badge: the type is set once, and a test that left the code is the case’s state (detached after a complete run, then retired), not its type. - Settings → Bin. Everything deleted in the last six months — suites (one row per deletion,
with what went along), cases, runs — with the date each is gone for good, and Restore on each
row: no more asking us. A case that went with its suite comes back with the suite. On the API:
GET /v1/bin, and the three…/restoreendpoints now answer the account that owns the project. - Dates on the wire are always UTC ISO 8601. A run’s
startedAt/finishedAt, result and auditcreatedAtused to arrive in the database’s own text form; every timestamp now reads2026-09-16T08:02:13.432Z, and the reference marks each asdate-time. - Every endpoint says what it is for and who calls it, and every request body comes with an
example that the platform’s own tests send against the API before it is published — the
reference is rebuilt from the same document.
POST /v1/runsnow documents both of its bodies: the run a reporter opens (schemaVersion: 2) and the finished eval runplune syncuploads (schemaVersion: 1). - A Postman collection, built from the API’s own description —
beta-api.plune.ai/postman.json, linked from the API’s Swagger page too. Folders per area,baseUrlandtokenas variables, example bodies, saved example answers; imports into Postman, Insomnia, Bruno and Hoppscotch.
2026-09-15 — Every screen as the design draws it; a run says when it ran
Section titled “2026-09-15 — Every screen as the design draws it; a run says when it ran”-
A case whose test left the code is marked, not forgotten. When a run that declares itself complete (
configuration.full: trueon the run — the whole suite, not--grep; the CLI sets it fromPLUNE_FULL_RUN=1since 0.12.0) finishes with nothing missing, every active case that source used to report and did not this time moves todetached, with the run named as the reason. The next result for such a case brings it back to active by itself. Manual cases, other sources and retired cases are never touched. -
Delete works on any case and any suite — and is reversible. A case with results can be deleted; its results stay in the runs they belong to. Deleting a suite takes every suite and case beneath it, and the dialog says how many before you confirm. Six months to put it back (from 2026-09-16 — Settings → Bin); after that it is gone for good. The “move N cases first” refusal is gone.
-
Every screen was checked against its design frame, element by element, and moved where it differed — fourteen screens in one pass. What you will notice: the sign-in and 404 cards, the onboarding as a numbered list with a copy field per command, the runs list in the columns of the design (Run · Kind · Status · Cost · Duration · When, the verdict and the counts in one cell), the run page with its score cards, the case page with Edit as the main action, the review queue as a plain list with the item on its own page (← Previous · Next →, was → now for a disputed result), the trends page as bar charts of the last 14 runs, and Settings with its sections in a column instead of tabs. The panel, the top bar and the content column were already right.
-
A failed request says so with a word. The error block is a card with a
request failedbadge, the reason, the advice, and the actions under them — Try again and, where the advice is to write to us, Write to us as a button rather than a link at the end of the sentence. -
A run nobody named shows the minute it started —
2026-09-15 15:26in your own clock — instead of the date alone, which read the same for every run of one day. A run named by the CLI or CI keeps its name; from@plune-ai/cli0.11.0 (adapter 0.2.3) a run is named<directory> · 2026-09-15 15:26 · ciby itself, andPLUNE_RUN_TITLEstill replaces the whole name. -
The dead ends are one card. Whether the address does not exist, the page could not render, or the API could not be reached, the screen is the same card: what happened, the likely cause, and a way out — the last of those used to be a bare sentence with nothing to press.
2026-09-14 — Retire a case, bring it back, confirm it
Section titled “2026-09-14 — Retire a case, bring it back, confirm it”- The case page moves the case. The
⋯menu on a case offers exactly what the lifecycle table allows a person from where the case stands — Retire, Return to work, Confirm (an approved case out ofin_review), Activate (a draft) — through the same was → becomes dialog as “Move to…”, with an optional Why. The reason and the date show beside the state badge for as long as the case stays there; the audit log keeps every reason for good. The lifecycle of a case. - Retired, but still reported. Results for a retired case are recorded as before and the state
stays
deprecated; the case page counts the results that arrived after the retirement and dates the last one — the cue to take the test out of the code too. - The form no longer sends a state. The Status select is gone from the edit form; saving a
title or a description never moves a case again.
deprecatedreads as retired everywhere the dashboard shows a state. allowedStateson a single case.GET /v1/test-cases/:idsays where the caller may move the case from here — a session and a token get different answers (additive; the list does not carry it).
2026-09-14 — A refused token mint says so
Section titled “2026-09-14 — A refused token mint says so”- A refused mint is visible. Generating a token past a limit used to do nothing on screen — the refusal was never caught. Now it shows above the list with the server’s reason and, for a throttle, how long to wait.
- Ten live tokens per account, three seconds between mints. The ceiling was twenty and the
mint throttle five per hour — which locked out exactly the person looking around on their first
day. Volume is now bounded by the ceiling (every token listed and revocable), the throttle only
turns a burst of clicks into one mint. The list says “N of 10”; at the ceiling the button is
disabled with the way out named.
GET /v1/tokensgainslimit(additive). Retry-Aftertells the truth. Every throttled answer now says the seconds until a slot frees, not the length of the whole window — sign-in included.
2026-09-12 — Cases as a tree
Section titled “2026-09-12 — Cases as a tree”- The suite tree is a filesystem. A folder holds folders and suites, a suite holds cases —
nothing holds both, and every write that would break the rule is refused with the same sentence.
A non-empty suite becomes a folder through
POST /v1/suites/:id/convert-to-folder; its cases move into a child suite of the same name in one transaction. Organizing cases. - An automated case files itself. On every road a case is born — created, approved from the
queue, or trusted on arrival — its
specRefdecides where it lives: directories become folders, the file becomes a suite, and the suite remembers its file. - Case fields.
priority(five levels),precondition,description, andpositionamong siblings. Tags live in the title —@smokeat the end of a name is the tag;tags[]in the API is sugar that rewrites that tail. Settings → Tags lists them with counts, renames and merges. Case fields. - The filter is the URL.
GET /v1/test-cases?suiteId&tag&priority&priorityMin&type&state&q— repeats inside one dimension are OR, dimensions are AND, a folder expands to its descendants, and an unknown value answers 400 with the allowed ones spelled out. The dashboard reads the same object from the address bar, so a selection is a link. Filtering. - Screens. Cases grouped in tree order with breadcrumb headings, priority glyphs and tag chips; Move to… and Delete with a “was → becomes” dialog; suites created with a type, reordered with ↑/↓, converted in place; filters in a drawer and the tree as a drill-down below 1024 px.
2026-09-10 — The beta gets a door, and a source can be trusted
Section titled “2026-09-10 — The beta gets a door, and a source can be trusted”- Invite-only. Sign-up is limited to an allowlist of mailboxes (an address and its
+aliasforms are one mailbox). A sign-in request for an address that is neither invited nor already registered gets the same 202 as any other and no email — the answer is not a way to ask whether an address is on the list; the sign-in screen says the beta is invite-only and links to the waitlist.GET /healthreportsdoor: invite-only | open. - Trust a source once, instead of a test at a time. Settings → Trusted sources (
PATCH /v1/settings {trustedDiscoverySources: ["junit"]}): tests a listed runner finds become cases on arrival — the verdict iscreated— instead of waiting in the review queue; everything created this way is marked as trusted in the audit log, and the setting has a writer, a date and a way back.--createon the reporter is still the offer; what becomes of it is decided here. - An approved case can be deleted. A case born from the review queue used to refuse deletion because the decision that created it pointed at it. Now the case goes and the decision stays — verdict, category, note and time untouched, with an audit row saying the case is gone. Results still refuse: they are evidence of past runs. The delete button asks first.
2026-09-09 — The first run tells the truth; a run can be deleted
Section titled “2026-09-09 — The first run tells the truth; a run can be deleted”- A first run no longer says nothing happened. Results from a suite that has no cases yet do
not become results — they wait in the review queue as discovered tests. The run page used to show
0 of 0and “recorded no results”; now it says how many tests are waiting and links to the queue. Onlypendingentries count: an approved one has become a case and is no longer waiting for you. - A run can be deleted, and put back.
DELETE /v1/runs/:id— the account owner, no confirmation, recursively (results, disputes, discovered tests); a second call is 404.POST /v1/runs/:id/restoreexists for the application owner only. A run was the one thing the platform could create and not remove. - Settings shows what this account is — the address and the role — above the tokens, and
GET /v1/mereturnsrolenext to the address. - The token screen names both homes. A CLI token goes into
plune loginon the generating road and into a CI environment variable on the free road; the screen used to mention only the first. - One voice. The Ukrainian interface addresses the reader one way throughout; 34 strings changed. English is unaffected.
2026-09-08 — A run has a name; the queue learns discovered tests
Section titled “2026-09-08 — A run has a name; the queue learns discovered tests”- Runs carry
title,environmentandlabels. The reporter variablesPLUNE_RUN_TITLE,PLUNE_ENVandPLUNE_LABELSnow land somewhere: the title heads the run page, the other two show as tags.GET /v1/runs?environment=splits the histories of a suite run against staging and against production.PLUNE_GROUPis still refused — run groups wait for their own feature. - Discovered tests are a third kind of queue entry. A reporter that ran a test the platform has
no case for offers it through
POST /v1/review-items/discovered; the answer is 200 with a verdict per test —queued,duplicate,refused,known, orcreatedwhen the source is trusted. Approving one creates the case and attaches every key, so from the next run the test resolves instead of being found again. A refusal outlives the run that provoked it. The review queue. - A proposal shows where it came from. Source (page, operation, or requirement) and technique on every queue row; rationale and the cost of the proposing run when you open it. An approved case shows the same on its own page — for cases created before this change too.
- Breaking.
techniqueon a proposal is limited to 32 characters; a longer name is refused where it used to pass. - The runs table gives its width to the column with something to read; an environment tag no longer wraps inside itself.
2026-09-07 — The queue reads in pages and remembers why
Section titled “2026-09-07 — The queue reads in pages and remembers why”- Breaking.
GET /v1/review-itemsis paginated. The answer is{items, nextCursor},limit1–200 (default 50). A client that readitemsand stopped sees the first page only — read untilnextCursoris null, and pass the cursor back verbatim; an invalid one answers 400 rather than silently restarting from the top. - One decision over an explicit list.
POST /v1/review-items/decisionstakes{itemIds, state, rejectionCategory?, note?}for up to 200 items and answers with a report —{applied, casesCreated, skipped[], duplicates[]}— because one stale item must not undo the other nineteen. There is deliberately no “approve everything pending”. - Breaking. A rejection needs a reason.
rejectionCategory— one ofduplicate,incorrect,out-of-scope,low-value,not-testable,other— and anoteof 10–2000 characters, on any write after which the item stays rejected, from the dashboard and from the API alike. The reason is visible on the card and can be changed; the previous pair stays in the audit log. - Queue rows are lighter than a single item:
proposedTitleand the names of the fields that differ from an existing case; the full body comes fromGET /v1/review-items/{id}.
2026-09-05 — Where the public record starts
Section titled “2026-09-05 — Where the public record starts”The beta as invitees first saw it: a bilingual dashboard (English by default, Ukrainian as the second language), and data model v2 underneath — case states, external keys as identity, run statuses, and an audit log on every change. Earlier history belongs to the team, not to this page.