Correction-batch UI: the end of the forward-only story (G6) - #313
Merged
Conversation
Audit gap G6, and the end of the forward-only story. A `completed` batch is immutable as a workflow unit — the kernel gives it no exit and none is coming. The product's answer to "this frame is wrong" had been a dead end dressed three different ways: an annotator that opened fully editable and refused every save (#306 made it a viewer), a bulk bar whose buttons were live and whose every request 409'd (#305 disabled them with a reason), and a sentence naming a correction batch that nothing could create. Each of those named the route onward deliberately, on the grounds that it beats a friendlier lie. This is what they were waiting for. **Scope is a choice and the default is the whole batch.** "Correct this batch" is the ordinary ask, so `all` sends no `asset_ids` at all — the server's own default is the parent's whole membership, and re-listing forty-eight ids to say so would be the client telling the API something it already knows. `selection` is the other ordinary ask, and it is offered only when there *is* one: a scope choice whose second option covers nothing is a choice between doing something and doing nothing. There is deliberately no "filtered set" option. The gallery's segments are a *view*, and a correction cut from whatever happens to be filtered at the moment of pressing is a batch nobody can describe afterwards — where a selection is a thing somebody chose. `defaultCorrectionName` is pure and exported because it is the part with a decision in it: a dialog that opened blank would make the common case cost a sentence of typing, and numbering keeps a chain readable in a listing. The count comes from the caller, which is already holding the project's batch listing. `CorrectionOf` renders lineage as one hop — what this batch corrects, not how deep the chain goes, which is the honest reading of the one field there is. It renders nothing without a parent: "not a correction of anything" is most batches, and a badge saying so on every one would be noise on the many to inform the few.
…es one Four surfaces, and the point is that they agree. **Gallery header** — the screen somebody is on when they find the frame that is wrong. It takes the current selection, so "these three are bad" is one press rather than a second pass inside the new batch. **Batches row** — beside Promote rather than in an overflow menu, because a completed batch has exactly two things left to do and hiding the second is hiding the one somebody is hunting for. The row also renders what it corrects, so a chain stays readable when the table is sorted by something else. **The annotator's read-only banner** links to the gallery rather than opening a dialog, and that is a product call: creating a batch is a curation act, curation lives on the batch view, and a second place batches are made is a second place the rules can drift. The annotator says which way is forward and hands the person to the screen that owns it. **The bulk bar's withheld sentence** gains "Create one", and the selection it is already holding becomes the correction's scope. The gallery holds the dialog's open state rather than `CorrectionButton` holding its own, because that screen has two ways in and two independent dialogs would be two states that can both be true. `CorrectionButton` is controlled when a caller supplies `open`, uncontrolled otherwise — which keeps the Batches row a one-liner. Lineage on both sides is derived from the project's batch listing rather than fetched: how many corrections point at this batch and what this batch points at are a filter and a lookup over one array the screen's siblings already request. ## Tests Twelve vitest cases for the control and its lineage — the two scopes and exactly what each sends, the selection scope absent when nothing is selected, navigation to the child, a refusal rendered as prose with the dialog staying open (one a dialog closes over is one nobody reads), and the suggested name. The round-trip runs in `cycle.spec.ts` against a **real server**, because the two claims worth making are about the kernel rather than about a stub: that the parent is genuinely untouched after a correction is cut, and that the child is a draft carrying the parent's name in its lineage. It re-checks the parent's trunk count afterwards, so "untouched" means what it says rather than only "still completed". Both fixture modules gain `create_correction` on `completed`, because that is what the server declares — a mock withholding it would describe a batch the API cannot produce. `capabilities.test.ts`'s "names every action the wire has" guard caught the missing constant, which is the job it was written for. One recorder fix: the body-capturing stub in `promote.test.tsx` called `JSON.parse` on every non-GET, and `promote` sends no body — so an empty string threw and failed every test in the file for a reason none of them was about.
JArmandoAnaya
enabled auto-merge (squash)
August 4, 2026 23:45
This was referenced Aug 5, 2026
JArmandoAnaya
added a commit
that referenced
this pull request
Aug 21, 2026
* feat(ui-core): a finished batch can be corrected Audit gap G6, and the end of the forward-only story. A `completed` batch is immutable as a workflow unit — the kernel gives it no exit and none is coming. The product's answer to "this frame is wrong" had been a dead end dressed three different ways: an annotator that opened fully editable and refused every save (#306 made it a viewer), a bulk bar whose buttons were live and whose every request 409'd (#305 disabled them with a reason), and a sentence naming a correction batch that nothing could create. Each of those named the route onward deliberately, on the grounds that it beats a friendlier lie. This is what they were waiting for. **Scope is a choice and the default is the whole batch.** "Correct this batch" is the ordinary ask, so `all` sends no `asset_ids` at all — the server's own default is the parent's whole membership, and re-listing forty-eight ids to say so would be the client telling the API something it already knows. `selection` is the other ordinary ask, and it is offered only when there *is* one: a scope choice whose second option covers nothing is a choice between doing something and doing nothing. There is deliberately no "filtered set" option. The gallery's segments are a *view*, and a correction cut from whatever happens to be filtered at the moment of pressing is a batch nobody can describe afterwards — where a selection is a thing somebody chose. `defaultCorrectionName` is pure and exported because it is the part with a decision in it: a dialog that opened blank would make the common case cost a sentence of typing, and numbering keeps a chain readable in a listing. The count comes from the caller, which is already holding the project's batch listing. `CorrectionOf` renders lineage as one hop — what this batch corrects, not how deep the chain goes, which is the honest reading of the one field there is. It renders nothing without a parent: "not a correction of anything" is most batches, and a badge saying so on every one would be noise on the many to inform the few. * feat(ui-core): every sentence that named a correction batch now reaches one Four surfaces, and the point is that they agree. **Gallery header** — the screen somebody is on when they find the frame that is wrong. It takes the current selection, so "these three are bad" is one press rather than a second pass inside the new batch. **Batches row** — beside Promote rather than in an overflow menu, because a completed batch has exactly two things left to do and hiding the second is hiding the one somebody is hunting for. The row also renders what it corrects, so a chain stays readable when the table is sorted by something else. **The annotator's read-only banner** links to the gallery rather than opening a dialog, and that is a product call: creating a batch is a curation act, curation lives on the batch view, and a second place batches are made is a second place the rules can drift. The annotator says which way is forward and hands the person to the screen that owns it. **The bulk bar's withheld sentence** gains "Create one", and the selection it is already holding becomes the correction's scope. The gallery holds the dialog's open state rather than `CorrectionButton` holding its own, because that screen has two ways in and two independent dialogs would be two states that can both be true. `CorrectionButton` is controlled when a caller supplies `open`, uncontrolled otherwise — which keeps the Batches row a one-liner. Lineage on both sides is derived from the project's batch listing rather than fetched: how many corrections point at this batch and what this batch points at are a filter and a lookup over one array the screen's siblings already request. ## Tests Twelve vitest cases for the control and its lineage — the two scopes and exactly what each sends, the selection scope absent when nothing is selected, navigation to the child, a refusal rendered as prose with the dialog staying open (one a dialog closes over is one nobody reads), and the suggested name. The round-trip runs in `cycle.spec.ts` against a **real server**, because the two claims worth making are about the kernel rather than about a stub: that the parent is genuinely untouched after a correction is cut, and that the child is a draft carrying the parent's name in its lineage. It re-checks the parent's trunk count afterwards, so "untouched" means what it says rather than only "still completed". Both fixture modules gain `create_correction` on `completed`, because that is what the server declares — a mock withholding it would describe a batch the API cannot produce. `capabilities.test.ts`'s "names every action the wire has" guard caught the missing constant, which is the job it was written for. One recorder fix: the body-capturing stub in `promote.test.tsx` called `JSON.parse` on every non-GET, and `promote` sends no body — so an empty string threw and failed every test in the file for a reason none of them was about.
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.
Task 10 — the last of the 2026-08 checkpoint audit remediation. Closes G6.
What this finishes
A
completedbatch is immutable as a workflow unit — the kernel gives it no exit and none is coming. The product's answer to "this frame is wrong" had been a dead end dressed three different ways:Every one of those sentences named the route onward deliberately, on the grounds that naming it beats a friendlier lie. This is what they were waiting for.
Where the control lives, and where it does not
Gallery header and Batches row, both capability-gated on
create_correction. The gallery takes the current selection, so "these three are bad" is one press rather than a second pass inside the new batch. The Batches row puts it beside Promote rather than in an overflow menu — a completed batch has exactly two things left to do, and hiding the second hides the one somebody is hunting for.The annotator links rather than duplicating. Creating a batch is a curation act, curation lives on the batch view, and a second place batches are made is a second place the rules can drift. The annotator says which way is forward and hands the person to the screen that owns it, with the batch in view.
Scope, and the option that is deliberately absent
allis the default and sends noasset_idsat all — the server's own default is the parent's whole membership, and re-listing forty-eight ids to say so would be the client telling the API something it already knows.selectionis the other ordinary ask, offered only when there is one: a scope choice whose second option covers nothing is a choice between doing something and doing nothing.No "filtered set" option. The gallery's segments are a view, and a correction cut from whatever happens to be filtered at the moment of pressing is a batch nobody can describe afterwards — where a selection is a thing somebody chose. The task listed it; I left it out for that reason.
Lineage
One hop, on both sides.
CorrectionOfsays what this batch corrects — not how deep the chain goes, which is the honest reading of the one field there is. It renders nothing without a parent, because "not a correction of anything" is most batches and a badge saying so on every one would be noise on the many to inform the few.Both directions are derived from the project's batch listing rather than fetched: how many corrections point at a batch and what it points at are a filter and a lookup over one array the screen's siblings already request.
Decisions taken autonomously
CorrectionButtonis controlled when givenopen, uncontrolled otherwise — that screen has two ways in, and two independent dialogs would be two states that can both be true.Found, not fixed
edit_membershipis still a declared capability with no wire surface — api: batch membership editing is not on the wire, and the gallery is now the caller #29 was waiting for #281's, and unchanged by this PR. It is the last declared-but-unreachable action in the product.Test plan
All four suites:
bash scripts/check.sh— green.CI=1 npx playwright test— 176 green.CI=1 npx playwright test -c playwright.cycle.config.ts— green, now including the full correction round-trip against a real server: complete → correct → land on the child with lineage rendered → return and confirm the parent is untouched, including its trunk count.The IA skill's sitemap gains the rule about where a correction is reached from, per its process requirement.