Merge branch 'main' into main

This commit is contained in:
github-actions[bot]
2026-04-29 08:42:58 +00:00
committed by GitHub
2 changed files with 70 additions and 28 deletions

View File

@@ -1,7 +1,8 @@
import { describe, it, expect, vi } from 'vitest'
import { render, screen, fireEvent, act } from '@testing-library/react'
import { render as rtlRender, screen, fireEvent, act } from '@testing-library/react'
import { AiPanel } from './AiPanel'
import { UNSUPPORTED_INLINE_PASTE_MESSAGE } from './InlineWikilinkInput'
import { TooltipProvider } from '@/components/ui/tooltip'
import type { VaultEntry } from '../types'
import { queueAiPrompt } from '../utils/aiPromptBridge'
import { bindVaultConfigStore, getVaultConfig, resetVaultConfigStore } from '../utils/vaultConfigStore'
@@ -67,6 +68,10 @@ const makeEntry = (overrides: Partial<VaultEntry> = {}): VaultEntry => ({
...overrides,
})
function render(ui: Parameters<typeof rtlRender>[0]) {
return rtlRender(ui, { wrapper: TooltipProvider })
}
describe('AiPanel', () => {
beforeEach(() => {
mockMessages = []
@@ -127,7 +132,7 @@ describe('AiPanel', () => {
}]
render(<AiPanel onClose={vi.fn()} vaultPath="/tmp/vault" />)
fireEvent.click(screen.getByRole('button', { name: 'Power User' }))
fireEvent.click(screen.getByRole('radio', { name: 'Power User' }))
expect(getVaultConfig().ai_agent_permission_mode).toBe('power_user')
expect(save).toHaveBeenLastCalledWith(expect.objectContaining({
@@ -143,8 +148,26 @@ describe('AiPanel', () => {
render(<AiPanel onClose={vi.fn()} vaultPath="/tmp/vault" />)
expect(screen.getByRole('button', { name: 'Vault Safe' })).toBeDisabled()
expect(screen.getByRole('button', { name: 'Power User' })).toBeDisabled()
expect(screen.getByRole('radio', { name: 'Vault Safe' })).toBeDisabled()
expect(screen.getByRole('radio', { name: 'Power User' })).toBeDisabled()
})
it('renders the permission mode toggle with high contrast selected state and explanatory tooltip', async () => {
render(<AiPanel onClose={vi.fn()} vaultPath="/tmp/vault" />)
expect(screen.getByTestId('ai-permission-mode-toggle')).toHaveClass('border', 'bg-muted')
const safeMode = screen.getByRole('radio', { name: 'Vault Safe' })
const powerUserMode = screen.getByRole('radio', { name: 'Power User' })
expect(safeMode).toHaveAttribute('aria-checked', 'true')
expect(safeMode).toHaveClass('bg-background', 'text-foreground', 'shadow-xs')
expect(powerUserMode).toHaveClass('text-muted-foreground')
fireEvent.focus(safeMode)
expect(await screen.findByTestId('ai-permission-mode-tooltip')).toHaveTextContent(
'Vault Safe keeps agents to file, search, and edit tools. Power User also allows local shell commands for this vault.',
)
})
it('renders data-testid ai-panel', () => {

View File

@@ -3,6 +3,8 @@ import { Robot, X, PaperPlaneRight, Plus, Link } from '@phosphor-icons/react'
import { Copy } from 'lucide-react'
import { AiMessage } from './AiMessage'
import { Button } from '@/components/ui/button'
import { ActionTooltip } from '@/components/ui/action-tooltip'
import { TooltipProvider } from '@/components/ui/tooltip'
import { WikilinkChatInput } from './WikilinkChatInput'
import { extractInlineWikilinkReferences } from './inlineWikilinkText'
import {
@@ -14,6 +16,10 @@ import type { AiAgentReadiness } from '../lib/aiAgents'
import type { NoteReference } from '../utils/ai-context'
import type { VaultEntry } from '../types'
const AI_PERMISSION_MODE_TOOLTIP = {
label: 'Vault Safe keeps agents to file, search, and edit tools. Power User also allows local shell commands for this vault.',
}
interface AiPanelHeaderProps {
agentLabel: string
agentReadiness: AiAgentReadiness
@@ -210,30 +216,43 @@ function AiPermissionModeToggle({
onChange: (mode: AiAgentPermissionMode) => void
}) {
return (
<div
className="grid rounded-md bg-muted"
style={{ gridTemplateColumns: '1fr 1fr', gap: 2, padding: 2 }}
role="group"
aria-label="AI agent permission mode"
data-testid="ai-permission-mode-toggle"
>
{(['safe', 'power_user'] as const).map((mode) => {
const selected = value === mode
return (
<Button
key={mode}
type="button"
size="xs"
variant={selected ? 'secondary' : 'ghost'}
disabled={disabled}
aria-pressed={selected}
onClick={() => onChange(mode)}
>
{AI_AGENT_PERMISSION_MODE_LABELS[mode].control}
</Button>
)
})}
</div>
<TooltipProvider>
<div
className="inline-flex w-full rounded-md border border-border bg-muted p-1"
role="radiogroup"
aria-label="AI agent permission mode"
data-testid="ai-permission-mode-toggle"
>
{(['safe', 'power_user'] as const).map((mode) => {
const selected = value === mode
return (
<ActionTooltip
key={mode}
copy={AI_PERMISSION_MODE_TOOLTIP}
side="bottom"
contentTestId="ai-permission-mode-tooltip"
>
<Button
type="button"
size="sm"
variant="ghost"
role="radio"
aria-checked={selected}
disabled={disabled}
className={
selected
? 'h-7 flex-1 border border-border bg-background text-foreground shadow-xs hover:bg-background'
: 'h-7 flex-1 text-muted-foreground hover:text-foreground'
}
onClick={() => onChange(mode)}
>
{AI_AGENT_PERMISSION_MODE_LABELS[mode].control}
</Button>
</ActionTooltip>
)
})}
</div>
</TooltipProvider>
)
}