Files
hanzo.ai/e2e/bookmark.spec.ts
zeekay 79aa0c3910 Recents: a conversation's menu pins, renames, archives and deletes it, by right-click or its options button
The app gate drives the row menu in light and in dark against a store that
answers as hanzoai/agent v1.0.12 does: a right-click opens it at the cursor and
the options button, from the keyboard, under itself; Pin lists the
conversation first under Pinned, Rename writes one title, Archive moves it to
the view's Archived status and back, and Delete asks before it takes the
conversation for good. The header's pin is the same pin. The Recents view menu
has no Starred group now, and the unit spec reads pins and the archived status.

Waits on @hanzo/rooms 0.1.35 for the menu itself; @hanzo/ai moves to 0.6.23 for
the thread update and remove it calls.

Claude-Session: https://claude.ai/code/session_01SCPMGSQsLNF2YtHFL4bwyp
2026-10-09 01:38:14 -07:00

61 lines
2.4 KiB
TypeScript

import { expect, test } from '@playwright/test'
import { signIn } from './session'
import { sidebar } from './app'
/**
* A PINNED CONVERSATION LEADS THE LIST, IN A BAND OF ITS OWN.
*
* The rest of that column is banded by WHEN, so a pinned conversation kept in
* its day would be findable only by remembering the day — which is the thing
* pinning exists to avoid. The pin is the server's (PATCH
* /v1/agents/chat/conversations/{id}), so it follows the person to every device.
*
* The pin is pressed where the conversation is read and the column shows it,
* so what this asserts is that one action reaches both.
*/
const SEED = 'gate-pin-thread'
const TRANSCRIPT = {
id: SEED,
messages: [{ id: 'm1', role: 'user', content: 'Ping' }],
}
test('pinning a conversation gives it a band above the days', async ({ page }) => {
await signIn(page)
const json = (body: unknown) => ({
status: 200,
contentType: 'application/json',
body: JSON.stringify(body),
})
let pinned = false
// TWO ROUTES, and the band needs the second: the transcript is what the pane
// reads, the LIST is what the column bands. Answering only the first pins a
// conversation the column has never heard of.
await page.route('**/v1/agents/chat/conversations/**', (r) => {
if (r.request().method() === 'PATCH') {
pinned = (r.request().postDataJSON() as { pinned?: boolean }).pinned ?? pinned
return r.fulfill(json({ id: SEED, title: 'Gate conversation', pinned }))
}
return r.fulfill(json(TRANSCRIPT))
})
await page.route('**/v1/agents/chat/conversations', (r) =>
r.fulfill(json({ conversations: [{ id: SEED, title: 'Gate conversation', updatedAt: new Date().toISOString(), pinned }] })),
)
await page.setViewportSize({ width: 1440, height: 900 })
await page.goto(`/chat?thread=${SEED}`)
await page.waitForLoadState('networkidle')
const pin = page.getByRole('button', { name: 'Pin this conversation' })
await expect(pin).toBeVisible({ timeout: 15_000 })
// No band before there is anything in it.
await sidebar(page)
await page.waitForTimeout(900)
await expect(page.getByText('Pinned', { exact: true })).toHaveCount(0)
await pin.click()
// The same act, read in the column.
await expect(page.getByText('Pinned', { exact: true })).toBeVisible({ timeout: 10_000 })
await expect(page.getByRole('button', { name: 'Unpin this conversation' })).toBeVisible()
})