diff --git a/apps/web/src/app/App.test.tsx b/apps/web/src/app/App.test.tsx index b42343a..2a661b9 100644 --- a/apps/web/src/app/App.test.tsx +++ b/apps/web/src/app/App.test.tsx @@ -12,6 +12,10 @@ const routeMockState = vi.hoisted(() => ({ error: null as unknown, })); +const toasterMockState = vi.hoisted(() => ({ + props: [] as Array>, +})); + vi.mock('./routes', () => ({ AppRoutes: () => { if (routeMockState.error) { @@ -25,6 +29,13 @@ vi.mock('./providers/ErrorBoundary', () => ({ ErrorBoundary: ({ children }: { children: ReactNode }) => <>{children}, })); +vi.mock('sonner', () => ({ + Toaster: (props: Record) => { + toasterMockState.props.push(props); + return null; + }, +})); + ( globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean } ).IS_REACT_ACT_ENVIRONMENT = true; @@ -70,6 +81,7 @@ describe('App', () => { }); usePreferencesStore.getState().reset(); routeMockState.error = null; + toasterMockState.props = []; document.documentElement.dataset.contrast = 'standard'; container = document.createElement('div'); document.body.appendChild(container); @@ -96,6 +108,22 @@ describe('App', () => { expect(document.documentElement.dataset.contrast).toBe('high'); }); + it('keeps persistent toasts dismissible and clear of mobile chrome', async () => { + await act(async () => { + root!.render(); + await Promise.resolve(); + }); + + expect(toasterMockState.props.at(-1)).toEqual(expect.objectContaining({ + closeButton: true, + mobileOffset: expect.objectContaining({ + bottom: 'calc(env(safe-area-inset-bottom) + 16rem)', + left: '0.75rem', + right: '0.75rem', + }), + })); + }); + it('routes save-load render failures into recovery instead of the generic app shell', async () => { const failure: Extract = { ok: false, diff --git a/apps/web/src/app/App.tsx b/apps/web/src/app/App.tsx index 0d44672..2729988 100644 --- a/apps/web/src/app/App.tsx +++ b/apps/web/src/app/App.tsx @@ -25,7 +25,13 @@ export function App() { standing.teamId === userTeamId) ?? null, + daysUntilTradeDeadline: seasonFlow.daysUntilTradeDeadline, + phaseLabel: seasonFlow.phaseLabel, + detailLabel: seasonFlow.detailLabel, + seasonSummary: seasonFlow.seasonSummary, + } + : null; const ambientMode = resolveAmbientMode( location.pathname, isInitialized, @@ -468,7 +481,7 @@ export function AppLayout() { {/* Main area: sidebar + content */}
-
+
<> {seasonFlow && ( - + {/* Command palette overlay */}
-
+
Navigation
) : null} -
+

{guidance.title}

-

{guidance.pagePurpose}

-

{guidance.whenToUse}

+

+ {guidance.firstSessionCue ?? guidance.pagePurpose} +

+

{guidance.whenToUse}

+
+ +
+
Story so far
+
    + {storySoFar.map((line) => ( +
  • + {line} +
  • + ))} +
@@ -254,6 +332,8 @@ export function AssistantPanel({ tickerFeed = [] }: AssistantPanelProps) {
{ratingsOpen ? ( -
+
Ratings read

{guidance.ratingsFocus}

) : null} {strategyOpen ? ( -
+
Bench coach note

{guidance.deeperStrategy}

) : null} -
+
{guidance.mobileTip}
-
+ {feedbackOpen ? ( +
+
+
Playtest feedback
+ {copiedFeedback ? ( + Copied + ) : null} +
+
+ + +
+