Files
hapi/scripts/dev/session-view-toggles-handoff.mjs
b1910b6b2e feat(web): session header files and outline view toggles (#952)
* feat(web): session header files and outline view toggles

Files and outline icons in SessionHeader act as depressed toggles; files
view shares the session header and places refresh beside the search box.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(web): add Playwright handoff script for session view toggles

Supports new-feature-intake visual gate: files toggle pressed + refresh beside search.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(dev): handoff script avoid networkidle on live hub SSE

HAPI keeps connections open on :3006; domcontentloaded is the correct wait.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web): place filesystem refresh outside search field

Refresh is a sibling of the search pill, not inside it, so the control
is visually and structurally separate from file search.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-19 17:36:36 +08:00

91 lines
3.6 KiB
JavaScript

#!/usr/bin/env node
/**
* Playwright handoff for session header view toggles (files + outline).
* Usage: node scripts/dev/session-view-toggles-handoff.mjs <sessionId> <cliApiToken> [screenshotPath]
*/
import { chromium } from 'playwright'
import { mkdirSync } from 'node:fs'
import { dirname, resolve } from 'node:path'
const sessionId = process.argv[2]
const cliToken = process.argv[3]
const screenshotPath = resolve(process.argv[4] ?? 'localdocs/playwright-runs/session-view-toggles-handoff.png')
if (!sessionId || !cliToken) {
console.error('usage: session-view-toggles-handoff.mjs <sessionId> <cliApiToken> [screenshotPath]')
process.exit(2)
}
function launchOptions() {
const chromePath = process.env.PLAYWRIGHT_CHROME_PATH?.trim()
if (chromePath) return { headless: true, executablePath: chromePath }
if (process.platform === 'linux' && !process.env.PLAYWRIGHT_BUNDLED_CHROMIUM) {
return { headless: true, channel: 'chrome' }
}
return { headless: true }
}
const baseUrl = 'http://127.0.0.1:3006'
const storageKey = `hapi_access_token::${baseUrl}`
const url = `${baseUrl}/sessions/${sessionId}`
const browser = await chromium.launch(launchOptions())
const context = await browser.newContext({
viewport: { width: 1440, height: 900 },
serviceWorkers: 'block',
})
const page = await context.newPage()
await page.addInitScript(({ key, token }) => {
localStorage.setItem(key, token)
}, { key: storageKey, token: cliToken })
const consoleMessages = []
page.on('console', (msg) => consoleMessages.push(`${msg.type()}: ${msg.text()}`))
page.on('pageerror', (err) => consoleMessages.push(`pageerror: ${err.message}`))
try {
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 })
const login = page.getByPlaceholder('Access token')
if (await login.isVisible({ timeout: 3000 }).catch(() => false)) {
await login.fill(cliToken)
await page.getByRole('button', { name: /sign in|login|connect/i }).click()
await page.waitForLoadState('domcontentloaded', { timeout: 60000 })
}
await page.getByRole('button', { name: 'Files' }).first().waitFor({ state: 'visible', timeout: 60000 })
// Toggle into files mode — button should become pressed.
await page.getByRole('button', { name: 'Files' }).first().click()
await page.getByPlaceholder('Search files').waitFor({ timeout: 30000 })
await page.getByRole('button', { name: 'Refresh filesystem view' }).waitFor({ timeout: 10000 })
const filesBtn = page.getByRole('button', { name: 'Return to conversation' })
await filesBtn.waitFor({ timeout: 5000 })
const pressed = await filesBtn.getAttribute('aria-pressed')
if (pressed !== 'true') {
throw new Error(`Expected files toggle aria-pressed=true, got ${pressed}`)
}
mkdirSync(dirname(screenshotPath), { recursive: true })
await page.screenshot({ path: screenshotPath, fullPage: false })
console.log(JSON.stringify({
ok: true,
screenshot: screenshotPath,
url: page.url().replace(/token=[^&]+/, 'token=<redacted>'),
filesTogglePressed: pressed,
}, null, 2))
} catch (error) {
mkdirSync(dirname(screenshotPath), { recursive: true })
await page.screenshot({ path: screenshotPath, fullPage: false }).catch(() => {})
console.error(JSON.stringify({
ok: false,
error: error instanceof Error ? error.message : String(error),
screenshot: screenshotPath,
bodyText: (await page.locator('body').innerText().catch(() => '')).slice(0, 500),
consoleMessages,
}, null, 2))
process.exitCode = 1
} finally {
await browser.close()
}