BUG-{number}: usePrintExpansion hook does not restore pre-print expansion state after afterprint
Severity: Major
Component: Frontend UI — Budget Overview print (CostBreakdownTable / usePrintExpansion)
Found in: e2e/tests/budget/budget-overview-print.spec.ts — "On-screen expansion state restored after afterprint"
Steps to Reproduce
- Navigate to Budget Overview (/budget/overview)
- Expand Work Items section and the Rohbau area (so Keller area row is visible)
- Leave the Keller area collapsed (so Kellerbau work item is NOT visible)
- Open the browser print dialog (or dispatch window.dispatchEvent(new Event('beforeprint')))
- Observe: all rows become fully expanded (including Kellerbau) — this is correct
- Close/cancel the print dialog (or dispatch window.dispatchEvent(new Event('afterprint')))
- Observe: Kellerbau work item remains VISIBLE — it should have been hidden again (pre-print state: Keller collapsed)
Expected Behavior
After afterprint fires, the CostBreakdownTable should restore to the exact expansion state that existed just before beforeprint fired. In this case: Work Items expanded, Rohbau expanded, Keller collapsed — so Kellerbau should NOT be visible.
Actual Behavior
Kellerbau (and all rows that were expanded during print) remain visible after afterprint. The table never collapses back to the pre-print state.
Root Cause (diagnosed)
usePrintExpansion captures the pre-print snapshot in a local variable inside its useEffect:
useEffect(() => {
let snapshot: Set<string> | null = null;
function handleBeforePrint() {
snapshot = new Set(expandedKeys); // ← captured here
forceExpand(); // ← setExpandedKeys(allKeys) → triggers React re-render
}
function handleAfterPrint() {
if (snapshot !== null) {
setExpandedKeys(snapshot); // ← this handler is REPLACED before afterprint fires
snapshot = null;
}
}
window.addEventListener('beforeprint', handleBeforePrint);
window.addEventListener('afterprint', handleAfterPrint);
return () => { /* cleanup */ };
}, [expandedKeys, forceExpand, setExpandedKeys]); // ← 'expandedKeys' in deps
The bug: forceExpand() inside handleBeforePrint calls setExpandedKeys(new Set(allKeys)), which changes expandedKeys. Because expandedKeys is in the useEffect dependency array, React runs the effect cleanup (removing the handlers that have snapshot) and then installs new handlers (with snapshot = null). When afterprint fires later, the NEW handleAfterPrint runs — snapshot is null → no restoration.
Fix Suggestion
Use a useRef to store the snapshot so the value persists across effect re-runs:
const snapshotRef = useRef<Set<string> | null>(null);
useEffect(() => {
function handleBeforePrint() {
snapshotRef.current = new Set(expandedKeys);
forceExpand();
}
function handleAfterPrint() {
if (snapshotRef.current !== null) {
setExpandedKeys(snapshotRef.current);
snapshotRef.current = null;
}
}
window.addEventListener('beforeprint', handleBeforePrint);
window.addEventListener('afterprint', handleAfterPrint);
return () => {
window.removeEventListener('beforeprint', handleBeforePrint);
window.removeEventListener('afterprint', handleAfterPrint);
};
}, [expandedKeys, forceExpand, setExpandedKeys]);
Environment
- Browser: Chromium (Playwright E2E)
- Viewport: Desktop 1920×1080
- Docker: yes (testcontainers)
Evidence
E2E test failure (PR #1447, shard 1, run 25992894130):
TimeoutError: locator.waitFor: Timeout 5000ms exceeded.
Call log:
- waiting for locator('...').getByRole('row').filter({ hasText: 'Kellerbau' }) to be hidden
15 × locator resolved to visible <tr class="rowLevel2_EefiO">…</tr>
The Kellerbau row remains visible even after afterprint is dispatched. The page accessibility tree confirms all rows stay expanded indefinitely.
Notes
The E2E test "On-screen expansion state restored after afterprint" in e2e/tests/budget/budget-overview-print.spec.ts:412 correctly describes the required behavior per the AC spec. The test is not at fault — the production hook has the closure bug described above.
The companion test "Print forces full expansion of collapsed breakdown rows via beforeprint" had a separate test-side selector bug (fixed in the same PR) and is not related to this issue.
BUG-{number}: usePrintExpansion hook does not restore pre-print expansion state after afterprint
Severity: Major
Component: Frontend UI — Budget Overview print (CostBreakdownTable / usePrintExpansion)
Found in: e2e/tests/budget/budget-overview-print.spec.ts — "On-screen expansion state restored after afterprint"
Steps to Reproduce
Expected Behavior
After
afterprintfires, the CostBreakdownTable should restore to the exact expansion state that existed just beforebeforeprintfired. In this case: Work Items expanded, Rohbau expanded, Keller collapsed — so Kellerbau should NOT be visible.Actual Behavior
Kellerbau (and all rows that were expanded during print) remain visible after
afterprint. The table never collapses back to the pre-print state.Root Cause (diagnosed)
usePrintExpansioncaptures the pre-print snapshot in a local variable inside itsuseEffect:The bug:
forceExpand()insidehandleBeforePrintcallssetExpandedKeys(new Set(allKeys)), which changesexpandedKeys. BecauseexpandedKeysis in theuseEffectdependency array, React runs the effect cleanup (removing the handlers that havesnapshot) and then installs new handlers (withsnapshot = null). Whenafterprintfires later, the NEWhandleAfterPrintruns —snapshotis null → no restoration.Fix Suggestion
Use a
useRefto store the snapshot so the value persists across effect re-runs:Environment
Evidence
E2E test failure (PR #1447, shard 1, run 25992894130):
The Kellerbau row remains visible even after
afterprintis dispatched. The page accessibility tree confirms all rows stay expanded indefinitely.Notes
The E2E test
"On-screen expansion state restored after afterprint"ine2e/tests/budget/budget-overview-print.spec.ts:412correctly describes the required behavior per the AC spec. The test is not at fault — the production hook has the closure bug described above.The companion test
"Print forces full expansion of collapsed breakdown rows via beforeprint"had a separate test-side selector bug (fixed in the same PR) and is not related to this issue.