apply_changes UI: assign + milestone in the dialog and Reginald's tool (#24)

Surfaces the assign/milestone mutations end-to-end so they're usable in-app and
by the agent — the rest of #24.

Agent path:
- propose_change tool + system prompt now accept `assignee` (login/null) and
  `milestone` (id/null). ProposeChangeArgs + proposalsFor extended: a new
  ProposalContext (current assignee/milestone + milestones list) lets a proposal
  skip no-ops and label the milestone. ChangeProposal gains an always-present
  `summary` (plan is now label-only) — chat-panel, use-chat, and the model
  executor render `summary`, so non-label proposals display correctly.

Dialog path:
- Client `listCollaborators()` (prepends the repo owner — /collaborators omits
  them, so a solo-owner repo still has an assignable person). New
  `gitea:collaborators` bridge. The Adjust dialog gains Assignee + Milestone
  pickers (current values from the reconciled backlog); pending assign/remilestone
  changes flow through the existing apply path.

Tests: +4 core (assign/milestone proposals with no-op skip; collaborators
owner-prepend + no-double-add). 138 core green; core + desktop typecheck clean;
14 fixture e2e green; live-backlog now drives the pickers on real data.
Fixed stale P2 refs in live-backlog (P2 is shipped → correctly off the runway).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Croissant Le Doux
2026-07-09 11:54:03 -04:00
parent 72dcd396f8
commit 4fc5b902c4
15 changed files with 215 additions and 38 deletions

View File

@@ -146,6 +146,22 @@ describe('createGiteaClient.getIssue', () => {
])
})
it('listCollaborators maps login+name and prepends the owner when absent', async () => {
const { fetch } = stubFetch([{ login: 'stephen', full_name: 'Stephen M.' }])
const people = await createGiteaClient(CONFIG, fetch).listCollaborators()
// owner (christian) prepended since /collaborators omits them; collaborator name preserved
expect(people).toEqual([
{ login: 'christian', name: 'christian' },
{ login: 'stephen', name: 'Stephen M.' },
])
})
it('listCollaborators does not double-add the owner when already listed', async () => {
const { fetch } = stubFetch([{ login: 'christian', full_name: 'Christian L.' }])
const people = await createGiteaClient(CONFIG, fetch).listCollaborators()
expect(people).toEqual([{ login: 'christian', name: 'Christian L.' }])
})
it('getFile returns null on 404 and content+sha on hit', async () => {
const miss = stubFetch('nope', 404)
expect(await createGiteaClient(CONFIG, miss.fetch).getFile('directives/log.jsonl')).toBeNull()