Commit graph

6 commits

Author SHA1 Message Date
d7d11124e3 feat(PBI-79): sprint-switch auto-select PBI/story + user-settings persist
Bij sprint-switch wordt de sprint-content server-side opgevraagd. Wanneer
de sprint precies één PBI (en die PBI exact één story binnen de sprint)
heeft, worden PBI en story automatisch geselecteerd. Alle drie keuzes
(sprint, pbi, story) worden atomair in user-settings opgeslagen zodat ze
cross-device blijven hangen.

- lib/user-settings.ts: layout krijgt nullable activePbis +
  activeStories per product.
- lib/active-sprint.ts: setActiveSelectionInSettings schrijft de drie
  keys atomair + notify pg_notify.
- actions/active-sprint.ts: switchActiveSprintAction(productId, sprintId)
  doet de server-side auto-select-resolutie (single PBI → single story)
  en returnt { sprintId, pbiId, storyId }.
- components/shared/sprint-switcher.tsx: handleSwitchSprint roept de
  nieuwe action aan en synchroniseert de workspace-store gelijk zodat
  de UI geen flash krijgt voor de SSR-refresh.
- components/backlog/active-selection-hydrator.tsx (nieuw): client-side
  effect dat user-settings.activePbis/activeStories naar workspace-store
  spiegelt; wint van de localStorage hint-restore.
- app/(app)/products/[id]/page.tsx: ActiveSelectionHydrator gemount
  binnen BacklogHydrationWrapper.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 18:13:20 +02:00
947d970231 feat(PBI-79/ST-1337): state A′ UI — metadata dialog + sticky banner + PbiList ombouw
UI-laag voor de sprint-definitie-flow (state A′).

Nieuw:
- NewSprintMetadataDialog (stap 1): sprint_goal + optionele dates;
  'Verder' schrijft via useUserSettingsStore.setPendingSprintDraft.
- SprintDefinitionBanner (sticky): toont doel + X PBI's / Y stories teller;
  'Annuleren' → AlertDialog confirm → clearPendingSprintDraft;
  'Sprint aanmaken' nog niet aangesloten (wacht op ST-1339).
- NewSprintTrigger: button in page header die de metadata-dialog opent;
  verbergt zichzelf zolang er al een draft loopt.
- SprintDraftBanner: client-wrapper, rendert banner alleen als draft bestaat.

Wijzigingen:
- lib/user-settings.ts: pendingSprintDraft startAt/endAt → z.string().date().
- PbiList: oude selectionMode + selectedIds + NewSprintDialog vervangen door
  hasDraft-afgeleide A′-mode met tri-state vinkjes; togglen muteert
  upsertPbiIntent('all'|'none') en wist storyOverrides per PBI.
- StoryPanel: in A′-mode toont elke story een cherrypick-checkbox die
  upsertStoryOverride('add'/'remove'/'clear') aanroept; cross-sprint-blocked
  stories krijgen disabled-icoon met sprint-naam tooltip.
- app/(app)/products/[id]/page.tsx: StartSprintButton vervangen door
  NewSprintTrigger; SprintDraftBanner gepositioneerd boven split-pane.

Tests: bestaande tests blijven groen (806 cases) — UI-specifieke component
tests volgen later. ST-1339 sluit createSprintWithSelectionAction aan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:48:51 +02:00
56c55e1813 feat(PBI-79/ST-1334): user-settings pendingSprintDraft-slot
- lib/user-settings.ts: nieuw workflow.pendingSprintDraft veld met
  compacte intent-shape (pbiIntent + per-PBI storyOverrides).
- actions/sprint-draft.ts: setPendingSprintDraftAction +
  clearPendingSprintDraftAction met product-membership-check + Zod-validatie.
- stores/user-settings/store.ts: setPendingSprintDraft / clearPendingSprintDraft
  optimistic acties + fine-grained mutators upsertPbiIntent / upsertStoryOverride.
  Sprint-draft actions worden dynamisch geïmporteerd zodat jsdom-tests
  zonder DATABASE_URL niet falen.
- Tests: nieuwe sprint-draft.test.ts (action-laag), uitbreiding
  user-settings store-tests (5 nieuwe cases) en schema-tests (4 cases).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 13:43:32 +02:00
2af6f24598 feat(PBI-79/ST-1333): active-sprint null-contract + clearActiveSprintAction
- lib/user-settings.ts: activeSprints values nullable in Zod-schema.
  Key-aanwezigheid heeft nu betekenis (key+null = bewust geen sprint;
  key ontbreekt = fallback-cascade).
- lib/active-sprint.ts: nieuwe readStoredActiveSprintState helper +
  resolveActiveSprint respecteert expliciet 'cleared' state zonder fallback.
  clearActiveSprintInSettings schrijft null i.p.v. de key te verwijderen.
- actions/active-sprint.ts: nieuwe clearActiveSprintAction met auth +
  membership-check.
- components/shared/sprint-switcher.tsx: '— Geen actieve sprint —'-optie
  in dropdown, disabled wanneer er geen actieve sprint is.
- Tests: nieuwe active-sprint.test.ts (resolver-paden + clear),
  active-sprint-action.test.ts (action-laag), uitbreiding user-settings.test.ts.

