fix: float Git branch picker above workspace content
Render the searchable branch picker in the shared floating portal so opening it no longer pushes down changes or commit history. Clamp it to the viewport and retain search focus, outside dismissal, and Escape focus restoration. Update existing migration fixtures for activity history and supervision schema changes. Focused suites passed: 149 storage tests, 41 UI tests, and the Electron overlay test; typecheck and lint passed. The full suite did not complete cleanly and reported failures outside this change. Release note: The Git branch picker now opens over the workspace without displacing the file list or commit history.
This commit is contained in:
@@ -168,7 +168,11 @@ it('migrates schema 35 conversations to unpinned while retaining existing data a
|
||||
const original = database.getConversation(header.id)
|
||||
database.close()
|
||||
const legacy = new DatabaseSync(path)
|
||||
legacy.exec('ALTER TABLE conversations DROP COLUMN pinned; PRAGMA user_version = 35;')
|
||||
legacy.exec(`ALTER TABLE conversations DROP COLUMN pinned;
|
||||
ALTER TABLE supervision_results DROP COLUMN graph_snapshot_json;
|
||||
DROP TABLE activity_history_records;
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
PRAGMA user_version = 35;`)
|
||||
legacy.close()
|
||||
try {
|
||||
database.initialize('C:\\Workspace')
|
||||
@@ -311,6 +315,8 @@ it('preserves existing supervision data when upgrading schema 41 and reopening',
|
||||
ALTER TABLE supervision_entity_changes DROP COLUMN source_reference_ids_json;
|
||||
ALTER TABLE supervision_relations DROP COLUMN source_reference_ids_json;
|
||||
ALTER TABLE supervision_results DROP COLUMN graph_snapshot_json;
|
||||
DROP TABLE activity_history_records;
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
PRAGMA user_version = 41;`)
|
||||
legacy.close()
|
||||
try {
|
||||
@@ -1068,6 +1074,9 @@ describe('AssistantDatabase', () => {
|
||||
DROP TABLE model_usage_calls;
|
||||
ALTER TABLE projects DROP COLUMN runtime_selection_json;
|
||||
ALTER TABLE conversations DROP COLUMN pinned;
|
||||
ALTER TABLE supervision_results DROP COLUMN graph_snapshot_json;
|
||||
DROP TABLE activity_history_records;
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
PRAGMA user_version = 3;
|
||||
`)
|
||||
oldDatabase.close()
|
||||
@@ -1225,6 +1234,9 @@ describe('AssistantDatabase', () => {
|
||||
legacy.exec(`
|
||||
DROP TABLE project_execution_spaces;
|
||||
ALTER TABLE conversations DROP COLUMN pinned;
|
||||
ALTER TABLE supervision_results DROP COLUMN graph_snapshot_json;
|
||||
DROP TABLE activity_history_records;
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
PRAGMA user_version = 26;
|
||||
`)
|
||||
legacy.close()
|
||||
@@ -1335,6 +1347,9 @@ describe('AssistantDatabase', () => {
|
||||
runtime_bundle_digest TEXT NOT NULL
|
||||
);
|
||||
ALTER TABLE conversations DROP COLUMN pinned;
|
||||
ALTER TABLE supervision_results DROP COLUMN graph_snapshot_json;
|
||||
DROP TABLE activity_history_records;
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
PRAGMA user_version = 30;
|
||||
`)
|
||||
legacy.close()
|
||||
@@ -1407,6 +1422,9 @@ describe('AssistantDatabase', () => {
|
||||
(project_id, runtime_bundle_digest)
|
||||
VALUES ('${project.id}', 'sha256:${'d'.repeat(64)}');
|
||||
ALTER TABLE conversations DROP COLUMN pinned;
|
||||
ALTER TABLE supervision_results DROP COLUMN graph_snapshot_json;
|
||||
DROP TABLE activity_history_records;
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
PRAGMA user_version = 30;
|
||||
`)
|
||||
legacy.close()
|
||||
@@ -1498,6 +1516,9 @@ describe('AssistantDatabase', () => {
|
||||
DROP INDEX projects_built_in_default_unique;
|
||||
ALTER TABLE projects DROP COLUMN built_in_default;
|
||||
ALTER TABLE conversations DROP COLUMN pinned;
|
||||
ALTER TABLE supervision_results DROP COLUMN graph_snapshot_json;
|
||||
DROP TABLE activity_history_records;
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
PRAGMA user_version = 24;
|
||||
`)
|
||||
legacy.close()
|
||||
@@ -1539,6 +1560,9 @@ describe('AssistantDatabase', () => {
|
||||
DROP INDEX projects_built_in_default_unique;
|
||||
ALTER TABLE projects DROP COLUMN built_in_default;
|
||||
ALTER TABLE conversations DROP COLUMN pinned;
|
||||
ALTER TABLE supervision_results DROP COLUMN graph_snapshot_json;
|
||||
DROP TABLE activity_history_records;
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
PRAGMA user_version = 24;
|
||||
`)
|
||||
legacy.close()
|
||||
@@ -1581,6 +1605,9 @@ describe('AssistantDatabase', () => {
|
||||
DROP INDEX projects_built_in_default_unique;
|
||||
ALTER TABLE projects DROP COLUMN built_in_default;
|
||||
ALTER TABLE conversations DROP COLUMN pinned;
|
||||
ALTER TABLE supervision_results DROP COLUMN graph_snapshot_json;
|
||||
DROP TABLE activity_history_records;
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
PRAGMA user_version = 24;
|
||||
`)
|
||||
legacy.close()
|
||||
@@ -1619,6 +1646,9 @@ describe('AssistantDatabase', () => {
|
||||
DROP INDEX projects_built_in_default_unique;
|
||||
ALTER TABLE projects DROP COLUMN built_in_default;
|
||||
ALTER TABLE conversations DROP COLUMN pinned;
|
||||
ALTER TABLE supervision_results DROP COLUMN graph_snapshot_json;
|
||||
DROP TABLE activity_history_records;
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
PRAGMA user_version = 24;
|
||||
`)
|
||||
legacy.close()
|
||||
@@ -1646,6 +1676,9 @@ describe('AssistantDatabase', () => {
|
||||
DROP INDEX projects_built_in_default_unique;
|
||||
ALTER TABLE projects DROP COLUMN built_in_default;
|
||||
ALTER TABLE conversations DROP COLUMN pinned;
|
||||
ALTER TABLE supervision_results DROP COLUMN graph_snapshot_json;
|
||||
DROP TABLE activity_history_records;
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
PRAGMA user_version = 24;
|
||||
`)
|
||||
legacy.close()
|
||||
@@ -1670,6 +1703,9 @@ describe('AssistantDatabase', () => {
|
||||
versionFive.exec(`
|
||||
DROP TABLE computer_control_actions;
|
||||
ALTER TABLE conversations DROP COLUMN pinned;
|
||||
ALTER TABLE supervision_results DROP COLUMN graph_snapshot_json;
|
||||
DROP TABLE activity_history_records;
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
PRAGMA user_version = 5;
|
||||
`)
|
||||
versionFive.close()
|
||||
@@ -1791,6 +1827,9 @@ describe('AssistantDatabase', () => {
|
||||
raw.exec(`
|
||||
DROP INDEX IF EXISTS idx_tasks_schedule;
|
||||
ALTER TABLE conversations DROP COLUMN pinned;
|
||||
ALTER TABLE supervision_results DROP COLUMN graph_snapshot_json;
|
||||
DROP TABLE activity_history_records;
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
PRAGMA user_version = 21;
|
||||
COMMIT;
|
||||
`)
|
||||
@@ -1881,6 +1920,9 @@ describe('AssistantDatabase', () => {
|
||||
legacy.exec(`
|
||||
DELETE FROM magic_todos;
|
||||
ALTER TABLE conversations DROP COLUMN pinned;
|
||||
ALTER TABLE supervision_results DROP COLUMN graph_snapshot_json;
|
||||
DROP TABLE activity_history_records;
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
PRAGMA user_version = 9;
|
||||
`)
|
||||
legacy.close()
|
||||
@@ -1932,7 +1974,11 @@ describe('AssistantDatabase', () => {
|
||||
now,
|
||||
now
|
||||
)
|
||||
legacy.exec('ALTER TABLE conversations DROP COLUMN pinned; PRAGMA user_version = 16')
|
||||
legacy.exec(`ALTER TABLE conversations DROP COLUMN pinned;
|
||||
ALTER TABLE supervision_results DROP COLUMN graph_snapshot_json;
|
||||
DROP TABLE activity_history_records;
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
PRAGMA user_version = 16`)
|
||||
legacy.close()
|
||||
|
||||
const migrated = new AssistantDatabase(databasePath)
|
||||
@@ -2877,7 +2923,11 @@ describe('AssistantDatabase', () => {
|
||||
WHERE id = ?`
|
||||
)
|
||||
.run(entry.id)
|
||||
legacy.exec('ALTER TABLE conversations DROP COLUMN pinned; PRAGMA user_version = 23')
|
||||
legacy.exec(`ALTER TABLE conversations DROP COLUMN pinned;
|
||||
ALTER TABLE supervision_results DROP COLUMN graph_snapshot_json;
|
||||
DROP TABLE activity_history_records;
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
PRAGMA user_version = 23`)
|
||||
legacy.close()
|
||||
|
||||
const migrated = new AssistantDatabase(databasePath)
|
||||
@@ -3013,6 +3063,9 @@ describe('AssistantDatabase', () => {
|
||||
INSERT INTO channel_events(channel, event_id, claimed_at)
|
||||
VALUES ('weixin', 'legacy-event', 1);
|
||||
ALTER TABLE conversations DROP COLUMN pinned;
|
||||
ALTER TABLE supervision_results DROP COLUMN graph_snapshot_json;
|
||||
DROP TABLE activity_history_records;
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
PRAGMA user_version = 18;
|
||||
`)
|
||||
legacy.close()
|
||||
@@ -3520,6 +3573,9 @@ describe('AssistantDatabase', () => {
|
||||
ALTER TABLE task_events DROP COLUMN remote_operation_id;
|
||||
ALTER TABLE task_events DROP COLUMN remote_binding_id;
|
||||
ALTER TABLE conversations DROP COLUMN pinned;
|
||||
ALTER TABLE supervision_results DROP COLUMN graph_snapshot_json;
|
||||
DROP TABLE activity_history_records;
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
PRAGMA user_version = 31;
|
||||
`)
|
||||
legacy.close()
|
||||
|
||||
@@ -76,7 +76,11 @@ describe('AssistantDatabase heartbeat persistence', () => {
|
||||
database.close()
|
||||
|
||||
const raw = new DatabaseSync(path)
|
||||
raw.exec('ALTER TABLE conversations DROP COLUMN pinned; PRAGMA user_version = 2')
|
||||
raw.exec(`ALTER TABLE conversations DROP COLUMN pinned;
|
||||
ALTER TABLE supervision_results DROP COLUMN graph_snapshot_json;
|
||||
DROP TABLE activity_history_records;
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
PRAGMA user_version = 2`)
|
||||
raw.close()
|
||||
|
||||
const migrated = new AssistantDatabase(path)
|
||||
@@ -128,7 +132,11 @@ describe('AssistantDatabase heartbeat persistence', () => {
|
||||
'DELETE FROM heartbeat_config_projects WHERE config_id = ?'
|
||||
)
|
||||
.run(config.id)
|
||||
raw.exec('ALTER TABLE conversations DROP COLUMN pinned; PRAGMA user_version = 20')
|
||||
raw.exec(`ALTER TABLE conversations DROP COLUMN pinned;
|
||||
ALTER TABLE supervision_results DROP COLUMN graph_snapshot_json;
|
||||
DROP TABLE activity_history_records;
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
PRAGMA user_version = 20`)
|
||||
raw.close()
|
||||
|
||||
const migrated = new AssistantDatabase(path)
|
||||
|
||||
@@ -37,7 +37,10 @@ it('supervision preserves result history and protected identities within scope t
|
||||
const original = read(a.id)
|
||||
db.close()
|
||||
const legacy = new DatabaseSync(path)
|
||||
legacy.exec('ALTER TABLE supervision_results DROP COLUMN graph_snapshot_json; PRAGMA user_version = 42;')
|
||||
legacy.exec(`ALTER TABLE supervision_results DROP COLUMN graph_snapshot_json;
|
||||
DROP TABLE activity_history_records;
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
PRAGMA user_version = 42;`)
|
||||
legacy.close()
|
||||
db.initialize(process.cwd())
|
||||
expect(read(a.id)).toEqual(original)
|
||||
|
||||
@@ -218,7 +218,10 @@ describe('Magic note SQLite and filesystem storage', () => {
|
||||
const entry = note.entries[0]!
|
||||
sql.prepare('UPDATE magic_note_entries SET content_json = ? WHERE id = ?').run(JSON.stringify(entry.content), entry.id)
|
||||
}
|
||||
sql.exec('PRAGMA user_version = 37')
|
||||
sql.exec(`ALTER TABLE supervision_results DROP COLUMN graph_snapshot_json;
|
||||
DROP TABLE activity_history_records;
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
PRAGMA user_version = 37`)
|
||||
rmSync(join(directory, 'notes'), { recursive: true })
|
||||
const progress: number[] = []
|
||||
upgradeAssistantStorage(path, (value) => progress.push(value.processed))
|
||||
|
||||
@@ -69,7 +69,7 @@ describe('WorkspaceFilesPanel', () => {
|
||||
expect(screen.queryByRole('navigation', { name: '路径' })).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('styles branch search as a shared field and preserves filtering, switching and creation', async () => {
|
||||
it('floats branch search outside the workspace and preserves filtering, switching and creation', async () => {
|
||||
let current = 'main'
|
||||
const manage = vi.fn(async (_project: string, action: WorkspaceManagementAction): Promise<WorkspaceManagementResult> => {
|
||||
if (action.kind === 'switchBranch' || action.kind === 'createBranch') {
|
||||
@@ -90,6 +90,10 @@ describe('WorkspaceFilesPanel', () => {
|
||||
const trigger = await screen.findByRole('button', { name: 'main' })
|
||||
fireEvent.click(trigger)
|
||||
const search = screen.getByRole('textbox', { name: '搜索本地或远程分支 / 新分支名称' })
|
||||
const panel = screen.getByRole('dialog', { name: '分支' })
|
||||
expect(panel.closest('.workspace-git')).toBeNull()
|
||||
expect(panel.closest('.floating-portal')).not.toBeNull()
|
||||
expect(trigger).toHaveAttribute('aria-controls', panel.id)
|
||||
expect(search.closest('label')).toHaveClass('field')
|
||||
expect(search.previousElementSibling?.tagName).toBe('SPAN')
|
||||
expect(search).toHaveFocus()
|
||||
@@ -110,6 +114,9 @@ describe('WorkspaceFilesPanel', () => {
|
||||
fireEvent.keyDown(screen.getByRole('textbox'), { key: 'Escape' })
|
||||
expect(screen.queryByRole('textbox')).not.toBeInTheDocument()
|
||||
expect(trigger).toHaveFocus()
|
||||
fireEvent.click(trigger)
|
||||
fireEvent.pointerDown(document.body)
|
||||
expect(screen.queryByRole('dialog', { name: '分支' })).not.toBeInTheDocument()
|
||||
expect(manage.mock.calls.some(([, action]) => action.kind === 'fetch')).toBe(false)
|
||||
})
|
||||
|
||||
@@ -205,6 +212,7 @@ describe('WorkspaceFilesPanel', () => {
|
||||
expect(css).toMatch(/\.workspace-git__branch-trigger\s*\{[^}]*width: auto;[^}]*height: 32px;[^}]*flex: 0 1 auto;[^}]*text-align: left/)
|
||||
expect(css).toMatch(/\.workspace-files__header\s*\{[^}]*justify-content: space-between/)
|
||||
expect(css).toMatch(/\.workspace-git__branches\s*\{[^}]*width: min\(100%, 360px\);[^}]*border-radius: var\(--radius-control\)/)
|
||||
expect(css).toMatch(/\.workspace-git__branches\s*\{[^}]*position: fixed;[^}]*max-height: calc\(100vh - 32px\);[^}]*overflow: auto/)
|
||||
expect(css).toMatch(/\.workspace-git__branch-list\s*\{[^}]*max-height: 240px;[^}]*overflow: auto/)
|
||||
expect(css).toMatch(/\.workspace-git__create-branch\s*\{[^}]*justify-self: start/)
|
||||
})
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useEffect, useEffectEvent, useRef, useState } from 'react'
|
||||
import { useEffect, useEffectEvent, useId, useLayoutEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ChevronDown, ChevronLeft, ChevronRight, Download, FileText, GitBranch } from 'lucide-react'
|
||||
import type { WorkspaceManagementAction, WorkspaceManagementResult } from '../../shared/workspace-management-contracts'
|
||||
import { FloatingPortal } from './FloatingPortal'
|
||||
|
||||
type Result<K extends WorkspaceManagementResult['kind']> = Extract<WorkspaceManagementResult, { kind: K }>
|
||||
|
||||
@@ -27,6 +28,8 @@ export function WorkspaceGitTools({ projectId, refreshToken, onRefresh, viewCont
|
||||
const returnTrigger = useRef<HTMLElement | null>(null)
|
||||
const fileTrigger = useRef<HTMLElement | null>(null)
|
||||
const branchTrigger = useRef<HTMLButtonElement>(null)
|
||||
const branchPanel = useRef<HTMLDivElement>(null)
|
||||
const branchPanelId = useId()
|
||||
const diffBack = useRef<HTMLButtonElement>(null)
|
||||
const commitBack = useRef<HTMLButtonElement>(null)
|
||||
const action = (value: WorkspaceManagementAction): Promise<WorkspaceManagementResult> => window.goodbuddy.workspace.manage(projectId, value)
|
||||
@@ -43,6 +46,53 @@ export function WorkspaceGitTools({ projectId, refreshToken, onRefresh, viewCont
|
||||
return () => { branchCounter.current++ }
|
||||
}, [projectId, refreshToken])
|
||||
useEffect(() => { if (path) diffBack.current?.focus(); else if (commit) commitBack.current?.focus() }, [path, commit])
|
||||
const positionBranches = useEffectEvent(() => {
|
||||
const panel = branchPanel.current
|
||||
const anchor = branchTrigger.current
|
||||
if (!panel || !anchor) return
|
||||
const viewport = window.visualViewport
|
||||
const left = viewport?.offsetLeft ?? 0
|
||||
const top = viewport?.offsetTop ?? 0
|
||||
const width = viewport?.width ?? window.innerWidth
|
||||
const height = viewport?.height ?? window.innerHeight
|
||||
const rect = anchor.getBoundingClientRect()
|
||||
const panelWidth = Math.max(0, Math.min(360, width - 32))
|
||||
panel.style.width = `${panelWidth}px`
|
||||
panel.style.maxHeight = `${Math.max(0, height - 32)}px`
|
||||
const panelHeight = panel.getBoundingClientRect().height
|
||||
const below = rect.bottom + 8
|
||||
const y = below + panelHeight <= top + height - 16 ? below : rect.top - 8 - panelHeight
|
||||
panel.style.left = `${Math.max(left + 16, Math.min(rect.left, left + width - panelWidth - 16))}px`
|
||||
panel.style.top = `${Math.max(top + 16, Math.min(y, top + height - panelHeight - 16))}px`
|
||||
})
|
||||
useLayoutEffect(() => { if (branchOpen) positionBranches() })
|
||||
useEffect(() => {
|
||||
if (!branchOpen) return
|
||||
branchPanel.current?.querySelector('input')?.focus({ preventScroll: true })
|
||||
const outside = (event: Event): void => {
|
||||
if (event.target instanceof Node && !branchTrigger.current?.contains(event.target) &&
|
||||
!branchPanel.current?.contains(event.target)) setBranchOpen(false)
|
||||
}
|
||||
const reposition = (): void => positionBranches()
|
||||
const observer = new ResizeObserver(reposition)
|
||||
if (branchPanel.current) observer.observe(branchPanel.current)
|
||||
if (branchTrigger.current) observer.observe(branchTrigger.current)
|
||||
document.addEventListener('pointerdown', outside)
|
||||
document.addEventListener('focusin', outside)
|
||||
window.addEventListener('resize', reposition)
|
||||
window.addEventListener('scroll', reposition, true)
|
||||
window.visualViewport?.addEventListener('resize', reposition)
|
||||
window.visualViewport?.addEventListener('scroll', reposition)
|
||||
return () => {
|
||||
observer.disconnect()
|
||||
document.removeEventListener('pointerdown', outside)
|
||||
document.removeEventListener('focusin', outside)
|
||||
window.removeEventListener('resize', reposition)
|
||||
window.removeEventListener('scroll', reposition, true)
|
||||
window.visualViewport?.removeEventListener('resize', reposition)
|
||||
window.visualViewport?.removeEventListener('scroll', reposition)
|
||||
}
|
||||
}, [branchOpen])
|
||||
const run = async (value: WorkspaceManagementAction): Promise<void> => {
|
||||
if (busy) return
|
||||
setBusy(true); setError('')
|
||||
@@ -92,16 +142,16 @@ export function WorkspaceGitTools({ projectId, refreshToken, onRefresh, viewCont
|
||||
<div className="workspace-git__current">
|
||||
<div className="workspace-git__toolbar">
|
||||
<div className="workspace-git__repository-actions">
|
||||
<button ref={branchTrigger} className="model-button workspace-git__branch-trigger" type="button" aria-expanded={branchOpen} onClick={() => setBranchOpen(!branchOpen)}><GitBranch size={14} aria-hidden="true" /><span className="model-button__label" title={branches?.current}>{branches?.current || t('management.branch')}</span><ChevronDown size={14} aria-hidden="true" /></button>
|
||||
<button ref={branchTrigger} className="model-button workspace-git__branch-trigger" type="button" aria-haspopup="dialog" aria-controls={branchOpen ? branchPanelId : undefined} aria-expanded={branchOpen} onClick={() => setBranchOpen(!branchOpen)}><GitBranch size={14} aria-hidden="true" /><span className="model-button__label" title={branches?.current}>{branches?.current || t('management.branch')}</span><ChevronDown size={14} aria-hidden="true" /></button>
|
||||
<button className="secondary-button workspace-git__fetch" type="button" title={t('management.fetch')} aria-label={t('management.fetch')} disabled={busy} onClick={() => void run({ kind: 'fetch' })}><Download size={14} aria-hidden="true" /></button>
|
||||
</div>
|
||||
{viewControl}
|
||||
</div>
|
||||
{branchOpen && <div className="workspace-git__branches" onKeyDown={(event) => { if (event.key === 'Escape') { setBranchOpen(false); branchTrigger.current?.focus() } }}>
|
||||
<label className="field"><span>{t('management.searchBranch')}</span><input autoFocus value={query} onChange={(event) => setQuery(event.target.value)} /></label>
|
||||
{branchOpen && <FloatingPortal anchorRef={branchTrigger}><div ref={branchPanel} id={branchPanelId} role="dialog" aria-label={t('management.branch')} className="workspace-git__branches" onKeyDown={(event) => { if (event.key === 'Escape') { event.preventDefault(); event.stopPropagation(); setBranchOpen(false); branchTrigger.current?.focus() } }}>
|
||||
<label className="field"><span>{t('management.searchBranch')}</span><input value={query} onChange={(event) => setQuery(event.target.value)} /></label>
|
||||
<div className="workspace-git__branch-list">{branches?.branches.filter((branch) => branch.name.toLowerCase().includes(query.toLowerCase())).map((branch) => <button className="workspace-files__changed-row" type="button" key={`${branch.remote}:${branch.name}`} disabled={busy} aria-current={!branch.remote && branches.current === branch.name ? 'true' : undefined} onClick={() => void run({ kind: 'switchBranch', branch: branch.name, remote: branch.remote })}><GitBranch size={14} aria-hidden="true" /><span title={branch.name}>{branch.name}</span><small>{t(branch.remote ? 'management.remote' : 'management.local')}</small></button>)}</div>
|
||||
<button className="secondary-button workspace-git__create-branch" type="button" disabled={busy || !query.trim()} onClick={() => void run({ kind: 'createBranch', branch: query })}>{t('management.createBranch')}</button>
|
||||
</div>}
|
||||
</div></FloatingPortal>}
|
||||
<div className="workspace-git__changes">{children}</div>
|
||||
</div>
|
||||
<div className="workspace-git__history-dock" style={{ flexBasis: historyOpen ? `${historySize}%` : undefined }}>
|
||||
|
||||
@@ -17995,14 +17995,18 @@ details.settings-section > :not(summary) + :not(summary) {
|
||||
border-color: var(--border-default);
|
||||
}
|
||||
.workspace-git__branches {
|
||||
position: fixed;
|
||||
display: grid;
|
||||
width: min(100%, 360px);
|
||||
max-height: calc(100vh - 32px);
|
||||
overflow: auto;
|
||||
min-width: 0;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-3);
|
||||
border: 1px solid var(--border-default);
|
||||
border-radius: var(--radius-control);
|
||||
background: var(--surface-subtle);
|
||||
background: var(--surface-raised);
|
||||
box-shadow: var(--shadow-dialog);
|
||||
color: var(--text-primary);
|
||||
font-size: var(--font-body);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user