Wave 2 of the 2026-09-28 design overhaul ("records are the main
character, a quiet instrument"). Decisions: design.md.
- Shell: sidebar with primary Home / Records / Meetings (Ctrl+1..3) and a
secondary "Tools" group; every item is a button with aria-current.
The status bar is removed — its "100% LOCAL PRIVACY · ZERO CLOUD SYNC"
claim contradicted device sync; the sidebar shows only queried state.
- Navigation context (routes, params, back, useRouteRequest) so Home can
open a specific meeting or record.
- Home replaces the dashboard: dictation key, start actions, only real
problems, one-line summary, recent records and meetings, file
transcription, free-tier limits.
- Settings become a full-screen route with vertical tabs (tabs no longer
clip) and a shared row grammar; long-standing bugs fixed: team and
enterprise tiers crashed the license tab (currentTier undefined),
first-run onboarding could not be left, Pro+ saw "upgrade to Pro+".
- Records: date-grouped single list with in-place expansion, search and
tags together, mode/favorite filters, load more past 50, undoable
delete. Meetings: list with search/rename/delete, summary-first detail
(summary | transcript | documents | my notes), notes persisted locally
instead of being lost. Tools pages cleaned up; stale-closure example
chips and a 1550x mouse-distance bug fixed.
- Shared pieces: PageHeader, EmptyStateCard, SearchInput ("/" works),
ConfirmDialog, notify with undo; display-labels maps domain values to
typed i18n keys so missing keys fail to compile.
- Mono font stack falls back to Pretendard for Hangul (Korean labels no
longer render spaced out).
- i18n: new ko/en keys via scripts/i18n-add-keys.mjs (one read-merge-
write so parallel editors cannot clobber locale files).
Some renderer files also carried pre-existing in-flight changes (error
recovery deep links, meeting document hooks); they are included as-is.
82 lines
2.6 KiB
TypeScript
82 lines
2.6 KiB
TypeScript
import { test, expect, _electron as electron } from '@playwright/test';
|
|
|
|
test.describe('History Page E2E', () => {
|
|
let electronApp: any;
|
|
let window: any;
|
|
|
|
test.beforeAll(async () => {
|
|
electronApp = await electron.launch({
|
|
args: ['out/main/index.js'],
|
|
env: {
|
|
...process.env,
|
|
NODE_ENV: 'test',
|
|
},
|
|
recordVideo: {
|
|
dir: 'test-results/videos/',
|
|
size: { width: 1280, height: 720 }
|
|
}
|
|
});
|
|
|
|
// Wait for the main window to be ready
|
|
let found = false;
|
|
for (const w of electronApp.windows()) {
|
|
await w.waitForLoadState('domcontentloaded');
|
|
const t = await w.title();
|
|
if (t === 'D3RO Voice' || t === 'd3ro-voice') {
|
|
window = w;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
window = await electronApp.waitForEvent('window', {
|
|
predicate: async (page) => {
|
|
await page.waitForLoadState('domcontentloaded');
|
|
const t = await page.title();
|
|
return t === 'D3RO Voice' || t === 'd3ro-voice';
|
|
}
|
|
});
|
|
}
|
|
|
|
// Bypass onboarding using the injected IPC API
|
|
await window.evaluate(async () => {
|
|
if ((window as any).electronAPI) {
|
|
await (window as any).electronAPI.config.set({ key: 'onboardingCompleted', value: true });
|
|
await (window as any).electronAPI.config.set({ key: 'system.startupBackend', value: 'online' });
|
|
}
|
|
});
|
|
|
|
// Reload to apply the bypassed config
|
|
await window.reload();
|
|
await window.waitForLoadState('domcontentloaded');
|
|
});
|
|
|
|
test.afterAll(async () => {
|
|
if (electronApp) {
|
|
await electronApp.close();
|
|
}
|
|
});
|
|
|
|
test('should clear all history', async () => {
|
|
// Navigate to Records (sidebar label "기록"/"Records")
|
|
await window.locator('text=/^(Records|기록)$/').first().click();
|
|
await expect(window.getByRole('heading', { level: 1, name: /^(Records|기록)$/ })).toBeVisible();
|
|
|
|
// 전체 삭제는 더보기 메뉴 안의 위험 행동 → 확인 대화상자를 거친다
|
|
await window.getByTestId('records-more-button').click();
|
|
const clearButton = window.getByTestId('clear-all-history-button');
|
|
await expect(clearButton).toBeVisible();
|
|
await clearButton.click();
|
|
|
|
// 기록이 있으면 확인 대화상자가 뜬다 — "모두 삭제"로 확정
|
|
const confirm = window.getByRole('button', { name: /^(Delete all|모두 삭제)$/ });
|
|
await confirm
|
|
.waitFor({ state: 'visible', timeout: 3000 })
|
|
.then(() => confirm.click())
|
|
.catch(() => undefined);
|
|
|
|
// Expect empty state to appear
|
|
await expect(window.getByTestId('empty-state-card')).toBeVisible();
|
|
});
|
|
});
|