feat: окно инструкций, свёрнутая история и обновление спонсоров

Инструкции вынесены в отдельное окно Electron; токены игроков не пропадают при смене сцены; предпросмотр и плейлисты адаптированы; история свёрнута по умолчанию.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-09-17 06:21:42 +08:00
parent 712086ddca
commit a29c72b307
24 changed files with 506 additions and 290 deletions
@@ -18,6 +18,7 @@ const SECONDARY_WINDOW_MAINS = [
'dice/main.tsx',
'sceneEditor/main.tsx',
'sceneDescription/main.tsx',
'instructions/main.tsx',
'control/main.tsx',
'presentation/main.tsx',
] as const;
@@ -119,6 +120,24 @@ void test('Control and Presentation use SceneNpcTokensOverlay', () => {
assert.ok(control.includes('ScenePlayerTokensOverlay'));
assert.ok(presentation.includes('ScenePlayerTokensOverlay'));
assert.ok(control.includes('toggle-session-players'));
assert.match(presentation, /<SessionTimerLayer viewport=\{markerViewport\} editable=\{false\} \/>/);
});
void test('useScenePlayerTokensSession: scene-switch broadcast is not gated by optimistic local revision', () => {
const src = fs.readFileSync(
path.join(rendererRoot, 'shared/playerToken/useScenePlayerTokensSession.ts'),
'utf8',
);
assert.ok(src.includes('serverRevRef'));
assert.doesNotMatch(src, /localRevRef/);
assert.doesNotMatch(src, /localRevRef\.current = Math\.max/);
});
void test('EditorApp: Instructions opens electron window, not modal', () => {
const src = fs.readFileSync(path.join(rendererRoot, 'editor/EditorApp.tsx'), 'utf8');
assert.ok(src.includes('windows.openInstructions'));
assert.doesNotMatch(src, /InstructionsModal/);
assert.ok(fs.existsSync(path.join(rendererRoot, 'instructions/InstructionsApp.tsx')));
});
void test('EditorApp: split Run button and launch-with-players modal', () => {
@@ -143,11 +162,24 @@ void test('ControlApp: Playlists tool opens window', () => {
assert.ok(src.includes('windows.openPlaylists'));
});
void test('PlaylistsPanel: long audio titles ellipsis via CSS', () => {
const css = fs.readFileSync(
path.join(rendererRoot, 'shared/playlists/PlaylistsPanel.module.css'),
'utf8',
);
const panel = fs.readFileSync(path.join(rendererRoot, 'shared/playlists/PlaylistsPanel.tsx'), 'utf8');
assert.match(css, /\.trackTitleReadonly[\s\S]*?text-overflow:\s*ellipsis/);
assert.match(css, /\.trackTitle[\s\S]*?text-overflow:\s*ellipsis/);
assert.ok(panel.includes('trackTitleReadonly'));
assert.ok(panel.includes('title={title}'));
});
void test('ControlApp: Timer tool opens window', () => {
const src = fs.readFileSync(path.join(rendererRoot, 'control/ControlApp.tsx'), 'utf8');
assert.ok(src.includes('data-testid="control-timer-tool"'));
assert.ok(src.includes('windows.openTimer'));
assert.ok(src.includes('SessionTimerLayer'));
assert.match(src, /<SessionTimerLayer viewport=\{markerViewport\} editable \/>/);
});
void test('ControlApp: Initiative tool opens window', () => {