feat: add daily home and vitals dashboard prototypes

This commit is contained in:
2026-09-28 18:30:59 +09:00
parent 0ee5d181a5
commit e870bdfcbb
32 changed files with 4028 additions and 37 deletions
+1 -1
View File
@@ -41,7 +41,7 @@ test('server functions also reject foreign identity and cross-site calls', async
const serverRequest = page.waitForRequest((request) =>
request.url().includes('/_serverFn/'),
)
await page.goto('/')
await page.goto('/deck')
const url = (await serverRequest).url()
expect(
(
+2 -2
View File
@@ -3,7 +3,7 @@ import { expect, test } from '../fixtures'
test('opens account management after hydration without inheriting real OAuth configuration', async ({
page,
}) => {
await page.goto('/')
await page.goto('/deck')
await expect(
page.getByRole('button', { name: '名前を変更', includeHidden: true }),
).toBeEnabled()
@@ -51,7 +51,7 @@ test('rejects an unbound OAuth callback without contacting an instance', async (
page,
}) => {
await page.goto('/oauth/mastodon/callback?state=invalid&code=not-a-real-code')
await expect(page).toHaveURL(/\/\?mastodon=failed$/)
await expect(page).toHaveURL(/\/deck\?mastodon=failed$/)
await expect(page.getByRole('alert')).toContainText(
'Mastodonの認可を完了できませんでした。',
)
+2 -2
View File
@@ -40,7 +40,7 @@ async function addColumn(
}
test.beforeEach(async ({ page }) => {
await page.goto('/')
await page.goto('/deck')
await expect(
page.getByRole('button', { name: 'デッキとして保存', exact: true }),
).toBeEnabled()
@@ -301,7 +301,7 @@ test('keeps an open draft through remote edits and rejects its stale save', asyn
})
try {
const device = await other.newPage()
await device.goto('/')
await device.goto('/deck')
await page.getByRole('button', { name: '名前を変更' }).click()
await page.getByLabel('調査テーマ・デッキ名').fill('編集中の名前')
await device.getByRole('button', { name: '名前を変更' }).click()
+134
View File
@@ -0,0 +1,134 @@
import { expect, test } from '../fixtures'
test('previews a natural-language memo and shares the executed record with journal', async ({
page,
}) => {
await page.goto('/')
await page.waitForLoadState('networkidle')
const memo = '明日のレビューは導線から確認する'
await page
.getByRole('textbox', { name: 'ホームへの入力' })
.fill(`メモ: ${memo}`)
await page.getByRole('button', { name: '候補を表示' }).click()
const execute = page.getByRole('button', { name: '実行する', exact: true })
await expect(execute).toBeVisible()
await execute.click()
await page.getByRole('link', { name: '記録', exact: true }).first().click()
await expect(page.getByRole('article', { name: 'メモの記録' })).toContainText(
memo,
)
await page.getByRole('link', { name: 'ホーム', exact: true }).first().click()
await expect(
page.getByRole('textbox', { name: 'ホームへの入力' }),
).toBeVisible()
})
test('keeps the home usable on narrow screens', async ({
page,
a11y,
}, testInfo) => {
await page.goto('/')
await page.waitForLoadState('networkidle')
for (const width of [320, 375, 768]) {
await page.setViewportSize({ width, height: 850 })
await expect(
page.getByRole('textbox', { name: 'ホームへの入力' }),
).toBeVisible()
expect(
await page.evaluate(() => document.documentElement.scrollWidth),
).toBe(width)
await page.screenshot({ path: testInfo.outputPath(`home-${width}.png`) })
if (width === 320) expect((await a11y().analyze()).violations).toEqual([])
}
})
test('adds tasks only after confirmation and can defer and restore them', async ({
page,
}) => {
await page.goto('/')
await page.waitForLoadState('networkidle')
const input = page.getByRole('textbox', { name: 'ホームへの入力' })
const complete = page.getByRole('button', {
name: '買い物を完了',
exact: true,
})
await input.fill('買い物を追加')
await page.getByRole('button', { name: '候補を表示' }).click()
await expect(complete).toHaveCount(0)
await page.getByRole('button', { name: '実行する', exact: true }).click()
await expect(complete).toBeVisible()
await input.fill('買い物をあとで')
await page.getByRole('button', { name: '候補を表示' }).click()
await expect(complete).toBeVisible()
await page.getByRole('button', { name: '実行する', exact: true }).click()
await expect(complete).toHaveCount(0)
await page.locator('.home-rest summary').click()
await page.getByRole('button', { name: '買い物を戻す', exact: true }).click()
await complete.click()
await expect(complete).toHaveCount(0)
await page.locator('.home-rest summary').click()
await expect(
page.getByRole('button', { name: '買い物を戻す', exact: true }),
).toBeVisible()
})
test('resolves communication within home and carries the status to the shared queue', async ({
page,
a11y,
}) => {
await page.goto('/')
await page.waitForLoadState('networkidle')
await page.getByRole('button', { name: '青木 遥との会話を開く' }).click()
const dialog = page.getByRole('dialog', { name: '青木 遥との連絡' })
await expect(
dialog.getByRole('textbox', { name: '返信', exact: true }),
).toBeVisible()
expect((await a11y().analyze()).violations).toEqual([])
await dialog.getByRole('button', { name: '今は置いておく' }).click()
await expect(dialog).toHaveCount(0)
await expect(
page.getByRole('button', { name: '青木 遥との会話を開く' }),
).toHaveCount(0)
await page.getByRole('link', { name: '連絡', exact: true }).first().click()
const rows = page.locator('.support-row')
await expect(
rows.filter({ hasText: '来週のレビュー、日程を決める' }),
).toHaveCount(0)
await page.getByRole('button', { name: 'あとで', exact: true }).click()
await rows.filter({ hasText: '来週のレビュー、日程を決める' }).click()
await page.getByRole('button', { name: '今に戻す' }).click()
const back = page.getByRole('button', { name: '連絡一覧に戻る' })
if (await back.isVisible()) await back.click()
await page.getByRole('link', { name: 'ホーム', exact: true }).first().click()
await expect(
page.getByRole('button', { name: '青木 遥との会話を開く' }),
).toBeVisible()
})
test('updates the prose brief as priorities change and opens the referenced conversation', async ({
page,
}) => {
await page.goto('/')
await page.waitForLoadState('networkidle')
const brief = page.getByRole('region', { name: '今日のブリーフ' })
await expect(brief).toContainText('レビュー資料を確認する')
await expect(brief).toContainText('4件')
await page
.getByRole('button', { name: 'レビュー資料を確認するを完了', exact: true })
.click()
await expect(brief).not.toContainText('レビュー資料を確認する')
await brief.getByRole('button', { name: '青木 遥さんへの返信' }).click()
const dialog = page.getByRole('dialog', {
name: '青木 遥との連絡',
exact: true,
})
await dialog.getByRole('button', { name: '今は置いておく' }).click()
await expect(brief).toContainText('3件')
await expect(brief).not.toContainText('青木 遥')
await page
.getByRole('textbox', { name: 'ホームへの入力' })
.fill('再開地点:資料の2ページ目から')
await page.getByRole('button', { name: '候補を表示' }).click()
await page.getByRole('button', { name: '実行する', exact: true }).click()
await expect(brief).toContainText('資料の2ページ目から')
})
+4 -4
View File
@@ -10,7 +10,7 @@ import {
test('pushes the current research snapshot on each SSE connection', async ({
page,
}) => {
await page.goto('/')
await page.goto('/deck')
const snapshots = await page.evaluate(async () => {
const connect = () =>
new Promise<unknown>((resolve, reject) => {
@@ -41,7 +41,7 @@ test('pushes the current research snapshot on each SSE connection', async ({
test('restores the conversation and growing temporary deck from reconnect snapshots', async ({
page,
}) => {
await page.goto('/')
await page.goto('/deck')
await showDeckManagement(page)
await page.getByRole('button', { name: 'カラムを追加', exact: true }).click()
const accounts = page.getByRole('combobox', {
@@ -215,7 +215,7 @@ test('renders Markdown and reveals an unloaded citation in an offscreen deck col
a11y,
}) => {
await page.emulateMedia({ reducedMotion: 'reduce' })
await page.goto('/')
await page.goto('/deck')
await showDeckManagement(page)
await page.getByRole('button', { name: 'カラムを追加', exact: true }).click()
const accounts = page.getByRole('combobox', {
@@ -322,7 +322,7 @@ test('explains unconfigured Codex and prevents starting research without affecti
page,
a11y,
}) => {
await page.goto('/')
await page.goto('/deck')
const heading = page.getByRole('heading', { level: 1 })
await expect(heading).toBeVisible()
const originalTitle = await heading.textContent()
+115
View File
@@ -0,0 +1,115 @@
import { expect, test } from '../fixtures'
test('filters body systems and opens lab history', async ({ page, a11y }) => {
await page.goto('/vitals')
await page.waitForLoadState('networkidle')
await expect(
page.getByRole('heading', { name: 'Body', exact: true }),
).toBeVisible()
const systems = page.locator('.vital-system-card')
await expect(systems).toHaveCount(8)
await page
.locator('.vital-body-page > .vital-tabs')
.getByRole('button', { name: 'Cardiovascular', exact: true })
.click()
await expect(systems).toHaveCount(1)
await systems.first().click()
const dialog = page.getByRole('dialog', { name: 'Cardiovascular' })
await expect(dialog).toContainText('68')
await dialog
.getByRole('button', { name: '閉じる', exact: true })
.last()
.click()
await page.getByRole('button', { name: 'Lab history', exact: true }).click()
await expect(page.locator('.vital-history')).toContainText('Jul 28')
await expect(page.locator('.vital-history')).toContainText('93')
await page.getByRole('button', { name: 'Systems', exact: true }).click()
await page
.locator('.vital-body-page > .vital-tabs')
.getByRole('button', { name: 'Overview', exact: true })
.click()
await expect(systems).toHaveCount(8)
expect((await a11y().analyze()).violations).toEqual([])
})
test('explores environment charts and simulated climate controls', async ({
page,
a11y,
}) => {
await page.goto('/vitals')
await page.waitForLoadState('networkidle')
const menu = page.getByRole('button', { name: 'Healthメニューを開く' })
if (await menu.isVisible()) await menu.click()
await page
.getByRole('complementary', { name: 'Health navigation' })
.getByRole('button', { name: 'Environment', exact: true })
.click()
await expect(
page.getByRole('heading', { name: 'Environment', exact: true }),
).toBeVisible()
await page.getByRole('button', { name: '7D', exact: true }).click()
await page
.getByRole('group', { name: 'Trend metric' })
.getByRole('button', { name: 'CO₂', exact: true })
.click()
await expect(
page.getByRole('img', { name: '7D CO₂ simulated environmental readings' }),
).toBeVisible()
await page
.locator('.vital-metric')
.filter({ hasText: 'Room temperature' })
.click()
const dialog = page.getByRole('dialog', { name: 'Room temperature' })
await expect(dialog).toContainText('64.0')
await dialog.getByRole('button', { name: 'Back to overview' }).click()
await page.getByRole('button', { name: 'Sync now', exact: true }).click()
await expect(
page.getByRole('status').filter({ hasText: 'Sample readings refreshed' }),
).toBeVisible()
expect((await a11y().analyze()).violations).toEqual([])
await page
.getByRole('button', { name: 'Climate control', exact: true })
.click()
await page.getByRole('button', { name: 'Cool', exact: true }).click()
await expect(
page.getByRole('button', { name: 'Cool', exact: true }),
).toHaveAttribute('aria-pressed', 'true')
const temperature = page.getByRole('slider', { name: 'Target temperature' })
await temperature.focus()
await temperature.press('ArrowRight')
await expect(temperature).toHaveValue('69')
await page.getByRole('button', { name: 'Air purifier · On' }).click()
await expect(
page.getByRole('button', { name: 'Air purifier · Off' }),
).toHaveAttribute('aria-pressed', 'false')
})
test('keeps both dashboards within the viewport', async ({
page,
}, testInfo) => {
await page.goto('/vitals')
await page.waitForLoadState('networkidle')
for (const width of [375, 768, 1440]) {
await page.setViewportSize({ width, height: 1000 })
expect(
await page.evaluate(() => document.documentElement.scrollWidth),
).toBe(width)
await page.screenshot({ path: testInfo.outputPath(`body-${width}.png`) })
const menu = page.getByRole('button', { name: 'Healthメニューを開く' })
if (await menu.isVisible()) await menu.click()
const navigation = page.getByRole('complementary', {
name: 'Health navigation',
})
await navigation
.getByRole('button', { name: 'Environment', exact: true })
.click()
expect(
await page.evaluate(() => document.documentElement.scrollWidth),
).toBe(width)
await page.screenshot({
path: testInfo.outputPath(`environment-${width}.png`),
})
if (await menu.isVisible()) await menu.click()
await navigation.getByRole('button', { name: 'Body', exact: true }).click()
}
})
+2 -2
View File
@@ -64,7 +64,7 @@ let pageErrors: string[]
test.beforeEach(async ({ page }) => {
pageErrors = []
page.on('pageerror', (error) => pageErrors.push(error.message))
await page.goto('/')
await page.goto('/deck')
await expect
.poll(() => toolNames(page))
.toEqual([
@@ -141,7 +141,7 @@ test('creates temporary research, edits it, persists explicitly and reopens it o
})
try {
const device = await other.newPage()
await device.goto('/')
await device.goto('/deck')
await expect(device.getByRole('heading', { level: 1 })).toHaveText(
'更新した調査',
)
+1 -1
View File
@@ -5,7 +5,7 @@ test('keeps the sidebar, composer and deck reachable at compact widths', async (
page,
a11y,
}, testInfo) => {
await page.goto('/')
await page.goto('/deck')
for (const width of [320, 375, 414, 768]) {
await page.setViewportSize({ width, height: 850 })
await showResearch(page)