Plan: docs/plans/PBI-79-backlog-sprint-workflow.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 13:35:32 +02:00
Janpeter Visser
bf7162a5fc
feat(PBI-76): migrate cookie-based prefs to user-settings (Phase 2) (#189)
* feat(PBI-76): extend UserSettings schema with layout

Adds layout.splitPanePositions and layout.activeSprints. These will
hold values currently kept in client-side and server-side cookies
(Phase 2). Two new tests cover the shape.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(PBI-76): migrate SplitPane positions to user-settings store

Outside of a drag the store is the source of truth (cross-tab
updates flow in for free). During a drag we keep splits in local
state so mousemove does not round-trip through the store. On
mouseup we persist the final splits via setPref. Removes
document.cookie reads/writes — cookieKey is reused as the
store-key for backwards compat.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(PBI-76): resolveActiveSprint reads from User.settings

lib/active-sprint:
- New helpers: getActiveSprintIdFromSettings, setActiveSprintInSettings,
  clearActiveSprintInSettings — all read/write user.settings.layout.activeSprints.
- resolveActiveSprint(productId, userId) — userId now required, falls back
  to first OPEN, then most recent CLOSED sprint.
- Cookie helpers (getActiveSprintIdFromCookie/setActiveSprintCookie/
  clearActiveSprintCookie) removed.

Callers updated to pass session.userId. The cookie-based fallback path
is gone — `actions/active-sprint.ts` and `actions/sprints.ts` will be
updated in the next commit (T-917).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(PBI-76): rewrite setActiveSprint callers to use settings

setActiveSprintAction, syncActiveSprintCookieAction, and the two
sprint-creation paths in actions/sprints.ts now write through
setActiveSprintInSettings (which also emits pg_notify for cross-tab
sync) instead of dropping a cookie. The action names keep the
'cookie' suffix in the user-visible API for now — clean rename can
come later.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(PBI-76): migration helper v2 — handle legacy cookies

Bumps marker version to 'v2'. buildMigrationPatch now also scans
document.cookie for `sp:*` (split-pane positions) and
`active_sprint_*` (active sprint per product) and lifts them into
layout.splitPanePositions / layout.activeSprints. clearLegacyStorage
replaces clearLegacyLocalStorage and clears both keys and cookies.
clearLegacyLocalStorage stays as a deprecated alias so the bridge
upgrade is a single rename.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(PBI-76): align tests with new SplitPane and active-sprint flow

- split-pane.test.tsx: seed positions via Zustand store instead of
  document.cookie; mock @/actions/user-settings so the prisma client
  is not transitively initialised in jsdom.
- backlog-split-pane.test.tsx: same action mock.
- sprint-dates.test.ts: add user.findUnique/update + $executeRaw
  mocks because createSprintAction now writes to user-settings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 21:20:29 +02:00
Janpeter Visser
a0e5867857
feat(PBI-76): user-settings DB-store infrastructure (Phase 0) (#185)
* docs(PBI-76): plan for user-settings DB-store

Persists view/filter prefs in User.settings (Json) instead of
localStorage. SSR-correct hydration, cross-tab sync via
LISTEN/NOTIFY + SSE, cross-device persistence.

Phased: 0=infra, 1=migrate flicker sources, 2=cookie consolidation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(PBI-76): User.settings json column + migration

Adds JSONB column to users table for persistent user prefs.
Idempotent SQL — safe on databases where column already exists.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(PBI-76): user-settings types and merge helpers

Zod schema for User.settings shape (views/devTools), deep-merge
helper that replaces arrays and merges nested objects, and a
safe parser that returns defaults on invalid input.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(PBI-76): updateUserSettingsAction with notify

Validates patch via Zod, deep-merges with current settings in
a transaction, persists to DB, and emits pg_notify on
scrum4me_changes for cross-tab/cross-device sync. Demo accounts
get 403, unauthenticated 401, invalid input 422.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(PBI-76): user-settings zustand store with optimistic flow

Hydrate from prop (SSR-correct), setPref via path with optimistic
update + rollback on server error, applyServerPatch for SSE-driven
cross-tab updates. Demo accounts skip server-write entirely.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(PBI-76): SSE route for user-settings

User-scoped /api/realtime/user-settings stream that filters
scrum4me_changes notifications on kind=user_settings and matching
userId. Forwards the patch as a data: event so other tabs can
applyServerPatch without re-fetching settings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(PBI-76): user-settings bridge mounted in app layout

Hydrates the zustand store with the user's persisted settings via
prop (SSR-correct, no flicker). Opens an EventSource to
/api/realtime/user-settings so changes from other tabs/devices
flow into the same store. Demo accounts skip the SSE subscription.

Layout now selects user.settings alongside the other user fields,
no extra DB roundtrip.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(PBI-76): user-settings lib/action/store coverage

22 vitest cases covering merge semantics (no mutation, array
replace, nested merge), Zod schema strictness, server action
auth/demo/validation paths, and the optimistic store flow
including rollback and demo-mode skip.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(PBI-76): sync package-lock to v1.3.3

Lockfile drifted after @prisma/client reinstall during the
schema regenerate. No dependency changes — just the version
field tracking package.json bumped in #184.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 12:44:32 +02:00