Fix #119: milestone picker + milestone issue links #128

Merged
crueber merged 2 commits from fix/issue-119 into main 2026-09-05 06:32:36 +00:00
Owner

Fixes #119 (no way to add a milestone to an issue).

Issue sidebar: new MilestonePicker (web/src/components/MilestonePicker.jsx) mirroring the LabelPicker + idiom — radio rows per repo milestone plus a No milestone clear row, PATCH on select (web/src/lib/milestones.js helpers, node --test covered), triage-gated like labels, Esc/outside-click close with focus restore, dark+light via shared classes. Sidebar shows the milestone title (bare-id fallback for deleted milestones).

Milestone page: each milestone now lists/links its issues via the existing server-side milestone= list filter (no client-side filtering); the title links to the filtered issue list. Counts were already server-derived.

Backend fix (required for the clear option): PATCH .../issues/{num} with explicit {"milestone": null} silently no-op'd — encoding/json maps null onto a nil **string, indistinguishable from an absent key. The handler now decodes the key as a value json.RawMessage (absent=nil, null=clear, string=set, else 400). Covered by TestPatchIssueMilestoneHTTP; package coverage 96.2% (gate 95%).

Verification: node --test web/test/unit/*.test.js 270/270; go test ./internal/issues/ -race clean; go vet + gofmt clean; headless-Chrome CDP drive (assign via picker, visible on issue + milestone page counts/link, both themes, clear back to none, server state confirmed via API). Only console entries are pre-existing 404s for unwired optional endpoints (/api/social, /api/tasks, lane root) on origin/main — untouched by this diff. Docs/features/02_issues.md updated in-change (11 table + Wave B notes).

Fixes #119 (no way to add a milestone to an issue). **Issue sidebar:** new `MilestonePicker` (`web/src/components/MilestonePicker.jsx`) mirroring the LabelPicker `+` idiom — radio rows per repo milestone plus a `No milestone` clear row, PATCH on select (`web/src/lib/milestones.js` helpers, `node --test` covered), triage-gated like labels, Esc/outside-click close with focus restore, dark+light via shared classes. Sidebar shows the milestone title (bare-id fallback for deleted milestones). **Milestone page:** each milestone now lists/links its issues via the existing server-side `milestone=` list filter (no client-side filtering); the title links to the filtered issue list. Counts were already server-derived. **Backend fix (required for the clear option):** `PATCH .../issues/{num}` with explicit `{"milestone": null}` silently no-op'd — encoding/json maps null onto a nil `**string`, indistinguishable from an absent key. The handler now decodes the key as a value `json.RawMessage` (absent=nil, null=clear, string=set, else 400). Covered by `TestPatchIssueMilestoneHTTP`; package coverage 96.2% (gate 95%). **Verification:** `node --test web/test/unit/*.test.js` 270/270; `go test ./internal/issues/ -race` clean; `go vet` + gofmt clean; headless-Chrome CDP drive (assign via picker, visible on issue + milestone page counts/link, both themes, clear back to none, server state confirmed via API). Only console entries are pre-existing 404s for unwired optional endpoints (/api/social, /api/tasks, lane root) on origin/main — untouched by this diff. Docs/features/02_issues.md updated in-change (11 table + Wave B notes).
Issue sidebar gets a MilestonePicker (+ dropdown mirroring LabelPicker:
radio rows per repo milestone + No-milestone clear row, PATCH on select,
triage-gated, Esc/outside-click, dark+light). Sidebar shows the milestone
title (bare id fallback for deleted milestones).

Milestone page lists/links each milestone's issues via the existing
server-side milestone= list filter (+ title link to the filtered list).

Backend: PATCH .../issues/{num} now distinguishes explicit null (clear)
from an absent milestone key (no-op) via value json.RawMessage —
encoding/json maps null onto a nil **string, so clear silently no-op'd
before. Docs/features/02_issues.md (11, Wave B notes) updated in-change.
Sign in to join this conversation.
No description provided.