feat: the write path — apply estimate/priority changes to gitea (P4-3 apply_changes)
The first write path. Read, forecast, and calibration were all real; now you can
*manage* CommiTea from CommiTea. Estimates/priority are exclusive label axes, so
a change is a label swap — proposed, approved, then written. Nothing is assumed.
core (@commitea/core):
- planIssueChange(current, change): pure diff planner — swaps the est/*|p/* axis,
clears on null, dedups a doubled axis; returns the resulting label set + a
before/after diff + noop flag. describeChange() renders "est/2d → est/5d".
- request() seam extended for writes (method/body, JSON, 204). client gains
listLabels() (name→id) and setIssueLabels() (PUT /issues/{n}/labels).
app:
- main bridge gitea:applyChange — resolves plan.labels → ids (cached, refetch on
miss), PUTs, returns the plan + fresh issue. Token never leaves main.
- preload + global.d.ts expose applyChange; useBacklog returns a refetch so a
write re-reconciles the board + forecast.
- Issue screen: an Adjust button (shown only when configured) opens a
propose-approve Dialog — estimate/priority pickers, live "est/3d → est/8d"
consequence, Apply/Cancel. AppShell wires it, reflects new labels on the open
issue immediately, and refetches.
Verified: 83 core tests green (7 apply-changes + 2 client-write new), desktop
typecheck clean, 14 fixture e2e green. Live spec exercises propose + CANCEL (no
mutation); the real PUT was verified once manually (change #2 est/3d→est/8d→200,
reverted clean). Icon: pencil (no sliders-horizontal in the set).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -123,6 +123,29 @@ describe('createGiteaClient.getIssue', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('setIssueLabels PUTs the label ids to the issue labels endpoint', async () => {
|
||||
const { fetch, calls } = stubFetch(null, 204)
|
||||
await createGiteaClient(CONFIG, fetch).setIssueLabels(9, [3, 7])
|
||||
|
||||
expect(calls).toHaveLength(1)
|
||||
expect(calls[0].url).toBe('https://gitea.stephenmann.io/api/v1/repos/christian/commitea/issues/9/labels')
|
||||
expect(calls[0].init?.method).toBe('PUT')
|
||||
expect(calls[0].init?.headers?.['Content-Type']).toBe('application/json')
|
||||
expect(JSON.parse(calls[0].init?.body ?? '{}')).toEqual({ labels: [3, 7] })
|
||||
})
|
||||
|
||||
it('listLabels returns id+name pairs', async () => {
|
||||
const { fetch } = stubFetch([
|
||||
{ id: 3, name: 'est/2d', color: 'fff' },
|
||||
{ id: 7, name: 'p/1', color: '000' },
|
||||
])
|
||||
const labels = await createGiteaClient(CONFIG, fetch).listLabels()
|
||||
expect(labels).toEqual([
|
||||
{ id: 3, name: 'est/2d' },
|
||||
{ id: 7, name: 'p/1' },
|
||||
])
|
||||
})
|
||||
|
||||
it('throws GiteaApiError carrying status + body on a non-2xx response', async () => {
|
||||
const { fetch } = stubFetch('not found', 404)
|
||||
const client = createGiteaClient(CONFIG, fetch)
|
||||
|
||||
Reference in New Issue
Block a user