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:
@@ -11,6 +11,7 @@ const MAIN = join(here, '..', 'out', 'main', 'index.js')
|
||||
test.describe('live backlog', () => {
|
||||
test('The pot + Focus render real gitea data', async () => {
|
||||
test.skip(!process.env.GITEA_LIVE, 'GITEA_LIVE not set — opt-in live test')
|
||||
test.setTimeout(90_000)
|
||||
const app = await electron.launch({ args: [MAIN], env: { ...process.env } })
|
||||
const win = await app.firstWindow()
|
||||
await win.waitForLoadState('domcontentloaded')
|
||||
@@ -42,16 +43,17 @@ test.describe('live backlog', () => {
|
||||
win.getByText(/cold-start priors · \d+\/20 closed issues estimated|calibrated on \d+ closed/),
|
||||
).toBeVisible()
|
||||
// Real per-milestone forecasts — these milestone names come from gitea, not the
|
||||
// fixture (which lists Beta / Pilot-ready / v1.0).
|
||||
await expect(win.getByText(/P2 — Scheduler/)).toBeVisible()
|
||||
// fixture (Beta / Pilot-ready / v1.0). The runway shows milestones with open
|
||||
// scope; P5 is active (P2 is fully shipped, so it's correctly omitted).
|
||||
await expect(win.getByText(/P5 — Dogfood/)).toBeVisible()
|
||||
// Real capacity config from pm-state (christian/stephen), not the fixture (Stephen/Ana K.)
|
||||
await expect(win.getByText('christian', { exact: true })).toBeVisible()
|
||||
await expect(win.getByText(/pd\/day/).first()).toBeVisible()
|
||||
await win.screenshot({ path: join(here, '.artifacts', 'screens', 'live-runway.png'), fullPage: true, animations: 'disabled' })
|
||||
|
||||
// Milestone drill-in — clicking a real milestone opens its real detail
|
||||
await win.getByText(/P2 — Scheduler/).click()
|
||||
await expect(win.getByRole('heading', { name: 'P2 — Scheduler + Monte Carlo' })).toBeVisible()
|
||||
// Milestone drill-in — clicking a real (active) milestone opens its real detail
|
||||
await win.getByText(/P5 — Dogfood/).click()
|
||||
await expect(win.getByRole('heading', { name: 'P5 — Dogfood + polish' })).toBeVisible()
|
||||
await expect(win.getByText(/\d+ issues · est \d+d/)).toBeVisible()
|
||||
await win.screenshot({ path: join(here, '.artifacts', 'screens', 'live-milestone.png'), fullPage: true, animations: 'disabled' })
|
||||
await rail.getByRole('button', { name: 'Runway' }).click()
|
||||
@@ -68,10 +70,15 @@ test.describe('live backlog', () => {
|
||||
await win.getByRole('main').getByRole('link').first().click()
|
||||
await expect(win.getByText(/· stephen\/commitea/)).toBeVisible()
|
||||
await win.getByRole('button', { name: 'Adjust' }).click()
|
||||
await expect(win.getByText('Adjust estimate & priority')).toBeVisible()
|
||||
await expect(win.getByText('Adjust issue')).toBeVisible()
|
||||
await win.getByRole('combobox').first().selectOption('est/8d')
|
||||
await expect(win.getByText('Proposed label change')).toBeVisible()
|
||||
await expect(win.getByText('Proposed change')).toBeVisible()
|
||||
await expect(win.getByText(/est\/8d/).last()).toBeVisible()
|
||||
// the unified tool also drives assignee + milestone — the pickers render and diff
|
||||
await expect(win.getByText('Assignee', { exact: true })).toBeVisible()
|
||||
await expect(win.getByText('Milestone', { exact: true })).toBeVisible()
|
||||
await win.getByRole('combobox').nth(2).selectOption('') // Assignee → Unassigned
|
||||
await expect(win.getByText('unassign', { exact: true })).toBeVisible()
|
||||
await win.screenshot({ path: join(here, '.artifacts', 'screens', 'live-apply-change.png'), fullPage: true, animations: 'disabled' })
|
||||
await win.getByRole('button', { name: 'Cancel' }).click() // no mutation
|
||||
|
||||
|
||||
Reference in New Issue
Block a user