6c6c8b96b7
fix(realtime): force-destroy pg socket on cleanup timeout (SSE leak) ( #44 )
...
Three SSE-routes (solo, backlog, notifications) each create a long-
running pg.Client that LISTENs on scrum4me_changes. On abrupt close
(Fast Refresh, browser refresh, Vercel function recycle) the
pgClient.end()-await sometimes hangs silently, leaving the underlying
socket connected to Postgres. The connection stays in 'idle' on Neon's
side and after ~10-20 reconnects the connection-pool fills up — new
SSE connects fail with ERR_INCOMPLETE_CHUNKED_ENCODING in the browser.
Fix: shared `closePgClientSafely` helper that races client.end()
against a 2 s timeout; on timeout it force-destroys the underlying
socket so the OS releases the FD and Postgres notices the disconnect.
Validated by direct DB inspection: 18 stale 'idle LISTEN'-connections
were piled up before the fix; after manual pg_terminate_backend cleanup
the SSE-stream stabilised. This change makes the pile-up impossible
going forward.
- new lib/realtime/pg-client-cleanup.ts
- 3 routes use the helper instead of bare `await pgClient.end()`
- 3 unit tests for the helper (timely-end, hang-falls-back-to-destroy,
end-rejection-is-swallowed)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 20:04:22 +02:00
8877ea469d
feat(M14): 3-pane backlog — generic SplitPane, BacklogStore, SSE realtime, card-grid TaskPanel ( #22 )
...
* feat(split-pane): refactor to generic n-pane SplitPane with cookie persistence
New API: panes[], defaultSplit[], cookieKey, tabLabels. Supports arbitrary
number of panes with n-1 draggable dividers and JSON cookie persistence.
Replaces TriplePane; mobile renders tabs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(split-pane): migrate callers to new panes[] API
Backlog page and sprint board now use generic SplitPane.
TriplePane removed; sprint board uses 3-pane with defaultSplit=[28,35,37].
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(split-pane): add unit tests for 2/3-pane, cookie-restore, mobile tabs
Added jsdom + @testing-library/react devDeps for component testing.
7 cases: render, divider count, cookie restore, invalid cookie fallback,
mobile tab render/switch, and no-dividers-on-mobile.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(backlog): add BacklogStore Zustand store with applyChange reducer
State: pbis, storiesByPbi, tasksByStory. setInitialData for server
hydration; applyChange(entity, op, data) handles I/U/D for SSE events.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(backlog): server-fetch tasks + hydrate BacklogStore on page load
Page now fetches tasks parallel to stories and groups by story_id.
BacklogHydrationWrapper calls setInitialData on mount so the store
is ready for downstream SSE consumers.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(backlog): add EmptyPanel shared component, replace inline empty states
EmptyPanel takes title?, message, and optional action with DemoTooltip.
Replaces duplicate inline empty-state markup in pbi-list and story-panel.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(backlog): add TaskPanel with sortable rows and TaskDialog wiring
Reads selectedStoryId + tasksByStory from stores. DnD reorder via
reorderTasksAction. Row click → ?editTask, + button → ?newTask&storyId.
DemoTooltip on drag handles and + button.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(backlog): wire TaskPanel + TaskDialog into backlog page
3-pane SplitPane [20,45,35]. searchParams for newTask/editTask.
TaskDialog and EditTaskLoader render on ?newTask and ?editTask.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(backlog): add TaskPanel tests for render states and click handlers
7 cases: no-story empty, no-tasks empty+action, tasks render, + button
router.push, row click router.push, demo disabled button, demo disabled handles.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(backlog): migrate PbiList to store-driven via useBacklogStore
Removes pbis prop; reads from useBacklogStore(s => s.pbis) so SSE
updates reflect in real-time without prop drilling.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(backlog): migrate StoryPanel to store-driven + selectStory on click
Removes storiesByPbi prop; reads from useBacklogStore. Card click now
dispatches selectStory(id) + shows isSelected highlight. Edit moved to
inline pencil button. page.tsx drops pbis/storiesByPbi props.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(backlog): add 3-pane integration tests for click-cascade flow
Covers: empty states, PBI→stories, story→tasks, cascade-reset,
isSelected highlight. localStorage mocked for sort-mode persistence.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ST-1115): SSE backlog realtime — endpoint, hook, hydration mount, tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ST-1116): mobile auto-switch tabs + back button in BacklogSplitPane
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs(ST-1116): update functional-spec (3-pane backlog + mobile) and architecture (backlog SSE + backlog-store)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ST-1117): TaskPanel card-grid — BacklogCard + rectSortingStrategy, tests updated
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(tests): correct PbiStatusApi type and remove duplicate mock keys
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 18:16:07 +02:00