fix(lobby): mark past journeys with solo/MP mode icon #167
+214
−0
Annotations
10 errors and 2 warnings
|
Test:
src/dice/DiceScene.tsx#L171
Error: [vitest] No "useLoader" export is defined on the "@react-three/fiber" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:
vi.mock(import("@react-three/fiber"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})
❯ D20Mesh src/dice/DiceScene.tsx:171:19
❯ Object.react_stack_bottom_frame node_modules/react-dom/cjs/react-dom-client.development.js:25904:20
❯ renderWithHooks node_modules/react-dom/cjs/react-dom-client.development.js:7662:22
❯ updateFunctionComponent node_modules/react-dom/cjs/react-dom-client.development.js:10166:19
❯ beginWork node_modules/react-dom/cjs/react-dom-client.development.js:11778:18
❯ runWithFiberInDEV node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ performUnitOfWork node_modules/react-dom/cjs/react-dom-client.development.js:17641:22
❯ workLoopSync node_modules/react-dom/cjs/react-dom-client.development.js:17469:41
|
|
Test:
src/dice/DiceScene.tsx#L171
Error: [vitest] No "useLoader" export is defined on the "@react-three/fiber" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:
vi.mock(import("@react-three/fiber"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})
❯ D20Mesh src/dice/DiceScene.tsx:171:19
❯ Object.react_stack_bottom_frame node_modules/react-dom/cjs/react-dom-client.development.js:25904:20
❯ renderWithHooks node_modules/react-dom/cjs/react-dom-client.development.js:7662:22
❯ updateFunctionComponent node_modules/react-dom/cjs/react-dom-client.development.js:10166:19
❯ beginWork node_modules/react-dom/cjs/react-dom-client.development.js:11778:18
❯ runWithFiberInDEV node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ performUnitOfWork node_modules/react-dom/cjs/react-dom-client.development.js:17641:22
❯ workLoopSync node_modules/react-dom/cjs/react-dom-client.development.js:17469:41
|
|
Test:
src/dice/DiceScene.tsx#L171
Error: [vitest] No "useLoader" export is defined on the "@react-three/fiber" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:
vi.mock(import("@react-three/fiber"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})
❯ D20Mesh src/dice/DiceScene.tsx:171:19
❯ Object.react_stack_bottom_frame node_modules/react-dom/cjs/react-dom-client.development.js:25904:20
❯ renderWithHooks node_modules/react-dom/cjs/react-dom-client.development.js:7662:22
❯ updateFunctionComponent node_modules/react-dom/cjs/react-dom-client.development.js:10166:19
❯ beginWork node_modules/react-dom/cjs/react-dom-client.development.js:11778:18
❯ runWithFiberInDEV node_modules/react-dom/cjs/react-dom-client.development.js:874:13
❯ performUnitOfWork node_modules/react-dom/cjs/react-dom-client.development.js:17641:22
❯ workLoopSync node_modules/react-dom/cjs/react-dom-client.development.js:17469:41
|
|
Test:
src/__tests__/lobby-start-ws-open.test.tsx#L170
Error: Test timed out in 5000ms.
If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout".
❯ src/__tests__/lobby-start-ws-open.test.tsx:170:3
|
|
Test:
src/__tests__/dice-overlay-wiring-34-5.test.ts#L238
AssertionError: expected 'handleDiceThrow = useCallback(\n (…' to match /face\s*,?\s*\}/
- Expected:
/face\s*,?\s*\}/
+ Received:
"handleDiceThrow = useCallback(
(params: DiceThrowParams, face: number[]) => {
if (!diceRequest) return;
const beatId = pendingBeatIdRef.current;
pendingBeatIdRef.current = null;
send({
type: MessageType.DICE_THROW,
payload: {
request_id: diceRequest.request_id,
throw_params: params,
face,
...(beatId ? { beat_id: beatId } : {}),
},
player_id: \"\",
});
// If this was a beat roll, set thinking — narrator will run server-side
if (beatId) {
setCanType(false);
setThinking(true);
}
},
[diceRequest, send],
)"
❯ src/__tests__/dice-overlay-wiring-34-5.test.ts:238:30
|
|
Test:
src/__tests__/dice-overlay-wiring-34-5.test.ts#L179
AssertionError: expected 'import { lazy, Suspense, useCallback,…' to match /playerId=\{/
- Expected:
/playerId=\{/
+ Received:
"import { lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState } from \"react\";
import { Route, Routes, useNavigate, useParams } from \"react-router-dom\";
import { ConnectScreen } from \"@/screens/ConnectScreen\";
import { CharacterCreation, type CreationScene } from \"@/components/CharacterCreation/CharacterCreation\";
import { GameBoard } from \"@/components/GameBoard/GameBoard\";
import { ImageBusProvider } from \"@/providers/ImageBusProvider\";
import type { ResourcePool } from \"@/components/CharacterPanel\";
import { ErrorBoundary } from \"@/components/ErrorBoundary\";
import { GameStateProvider, useGameState } from \"@/providers/GameStateProvider\";
import { useGameSocket } from \"@/hooks/useGameSocket\";
import { useGenreTheme } from \"@/hooks/useGenreTheme\";
import { useChromeArchetype } from \"@/hooks/useChromeArchetype\";
import { useAudioCue } from \"@/hooks/useAudioCue\";
import { useAudio } from \"@/hooks/useAudio\";
import { useStateMirror } from \"@/hooks/useStateMirror\";
import { useSlashCommands } from \"@/hooks/useSlashCommands\";
import { useGameBoardLayout } from \"@/hooks/useGameBoardLayout\";
import { useLayoutMode } from \"@/hooks/useLayoutMode\";
import { MessageType, type GameMessage } from \"@/types/protocol\";
import type { CharacterSheetData } from \"@/components/CharacterSheet\";
import type { InventoryData } from \"@/components/InventoryPanel\";
import type { MapState } from \"@/components/MapOverlay\";
import type { CharacterSummary } from \"@/types/party\";
import type { ConfrontationData, BeatOption } from \"@/components/ConfrontationOverlay\";
import type { TurnStatusEntry } from \"@/components/TurnStatusPanel\";
import type { DiceRequestPayload, DiceResultPayload, DiceThrowParams } from \"@/types/payloads\";
import type { GenresResponse } from \"@/types/genres\";
import { MultiplayerSessionStatus, type SessionPlayerStatus } from \"@/components/MultiplayerSessionStatus\";
import { ReconnectBanner } from \"@/components/ReconnectBanner\";
import { PausedBanner } from \"@/components/PausedBanner\";
import { OfflineBanner } from \"@/components/OfflineBanner\";
import { useDisplayName } from \"@/hooks/useDisplayName\";
import { usePeerEventCache } from \"@/hooks/usePeerEventCache\";
import { appendHistory, loadHistory } from \"@/screens/lobby/historyStore\";
const LazyDashboard = lazy(() =>
import(\"@/components/Dashboard/DashboardApp\").then((m) => ({ default: m.DashboardApp })),
);
// DiceOverlay overlay removed — dice now render inline in the Confrontation panel
// via InlineDiceTray. The DiceOverlay component and DiceSpikePage are retained
// for isolated testing.
type SessionPhase = \"connect\" | \"creation\" | \"game\";
// Server ERROR codes that mean the session is unrecoverable and the user
// must escape (full-screen fatal panel, disconnect WS, no auto-reconnect).
// Anything NOT in this set is treated as a transient per-message rejection
// — the WS stays open and a sanitized banner surfaces the failure. Add
// new codes here only when the server has *also* set reconnect_required to
// stop the reconnect loop; otherwise the client and server disagree about
// recovery and the user gets stuck on a fatal panel that the server
// doesn't think is fatal.
const FATAL_ERROR_CODES: ReadonlySet<string> = new Set([
\"save_schema_invalid\",
]);
// Strip Pydantic-validation noise from a server ERROR message before
// surfacing it to the player. The raw `validation error for GameMessage`
// dump (with field-path lines like `payload.targetStep — Extra inputs are
// not permitted` and `errors.pydantic.dev/...` URLs) is useful in OTEL
// and the dev console but not for end-users — they need an action-shaped
// sentence, not a schema dump. Keep the first non-empty line up to a
// reasonable length; everything after the first `\\n` is internal detail.
function sanitizeErrorMessage(raw: string): string {
const firstLine = raw.split(\"\
|
|
Test:
src/__tests__/dice-overlay-wiring-34-5.test.ts#L87
AssertionError: expected 'import { lazy, Suspense, useCallback,…' to match /onThrow=\{/
- Expected:
/onThrow=\{/
+ Received:
"import { lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState } from \"react\";
import { Route, Routes, useNavigate, useParams } from \"react-router-dom\";
import { ConnectScreen } from \"@/screens/ConnectScreen\";
import { CharacterCreation, type CreationScene } from \"@/components/CharacterCreation/CharacterCreation\";
import { GameBoard } from \"@/components/GameBoard/GameBoard\";
import { ImageBusProvider } from \"@/providers/ImageBusProvider\";
import type { ResourcePool } from \"@/components/CharacterPanel\";
import { ErrorBoundary } from \"@/components/ErrorBoundary\";
import { GameStateProvider, useGameState } from \"@/providers/GameStateProvider\";
import { useGameSocket } from \"@/hooks/useGameSocket\";
import { useGenreTheme } from \"@/hooks/useGenreTheme\";
import { useChromeArchetype } from \"@/hooks/useChromeArchetype\";
import { useAudioCue } from \"@/hooks/useAudioCue\";
import { useAudio } from \"@/hooks/useAudio\";
import { useStateMirror } from \"@/hooks/useStateMirror\";
import { useSlashCommands } from \"@/hooks/useSlashCommands\";
import { useGameBoardLayout } from \"@/hooks/useGameBoardLayout\";
import { useLayoutMode } from \"@/hooks/useLayoutMode\";
import { MessageType, type GameMessage } from \"@/types/protocol\";
import type { CharacterSheetData } from \"@/components/CharacterSheet\";
import type { InventoryData } from \"@/components/InventoryPanel\";
import type { MapState } from \"@/components/MapOverlay\";
import type { CharacterSummary } from \"@/types/party\";
import type { ConfrontationData, BeatOption } from \"@/components/ConfrontationOverlay\";
import type { TurnStatusEntry } from \"@/components/TurnStatusPanel\";
import type { DiceRequestPayload, DiceResultPayload, DiceThrowParams } from \"@/types/payloads\";
import type { GenresResponse } from \"@/types/genres\";
import { MultiplayerSessionStatus, type SessionPlayerStatus } from \"@/components/MultiplayerSessionStatus\";
import { ReconnectBanner } from \"@/components/ReconnectBanner\";
import { PausedBanner } from \"@/components/PausedBanner\";
import { OfflineBanner } from \"@/components/OfflineBanner\";
import { useDisplayName } from \"@/hooks/useDisplayName\";
import { usePeerEventCache } from \"@/hooks/usePeerEventCache\";
import { appendHistory, loadHistory } from \"@/screens/lobby/historyStore\";
const LazyDashboard = lazy(() =>
import(\"@/components/Dashboard/DashboardApp\").then((m) => ({ default: m.DashboardApp })),
);
// DiceOverlay overlay removed — dice now render inline in the Confrontation panel
// via InlineDiceTray. The DiceOverlay component and DiceSpikePage are retained
// for isolated testing.
type SessionPhase = \"connect\" | \"creation\" | \"game\";
// Server ERROR codes that mean the session is unrecoverable and the user
// must escape (full-screen fatal panel, disconnect WS, no auto-reconnect).
// Anything NOT in this set is treated as a transient per-message rejection
// — the WS stays open and a sanitized banner surfaces the failure. Add
// new codes here only when the server has *also* set reconnect_required to
// stop the reconnect loop; otherwise the client and server disagree about
// recovery and the user gets stuck on a fatal panel that the server
// doesn't think is fatal.
const FATAL_ERROR_CODES: ReadonlySet<string> = new Set([
\"save_schema_invalid\",
]);
// Strip Pydantic-validation noise from a server ERROR message before
// surfacing it to the player. The raw `validation error for GameMessage`
// dump (with field-path lines like `payload.targetStep — Extra inputs are
// not permitted` and `errors.pydantic.dev/...` URLs) is useful in OTEL
// and the dev console but not for end-users — they need an action-shaped
// sentence, not a schema dump. Keep the first non-empty line up to a
// reasonable length; everything after the first `\\n` is internal detail.
function sanitizeErrorMessage(raw: string): string {
const firstLine = raw.split(\"\\n
|
|
Test:
src/__tests__/dice-overlay-wiring-34-5.test.ts#L29
AssertionError: expected 'import { lazy, Suspense, useCallback,…' to match /lazy\(\s*\(\)\s*=>\s*import\(['"].*d…/i
- Expected:
/lazy\(\s*\(\)\s*=>\s*import\(['"].*dice.*DiceOverlay['"]\)/i
+ Received:
"import { lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState } from \"react\";
import { Route, Routes, useNavigate, useParams } from \"react-router-dom\";
import { ConnectScreen } from \"@/screens/ConnectScreen\";
import { CharacterCreation, type CreationScene } from \"@/components/CharacterCreation/CharacterCreation\";
import { GameBoard } from \"@/components/GameBoard/GameBoard\";
import { ImageBusProvider } from \"@/providers/ImageBusProvider\";
import type { ResourcePool } from \"@/components/CharacterPanel\";
import { ErrorBoundary } from \"@/components/ErrorBoundary\";
import { GameStateProvider, useGameState } from \"@/providers/GameStateProvider\";
import { useGameSocket } from \"@/hooks/useGameSocket\";
import { useGenreTheme } from \"@/hooks/useGenreTheme\";
import { useChromeArchetype } from \"@/hooks/useChromeArchetype\";
import { useAudioCue } from \"@/hooks/useAudioCue\";
import { useAudio } from \"@/hooks/useAudio\";
import { useStateMirror } from \"@/hooks/useStateMirror\";
import { useSlashCommands } from \"@/hooks/useSlashCommands\";
import { useGameBoardLayout } from \"@/hooks/useGameBoardLayout\";
import { useLayoutMode } from \"@/hooks/useLayoutMode\";
import { MessageType, type GameMessage } from \"@/types/protocol\";
import type { CharacterSheetData } from \"@/components/CharacterSheet\";
import type { InventoryData } from \"@/components/InventoryPanel\";
import type { MapState } from \"@/components/MapOverlay\";
import type { CharacterSummary } from \"@/types/party\";
import type { ConfrontationData, BeatOption } from \"@/components/ConfrontationOverlay\";
import type { TurnStatusEntry } from \"@/components/TurnStatusPanel\";
import type { DiceRequestPayload, DiceResultPayload, DiceThrowParams } from \"@/types/payloads\";
import type { GenresResponse } from \"@/types/genres\";
import { MultiplayerSessionStatus, type SessionPlayerStatus } from \"@/components/MultiplayerSessionStatus\";
import { ReconnectBanner } from \"@/components/ReconnectBanner\";
import { PausedBanner } from \"@/components/PausedBanner\";
import { OfflineBanner } from \"@/components/OfflineBanner\";
import { useDisplayName } from \"@/hooks/useDisplayName\";
import { usePeerEventCache } from \"@/hooks/usePeerEventCache\";
import { appendHistory, loadHistory } from \"@/screens/lobby/historyStore\";
const LazyDashboard = lazy(() =>
import(\"@/components/Dashboard/DashboardApp\").then((m) => ({ default: m.DashboardApp })),
);
// DiceOverlay overlay removed — dice now render inline in the Confrontation panel
// via InlineDiceTray. The DiceOverlay component and DiceSpikePage are retained
// for isolated testing.
type SessionPhase = \"connect\" | \"creation\" | \"game\";
// Server ERROR codes that mean the session is unrecoverable and the user
// must escape (full-screen fatal panel, disconnect WS, no auto-reconnect).
// Anything NOT in this set is treated as a transient per-message rejection
// — the WS stays open and a sanitized banner surfaces the failure. Add
// new codes here only when the server has *also* set reconnect_required to
// stop the reconnect loop; otherwise the client and server disagree about
// recovery and the user gets stuck on a fatal panel that the server
// doesn't think is fatal.
const FATAL_ERROR_CODES: ReadonlySet<string> = new Set([
\"save_schema_invalid\",
]);
// Strip Pydantic-validation noise from a server ERROR message before
// surfacing it to the player. The raw `validation error for GameMessage`
// dump (with field-path lines like `payload.targetStep — Extra inputs are
// not permitted` and `errors.pydantic.dev/...` URLs) is useful in OTEL
// and the dev console but not for end-users — they need an action-shaped
// sentence, not a schema dump. Keep the first non-empty line up to a
// reasonable length; everything after the first `\\n` is internal detail.
function
|
|
Test:
src/__tests__/confrontation-wiring.test.tsx#L306
AssertionError: expected 'import { lazy, Suspense, useCallback,…' to match /type:\s*MessageType\.BEAT_SELECTION/
- Expected:
/type:\s*MessageType\.BEAT_SELECTION/
+ Received:
"import { lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState } from \"react\";
import { Route, Routes, useNavigate, useParams } from \"react-router-dom\";
import { ConnectScreen } from \"@/screens/ConnectScreen\";
import { CharacterCreation, type CreationScene } from \"@/components/CharacterCreation/CharacterCreation\";
import { GameBoard } from \"@/components/GameBoard/GameBoard\";
import { ImageBusProvider } from \"@/providers/ImageBusProvider\";
import type { ResourcePool } from \"@/components/CharacterPanel\";
import { ErrorBoundary } from \"@/components/ErrorBoundary\";
import { GameStateProvider, useGameState } from \"@/providers/GameStateProvider\";
import { useGameSocket } from \"@/hooks/useGameSocket\";
import { useGenreTheme } from \"@/hooks/useGenreTheme\";
import { useChromeArchetype } from \"@/hooks/useChromeArchetype\";
import { useAudioCue } from \"@/hooks/useAudioCue\";
import { useAudio } from \"@/hooks/useAudio\";
import { useStateMirror } from \"@/hooks/useStateMirror\";
import { useSlashCommands } from \"@/hooks/useSlashCommands\";
import { useGameBoardLayout } from \"@/hooks/useGameBoardLayout\";
import { useLayoutMode } from \"@/hooks/useLayoutMode\";
import { MessageType, type GameMessage } from \"@/types/protocol\";
import type { CharacterSheetData } from \"@/components/CharacterSheet\";
import type { InventoryData } from \"@/components/InventoryPanel\";
import type { MapState } from \"@/components/MapOverlay\";
import type { CharacterSummary } from \"@/types/party\";
import type { ConfrontationData, BeatOption } from \"@/components/ConfrontationOverlay\";
import type { TurnStatusEntry } from \"@/components/TurnStatusPanel\";
import type { DiceRequestPayload, DiceResultPayload, DiceThrowParams } from \"@/types/payloads\";
import type { GenresResponse } from \"@/types/genres\";
import { MultiplayerSessionStatus, type SessionPlayerStatus } from \"@/components/MultiplayerSessionStatus\";
import { ReconnectBanner } from \"@/components/ReconnectBanner\";
import { PausedBanner } from \"@/components/PausedBanner\";
import { OfflineBanner } from \"@/components/OfflineBanner\";
import { useDisplayName } from \"@/hooks/useDisplayName\";
import { usePeerEventCache } from \"@/hooks/usePeerEventCache\";
import { appendHistory, loadHistory } from \"@/screens/lobby/historyStore\";
const LazyDashboard = lazy(() =>
import(\"@/components/Dashboard/DashboardApp\").then((m) => ({ default: m.DashboardApp })),
);
// DiceOverlay overlay removed — dice now render inline in the Confrontation panel
// via InlineDiceTray. The DiceOverlay component and DiceSpikePage are retained
// for isolated testing.
type SessionPhase = \"connect\" | \"creation\" | \"game\";
// Server ERROR codes that mean the session is unrecoverable and the user
// must escape (full-screen fatal panel, disconnect WS, no auto-reconnect).
// Anything NOT in this set is treated as a transient per-message rejection
// — the WS stays open and a sanitized banner surfaces the failure. Add
// new codes here only when the server has *also* set reconnect_required to
// stop the reconnect loop; otherwise the client and server disagree about
// recovery and the user gets stuck on a fatal panel that the server
// doesn't think is fatal.
const FATAL_ERROR_CODES: ReadonlySet<string> = new Set([
\"save_schema_invalid\",
]);
// Strip Pydantic-validation noise from a server ERROR message before
// surfacing it to the player. The raw `validation error for GameMessage`
// dump (with field-path lines like `payload.targetStep — Extra inputs are
// not permitted` and `errors.pydantic.dev/...` URLs) is useful in OTEL
// and the dev console but not for end-users — they need an action-shaped
// sentence, not a schema dump. Keep the first non-empty line up to a
// reasonable length; everything after the first `\\n` is internal detail.
function sanitizeErrorMessage(raw: str
|
|
Test:
src/__tests__/confrontation-wiring.test.tsx#L266
AssertionError: expected 'import {\n createContext,\n useCall…' to match /ConfrontationWidget.*data=\{confronta…/
- Expected:
/ConfrontationWidget.*data=\{confrontationData\}/
+ Received:
"import {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
type ReactNode,
} from \"react\";
import {
DockviewReact,
type DockviewReadyEvent,
type DockviewApi,
type IDockviewPanelProps,
} from \"dockview-react\";
import \"dockview-react/dist/styles/dockview.css\";
import \"@/styles/dockview-theme.css\";
import { useRunningHeader } from \"@/hooks/useRunningHeader\";
import InputBar from \"@/components/InputBar\";
import { MultiplayerTurnBanner } from \"@/components/MultiplayerTurnBanner\";
import { useBreakpoint } from \"@/hooks/useBreakpoint\";
import { useImageBus } from \"@/providers/ImageBusProvider\";
import { useGameBoardLayout } from \"@/hooks/useGameBoardLayout\";
import { useGameBoardHotkeys } from \"@/hooks/useGameBoardHotkeys\";
import { TurnStatusPanel, type TurnStatusEntry } from \"@/components/TurnStatusPanel\";
import type { ResourceThreshold } from \"@/components/GenericResourceBar\";
import type { CharacterSheetData } from \"@/components/CharacterSheet\";
import type { InventoryData } from \"@/components/InventoryPanel\";
import type { MapState } from \"@/components/MapOverlay\";
import type { ConfrontationData } from \"@/components/ConfrontationOverlay\";
import type { KnowledgeEntry, ItemDepletion, ResourceAlert } from \"@/providers/GameStateProvider\";
import type { ResourcePool } from \"@/components/CharacterPanel\";
import type { CharacterSummary } from \"@/types/party\";
import type { useAudio } from \"@/hooks/useAudio\";
import type { NowPlaying } from \"@/hooks/useAudioCue\";
import type { GameMessage } from \"@/types/protocol\";
import type { DiceRequestPayload, DiceResultPayload, DiceThrowParams } from \"@/types/payloads\";
import type { LayoutMode } from \"@/hooks/useLayoutMode\";
import { WIDGET_REGISTRY, type WidgetId } from \"./widgetRegistry\";
import { BackgroundCanvas } from \"./BackgroundCanvas\";
import { MobileTabView } from \"./MobileTabView\";
import { NarrativeWidget } from \"./widgets/NarrativeWidget\";
import { CharacterWidget } from \"./widgets/CharacterWidget\";
import { MapWidget } from \"./widgets/MapWidget\";
import { InventoryWidget } from \"./widgets/InventoryWidget\";
// JournalWidget removed playtest 2026-04-11 — see widgetRegistry.ts comment.
// JournalView and the journal data pipeline are intentionally retained.
import { KnowledgeWidget } from \"./widgets/KnowledgeWidget\";
import { ConfrontationWidget } from \"./widgets/ConfrontationWidget\";
import { AudioWidget } from \"./widgets/AudioWidget\";
import { ImageGalleryWidget } from \"./widgets/ImageGalleryWidget\";
// ────────────────────────────────────────────────────────────────────────────
// Dockview closure bridge
//
// Dockview freezes the `component` reference at panel-creation time
// (see node_modules/dockview/dist/cjs/dockview/reactContentPart.js — the
// `ReactPanelContentPart` constructor stores `this.component = component`
// and the `update()` method only forwards new params, never a new component).
// So if we defined `PanelAdapter` inline with a `useCallback([renderWidgetContent])`
// dep, the adapter's closure over `renderWidgetContent` — and therefore over
// `messages`, `thinking`, `characterSheet`, etc. — would be locked in forever
// at the moment each panel was first added. Any subsequent setState in the
// parent would be invisible inside the dockview panel: the narrative panel
// wouldn't show new turns, the character panel wouldn't show HP changes,
// and so on. Refreshing the page would appear to \"fix\" it because sessionStorage
// hydration gave the first render the correct initial state.
//
// The fix is to make `PanelAdapter` and `dockviewComponents` module-level
// stable references
|
|
Complete job
Node.js 20 actions are deprecated. The following actions are running on Node.js 20 and may not work as expected: actions/checkout@v4, actions/setup-node@v4. Actions will be forced to run with Node.js 24 by default starting June 2nd, 2026. Node.js 20 will be removed from the runner on September 16th, 2026. Please check if updated versions of these actions are available that support Node.js 24. To opt into Node.js 24 now, set the FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true environment variable on the runner or in your workflow file. Once Node.js 24 becomes the default, you can temporarily opt out by setting ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true. For more information see: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/
|
|
Lint (no cache):
src/App.tsx#L1332
React Hook useEffect has a missing dependency: 'displayName'. Either include it or remove the dependency array
|
Loading