fix: stabilize the live recommendation path
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
bfea9efb5e
commit
dbc5c2d93f
12 changed files with 113 additions and 31 deletions
|
|
@ -48,7 +48,7 @@ const modeLabel = computed(() => {
|
|||
<span class="mode" :class="{ failed: healthFailed }">
|
||||
{{ modeLabel }}
|
||||
</span>
|
||||
<a v-if="demoUrl" class="demo-link" :href="demoUrl">
|
||||
<a v-if="demoUrl" class="button demo-link" :href="demoUrl">
|
||||
{{ messages.appHeaderDemoInstance }}
|
||||
</a>
|
||||
<AuthStatus :auth="auth" @logout="$emit('logout')" />
|
||||
|
|
@ -146,21 +146,19 @@ const modeLabel = computed(() => {
|
|||
}
|
||||
|
||||
.demo-link {
|
||||
color: var(--c-text-chip);
|
||||
text-transform: uppercase;
|
||||
text-decoration: none;
|
||||
border-bottom: 1px solid var(--c-line-strong);
|
||||
}
|
||||
|
||||
.demo-link:hover {
|
||||
color: var(--c-text);
|
||||
border-color: var(--c-accent);
|
||||
}
|
||||
|
||||
.button {
|
||||
display: inline-flex;
|
||||
flex: none;
|
||||
align-items: center;
|
||||
min-height: 31px;
|
||||
padding: 6px 11px;
|
||||
color: var(--c-text-muted);
|
||||
font-family: var(--f-mono);
|
||||
font-size: var(--t-micro);
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border: 1px solid var(--c-line);
|
||||
|
|
@ -169,6 +167,7 @@ const modeLabel = computed(() => {
|
|||
|
||||
.button:hover {
|
||||
color: var(--c-text);
|
||||
text-decoration: none;
|
||||
border-color: var(--c-focus-border);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ const logoutLabel = computed(() => {
|
|||
>{{ auth.user.display_name }}
|
||||
</span>
|
||||
<button
|
||||
v-if="auth.user.can_logout"
|
||||
class="button"
|
||||
type="button"
|
||||
:disabled="auth.status === 'logging_out'"
|
||||
|
|
|
|||
|
|
@ -9,10 +9,14 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|||
}
|
||||
|
||||
function parseCurrentUser(value: unknown): CurrentUser {
|
||||
if (!isRecord(value) || typeof value.display_name !== 'string') {
|
||||
if (
|
||||
!isRecord(value) ||
|
||||
typeof value.display_name !== 'string' ||
|
||||
typeof value.can_logout !== 'boolean'
|
||||
) {
|
||||
throw new Error(messages.useApiInvalidCurrentUser)
|
||||
}
|
||||
return { display_name: value.display_name }
|
||||
return { display_name: value.display_name, can_logout: value.can_logout }
|
||||
}
|
||||
|
||||
function parsePlaylistResponse(value: unknown): { url: string | null } {
|
||||
|
|
|
|||
|
|
@ -76,4 +76,5 @@ export interface PlaylistCreateResponse {
|
|||
|
||||
export interface CurrentUser {
|
||||
display_name: string
|
||||
can_logout: boolean
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,6 +85,48 @@ describe('result presentation', () => {
|
|||
expect(onReset).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('renders demo as a header button and hides logout for a seeded user', () => {
|
||||
const demoUrl = 'https://demo.example.com'
|
||||
const root = mountComponent(AppHeader, {
|
||||
auth: {
|
||||
status: 'authenticated',
|
||||
user: { display_name: 'Seed Listener', can_logout: false },
|
||||
message: null,
|
||||
},
|
||||
mode: 'live',
|
||||
healthFailed: false,
|
||||
demoUrl,
|
||||
})
|
||||
const demoLink = root.querySelector<HTMLAnchorElement>('a.demo-link')
|
||||
|
||||
expect(demoLink?.classList.contains('button')).toBe(true)
|
||||
expect(demoLink?.href).toBe(`${demoUrl}/`)
|
||||
expect(demoLink?.textContent).toContain('demo')
|
||||
expect(root.textContent).toContain('Seed Listener')
|
||||
expect(root.textContent).not.toContain('Log out')
|
||||
})
|
||||
|
||||
it('keeps logout available for an interactive local user', () => {
|
||||
const onLogout = vi.fn()
|
||||
const root = mountComponent(AppHeader, {
|
||||
auth: {
|
||||
status: 'authenticated',
|
||||
user: { display_name: 'Local Listener', can_logout: true },
|
||||
message: null,
|
||||
},
|
||||
mode: 'live',
|
||||
healthFailed: false,
|
||||
demoUrl: null,
|
||||
onLogout,
|
||||
})
|
||||
const logout = [...root.querySelectorAll<HTMLButtonElement>('button')].find((button) =>
|
||||
button.textContent?.includes('Log out'),
|
||||
)
|
||||
|
||||
logout?.click()
|
||||
expect(onLogout).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('keeps completion context outside the focusable track region', () => {
|
||||
const root = mountComponent(AssistantMessage, { turn: doneTurn(), canSave: true })
|
||||
const region = root.querySelector<HTMLElement>(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue