mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-29 07:20:43 +00:00
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
61 lines
2.4 KiB
TypeScript
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()
|
|
})
|