feat(milestones-ui): selector + management modal + card badge (closes #40)#47
Merged
Conversation
…d badge Closes the UI half of #39's milestone feature. Backend (schema, repo, API) is on main since c2a8df0 → 364080b; this PR makes them visible. ### New files - `milestone-types.ts` — shared `ApiMilestone`, `MilestoneMeta`, `MilestoneView` for board-client / drawer / manager / cache to all reference the same shape without circular import. - `milestone-manager.tsx` — self-contained modal: create form, list of milestones with archive-toggle, edit-in-place, delete with confirm. Bind-to convention is "milestone:new" (same simple convention as templates / labels). ### board-client.tsx - Loads milestones in the initial Promise.all with `?archived=1` (the manager filters client-side via the "Show archived" toggle). - `decryptMilestone` helper next to `decryptTemplate`. - Four new handlers: `createMilestone`, `updateMilestone`, `deleteMilestone`, `assignCardMilestone` — all wire through the routes shipped in #39. - New "Milestones (N)" button in the board header next to the export buttons. Opens the manager modal. - Mounts `<MilestoneManager>` conditionally. ### card-drawer.tsx - `CardSnapshot` gains `milestoneId: string | null`. - `DrawerProps` gains `milestones: MilestoneView[]` and `onMilestoneChange(milestoneId: string | null): Promise<void>`. - New `<label>` block right after Due Date with a `<select>` listing active milestones (plus the currently-assigned one even if it is archived). Changing the value PUTs `/api/cards/[id]/milestone`. ### kanban-view.tsx - `KanbanCard` gains `milestoneId: string | null`. - `KanbanView` accepts `milestones: MilestoneView[]`. - `SortableCard` receives an optional `milestone` prop and renders a small "◆ <name>" badge next to the Due-date / checklist counters on the card preview. ### Other plumbing - `cards.ts` repository now returns `milestoneId` in the CardDto so the field flows through the public API. `Card_milestoneId_idx` from #39 keeps the per-milestone listing cheap. - `board-cache.ts` (IndexedDB snapshot) adds `milestones` to the cached `BoardSnapshot` so offline-first reloads keep them visible. Snapshot version stays at 1 — defensive `?? []` on load handles pre-existing caches without bump. ### Out of scope (follow-ups) - Export (`board-export.ts`) does not yet include milestones in JSON / Markdown exports. Trivial follow-up. - The card-snapshot `onChange` propagation doesn't carry `milestoneId` because the source of truth for that field is the dedicated `PUT /api/cards/[id]/milestone` path, not the bulk save. ### Test plan - [x] `pnpm typecheck` clean - [x] `pnpm lint` clean - [x] `pnpm test` — 107/107 (no new tests; behaviour exercised by E2E) - [ ] CI green - [ ] Manual: open board → "Milestones" button → create, edit, archive, delete. Open card → select milestone in drawer → reload → still assigned. Card preview shows the badge. Closes #40. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Musiker15 <info@musiker15.de>
5 tasks
Musiker15
added a commit
that referenced
this pull request
May 30, 2026
User.isAdmin was stored and read but enforced nothing. Add a real admin area, strictly metadata-only so zero-knowledge holds for admins too (an admin can manage accounts but never decrypt anyone's content). - guard: requireAdmin() (requireSession + isAdmin -> 403 otherwise) - repo src/lib/repositories/admin.ts: listUsers (metadata only), setUserLock (indefinite lock + revoke sessions / unlock + reset counter), resetFailedAttempts, setUserAdmin (grant/revoke). Safety rails: no self-lock; the last admin cannot be demoted (pure wouldRemoveLastAdmin, unit-tested). Every action audited (ADMIN_USER_LOCK/UNLOCK/ RESET_ATTEMPTS, ADMIN_ROLE_GRANT/REVOKE). - routes: GET /api/admin/users, PATCH /api/admin/users/[id] (lock|unlock|resetAttempts|setAdmin via a zod discriminated union) - UI: (app)/admin/ page (isAdmin-gated, bounces non-admins) + user table with per-row actions; admin nav link shown only to admins - docs: CLAUDE.md (gap list, endpoint table, header), CHANGELOG, OpenAPI (Admin tag + routes + AdminUser schema), audit-log comment Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
Summary
Completes the milestone feature started in #39. Backend was merged; this PR ships the UI.
What's in
◆ <name>badge alongside Due / checklist counters when the card has a milestone.Files touched
milestone-manager.tsx,milestone-types.tsboard-client.tsx,card-drawer.tsx,kanban-view.tsx,board-cache.ts(IDB snapshot),cards.ts(CardDto + repo include)Encryption
enc_nameis opaque ({ name, description? }JSON, XChaCha20-Poly1305, bindTo"milestone:new"). Dates andarchivedare server-visible — matches the rest of the system.Out of scope (deliberate follow-ups)
board-export.tsdoesn't yet emit milestones in JSON/Markdown exports. Trivial drop-in follow-up.onChange(snapshot)in the card drawer doesn't carrymilestoneId. The dedicatedPUT /api/cards/[id]/milestoneis the source of truth for that field; the bulk save doesn't need to round-trip it.Test plan
pnpm typecheckcleanpnpm lintcleanpnpm test— 107/107Closes #40.
🤖 Generated with Claude Code