mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 00:02:31 +00:00
Add feature interaction usage bucket telemetry (#5119)
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -74,7 +74,9 @@ function editForCandidate(candidate, key, translation, sourceFile) {
|
||||
}
|
||||
|
||||
function sourceKindForPath(filePath) {
|
||||
return filePath.endsWith('.tsx') || filePath.endsWith('.jsx') ? ts.ScriptKind.TSX : ts.ScriptKind.TS
|
||||
return filePath.endsWith('.tsx') || filePath.endsWith('.jsx')
|
||||
? ts.ScriptKind.TSX
|
||||
: ts.ScriptKind.TS
|
||||
}
|
||||
|
||||
function findNodeByRange(sourceFile, start, end) {
|
||||
@@ -201,7 +203,9 @@ async function collectCandidateFiles(root) {
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(dir, entry.name)
|
||||
if (entry.isDirectory()) {
|
||||
if (!['.git', 'assets', 'dist', 'node_modules', 'out', '__snapshots__'].includes(entry.name)) {
|
||||
if (
|
||||
!['.git', 'assets', 'dist', 'node_modules', 'out', '__snapshots__'].includes(entry.name)
|
||||
) {
|
||||
stack.push(fullPath)
|
||||
}
|
||||
continue
|
||||
|
||||
@@ -160,6 +160,12 @@ describe('telemetry IPC handlers', () => {
|
||||
registerWith({ installId: 'x', existedBeforeTelemetryRelease: false, optedIn: true })
|
||||
const handler = handlers.get('telemetry:track')!
|
||||
handler({}, 'app_starred_orca', { source: 'settings' })
|
||||
handler({}, 'feature_interaction_usage_bucket_reached', {
|
||||
feature_id: 'tasks',
|
||||
feature_category: 'task_management',
|
||||
count_bucket: 'count_1',
|
||||
bucket_source: 'crossed_now'
|
||||
})
|
||||
expect(trackMock).not.toHaveBeenCalled()
|
||||
expect(getCohortAtEmitMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -53,7 +53,10 @@ import type { OptInVia } from '../../shared/telemetry-events'
|
||||
// mirrors how other core-handlers accept the store explicitly.
|
||||
let storeRef: Store | null = null
|
||||
|
||||
const MAIN_OWNED_TELEMETRY_EVENTS = new Set<EventName>(['app_starred_orca'])
|
||||
const MAIN_OWNED_TELEMETRY_EVENTS = new Set<EventName>([
|
||||
'app_starred_orca',
|
||||
'feature_interaction_usage_bucket_reached'
|
||||
])
|
||||
|
||||
/**
|
||||
* Derive the `via` discriminator for a `telemetry:setOptIn` call from
|
||||
|
||||
@@ -81,6 +81,11 @@ const WORKFLOW_DEFAULT_WORKSPACE_STATUSES = [
|
||||
{ id: 'todo', label: 'Todo', color: 'neutral', icon: 'circle' }
|
||||
]
|
||||
|
||||
const { trackMock, getCohortAtEmitMock } = vi.hoisted(() => ({
|
||||
trackMock: vi.fn(),
|
||||
getCohortAtEmitMock: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('electron', () => ({
|
||||
app: {
|
||||
getPath: () => testState.dir
|
||||
@@ -102,6 +107,14 @@ vi.mock('./git/repo', () => ({
|
||||
getGitUsername: vi.fn().mockReturnValue('testuser')
|
||||
}))
|
||||
|
||||
vi.mock('./telemetry/client', () => ({
|
||||
track: trackMock
|
||||
}))
|
||||
|
||||
vi.mock('./telemetry/cohort-classifier', () => ({
|
||||
getCohortAtEmit: getCohortAtEmitMock
|
||||
}))
|
||||
|
||||
/** Reset modules and dynamically import Store so the data-file path picks up the current testState.dir */
|
||||
async function createStore() {
|
||||
vi.resetModules()
|
||||
@@ -257,6 +270,9 @@ function makeBalancedLegacyPaneLayout(start: number, end: number): TerminalPaneL
|
||||
describe('Store', () => {
|
||||
beforeEach(() => {
|
||||
testState.dir = mkdtempSync(join(tmpdir(), 'orca-test-'))
|
||||
trackMock.mockReset()
|
||||
getCohortAtEmitMock.mockReset()
|
||||
getCohortAtEmitMock.mockReturnValue({ nth_repo_added: 2 })
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
@@ -268,7 +284,7 @@ describe('Store', () => {
|
||||
it('returns empty repos when no data file exists', async () => {
|
||||
const store = await createStore()
|
||||
expect(store.getRepos()).toEqual([])
|
||||
})
|
||||
}, 15_000)
|
||||
|
||||
it('returns default settings when no data file exists', async () => {
|
||||
const store = await createStore()
|
||||
@@ -3037,6 +3053,61 @@ describe('Store', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('normalizes malformed main-owned feature telemetry bucket markers on read', async () => {
|
||||
writeDataFile({
|
||||
schemaVersion: 1,
|
||||
repos: [],
|
||||
worktreeMeta: {},
|
||||
settings: {},
|
||||
ui: {},
|
||||
githubCache: { pr: {}, issue: {} },
|
||||
workspaceSession: {},
|
||||
featureInteractionTelemetryBuckets: {
|
||||
tasks: 'count_2',
|
||||
browser: 'count_4',
|
||||
unknown: 'count_1'
|
||||
}
|
||||
})
|
||||
|
||||
const store = await createStore()
|
||||
store.flush()
|
||||
|
||||
const persisted = readDataFile() as PersistedState
|
||||
expect(persisted.featureInteractionTelemetryBuckets).toEqual({ tasks: 'count_2' })
|
||||
})
|
||||
|
||||
it('does not expose or accept UI shadow writes for main-owned feature telemetry markers', async () => {
|
||||
writeDataFile({
|
||||
schemaVersion: 1,
|
||||
repos: [],
|
||||
worktreeMeta: {},
|
||||
settings: {},
|
||||
ui: {
|
||||
featureInteractionTelemetryBuckets: { tasks: 'count_1000_plus' }
|
||||
},
|
||||
githubCache: { pr: {}, issue: {} },
|
||||
workspaceSession: {},
|
||||
featureInteractionTelemetryBuckets: { tasks: 'count_2' }
|
||||
})
|
||||
|
||||
const store = await createStore()
|
||||
|
||||
expect('featureInteractionTelemetryBuckets' in (store.getUI() as Record<string, unknown>)).toBe(
|
||||
false
|
||||
)
|
||||
|
||||
store.updateUI({
|
||||
featureInteractionTelemetryBuckets: { tasks: 'count_500_999' }
|
||||
} as never)
|
||||
store.flush()
|
||||
|
||||
const persisted = readDataFile() as PersistedState & {
|
||||
ui: Record<string, unknown>
|
||||
}
|
||||
expect(persisted.featureInteractionTelemetryBuckets).toEqual({ tasks: 'count_2' })
|
||||
expect(persisted.ui.featureInteractionTelemetryBuckets).toBeUndefined()
|
||||
})
|
||||
|
||||
it('normalizes feature tip ids from direct UI writes', async () => {
|
||||
const store = await createStore()
|
||||
|
||||
@@ -3068,6 +3139,179 @@ describe('Store', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('emits feature interaction telemetry only when a higher bucket is reached', async () => {
|
||||
const store = await createStore()
|
||||
|
||||
store.recordFeatureInteraction('tasks')
|
||||
store.recordFeatureInteraction('tasks')
|
||||
store.recordFeatureInteraction('tasks')
|
||||
store.recordFeatureInteraction('tasks')
|
||||
store.flush()
|
||||
|
||||
expect(trackMock).toHaveBeenCalledTimes(3)
|
||||
expect(trackMock).toHaveBeenNthCalledWith(1, 'feature_interaction_usage_bucket_reached', {
|
||||
feature_id: 'tasks',
|
||||
feature_category: 'task_management',
|
||||
count_bucket: 'count_1',
|
||||
bucket_source: 'crossed_now',
|
||||
nth_repo_added: 2
|
||||
})
|
||||
expect(trackMock).toHaveBeenNthCalledWith(2, 'feature_interaction_usage_bucket_reached', {
|
||||
feature_id: 'tasks',
|
||||
feature_category: 'task_management',
|
||||
count_bucket: 'count_2',
|
||||
bucket_source: 'crossed_now',
|
||||
nth_repo_added: 2
|
||||
})
|
||||
expect(trackMock).toHaveBeenNthCalledWith(3, 'feature_interaction_usage_bucket_reached', {
|
||||
feature_id: 'tasks',
|
||||
feature_category: 'task_management',
|
||||
count_bucket: 'count_3_4',
|
||||
bucket_source: 'crossed_now',
|
||||
nth_repo_added: 2
|
||||
})
|
||||
expect((readDataFile() as PersistedState).featureInteractionTelemetryBuckets).toEqual({
|
||||
tasks: 'count_3_4'
|
||||
})
|
||||
})
|
||||
|
||||
it('emits one observed-existing bucket for pre-rollout interaction counts', async () => {
|
||||
const store = await createStore()
|
||||
store.updateUI({
|
||||
featureInteractions: {
|
||||
tasks: { firstInteractedAt: 100, interactionCount: 137 }
|
||||
}
|
||||
})
|
||||
trackMock.mockClear()
|
||||
|
||||
store.recordFeatureInteraction('tasks')
|
||||
store.recordFeatureInteraction('tasks')
|
||||
store.flush()
|
||||
|
||||
expect(trackMock).toHaveBeenCalledTimes(1)
|
||||
expect(trackMock).toHaveBeenCalledWith('feature_interaction_usage_bucket_reached', {
|
||||
feature_id: 'tasks',
|
||||
feature_category: 'task_management',
|
||||
count_bucket: 'count_100_199',
|
||||
bucket_source: 'observed_existing',
|
||||
nth_repo_added: 2
|
||||
})
|
||||
expect((readDataFile() as PersistedState).featureInteractionTelemetryBuckets).toEqual({
|
||||
tasks: 'count_100_199'
|
||||
})
|
||||
})
|
||||
|
||||
it('emits only the top-coded observed-existing bucket for pre-rollout power users', async () => {
|
||||
const store = await createStore()
|
||||
store.updateUI({
|
||||
featureInteractions: {
|
||||
tasks: { firstInteractedAt: 100, interactionCount: 1200 }
|
||||
}
|
||||
})
|
||||
trackMock.mockClear()
|
||||
|
||||
store.recordFeatureInteraction('tasks')
|
||||
|
||||
expect(trackMock).toHaveBeenCalledTimes(1)
|
||||
expect(trackMock).toHaveBeenCalledWith('feature_interaction_usage_bucket_reached', {
|
||||
feature_id: 'tasks',
|
||||
feature_category: 'task_management',
|
||||
count_bucket: 'count_1000_plus',
|
||||
bucket_source: 'observed_existing',
|
||||
nth_repo_added: 2
|
||||
})
|
||||
})
|
||||
|
||||
it('emits high bucket crossings once and ignores same-range increments', async () => {
|
||||
writeDataFile({
|
||||
schemaVersion: 1,
|
||||
repos: [],
|
||||
worktreeMeta: {},
|
||||
settings: {},
|
||||
ui: {
|
||||
featureInteractions: {
|
||||
tasks: { firstInteractedAt: 100, interactionCount: 198 }
|
||||
}
|
||||
},
|
||||
githubCache: { pr: {}, issue: {} },
|
||||
workspaceSession: {},
|
||||
featureInteractionTelemetryBuckets: { tasks: 'count_100_199' }
|
||||
})
|
||||
const store = await createStore()
|
||||
|
||||
store.recordFeatureInteraction('tasks')
|
||||
store.recordFeatureInteraction('tasks')
|
||||
|
||||
expect(trackMock).toHaveBeenCalledTimes(1)
|
||||
expect(trackMock).toHaveBeenCalledWith('feature_interaction_usage_bucket_reached', {
|
||||
feature_id: 'tasks',
|
||||
feature_category: 'task_management',
|
||||
count_bucket: 'count_200_499',
|
||||
bucket_source: 'crossed_now',
|
||||
nth_repo_added: 2
|
||||
})
|
||||
})
|
||||
|
||||
it('does not emit for count 4 but emits the count_1000_plus crossing', async () => {
|
||||
const store = await createStore()
|
||||
|
||||
store.recordFeatureInteraction('tasks')
|
||||
store.recordFeatureInteraction('tasks')
|
||||
store.recordFeatureInteraction('tasks')
|
||||
trackMock.mockClear()
|
||||
|
||||
store.recordFeatureInteraction('tasks')
|
||||
expect(trackMock).not.toHaveBeenCalled()
|
||||
|
||||
writeDataFile({
|
||||
schemaVersion: 1,
|
||||
repos: [],
|
||||
worktreeMeta: {},
|
||||
settings: {},
|
||||
ui: {
|
||||
featureInteractions: {
|
||||
tasks: { firstInteractedAt: 100, interactionCount: 999 }
|
||||
}
|
||||
},
|
||||
githubCache: { pr: {}, issue: {} },
|
||||
workspaceSession: {},
|
||||
featureInteractionTelemetryBuckets: { tasks: 'count_500_999' }
|
||||
})
|
||||
const reloaded = await createStore()
|
||||
|
||||
reloaded.recordFeatureInteraction('tasks')
|
||||
expect(trackMock).toHaveBeenCalledTimes(1)
|
||||
expect(trackMock).toHaveBeenCalledWith('feature_interaction_usage_bucket_reached', {
|
||||
feature_id: 'tasks',
|
||||
feature_category: 'task_management',
|
||||
count_bucket: 'count_1000_plus',
|
||||
bucket_source: 'crossed_now',
|
||||
nth_repo_added: 2
|
||||
})
|
||||
})
|
||||
|
||||
it('dedupes against the persisted bucket marker', async () => {
|
||||
writeDataFile({
|
||||
schemaVersion: 1,
|
||||
repos: [],
|
||||
worktreeMeta: {},
|
||||
settings: {},
|
||||
ui: {
|
||||
featureInteractions: {
|
||||
tasks: { firstInteractedAt: 100, interactionCount: 100 }
|
||||
}
|
||||
},
|
||||
githubCache: { pr: {}, issue: {} },
|
||||
workspaceSession: {},
|
||||
featureInteractionTelemetryBuckets: { tasks: 'count_100_199' }
|
||||
})
|
||||
const store = await createStore()
|
||||
|
||||
store.recordFeatureInteraction('tasks')
|
||||
|
||||
expect(trackMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('updateUI restores fixed card properties from direct UI writes', async () => {
|
||||
const store = await createStore()
|
||||
store.updateUI({ worktreeCardProperties: ['inline-agents'] })
|
||||
|
||||
+81
-21
@@ -94,7 +94,11 @@ import { normalizeTerminalShortcutPolicy } from '../shared/keybindings'
|
||||
import { normalizeAppIconId } from '../shared/app-icon'
|
||||
import { normalizeTerminalCustomThemes } from '../shared/terminal-custom-themes'
|
||||
import {
|
||||
compareFeatureInteractionUsageBuckets,
|
||||
getFeatureInteractionCategory,
|
||||
getFeatureInteractionUsageBucket,
|
||||
normalizeFeatureInteractions,
|
||||
normalizeFeatureInteractionTelemetryBuckets,
|
||||
type FeatureInteractionId
|
||||
} from '../shared/feature-interactions'
|
||||
import { normalizeContextualTourIds } from '../shared/contextual-tours'
|
||||
@@ -134,6 +138,8 @@ import {
|
||||
migrateWorkspaceSessionTerminalScrollbackSnapshots,
|
||||
readTerminalScrollbackSnapshotSync
|
||||
} from './terminal-scrollback-snapshots'
|
||||
import { track } from './telemetry/client'
|
||||
import { getCohortAtEmit } from './telemetry/cohort-classifier'
|
||||
|
||||
function encrypt(plaintext: string): string {
|
||||
if (!plaintext || !safeStorage.isEncryptionAvailable()) {
|
||||
@@ -324,6 +330,21 @@ function mergeContextualTourSeenIds(
|
||||
return [...merged]
|
||||
}
|
||||
|
||||
function stripMainOwnedTelemetryMarkerFromUI(
|
||||
value: Partial<PersistedState['ui']> | undefined
|
||||
): Partial<PersistedState['ui']> {
|
||||
if (!value || typeof value !== 'object') {
|
||||
return {}
|
||||
}
|
||||
const { featureInteractionTelemetryBuckets: _reserved, ...ui } = value as Partial<
|
||||
PersistedState['ui']
|
||||
> & {
|
||||
featureInteractionTelemetryBuckets?: unknown
|
||||
}
|
||||
void _reserved
|
||||
return ui
|
||||
}
|
||||
|
||||
function normalizeSortBy(sortBy: unknown): PersistedState['ui']['sortBy'] {
|
||||
if (
|
||||
sortBy === 'smart' ||
|
||||
@@ -1900,6 +1921,9 @@ export class Store {
|
||||
result = {
|
||||
...defaults,
|
||||
...parsed,
|
||||
featureInteractionTelemetryBuckets: normalizeFeatureInteractionTelemetryBuckets(
|
||||
parsed.featureInteractionTelemetryBuckets
|
||||
),
|
||||
projectGroups: normalizeProjectGroups(parsed.projectGroups),
|
||||
worktreeLineageById: parsed.worktreeLineageById ?? {},
|
||||
settings: {
|
||||
@@ -2106,7 +2130,7 @@ export class Store {
|
||||
}
|
||||
return {
|
||||
...defaults.ui,
|
||||
...parsed.ui,
|
||||
...stripMainOwnedTelemetryMarkerFromUI(parsed.ui),
|
||||
// Why: migrate once from the retired Appearance setting only
|
||||
// when no explicit persisted chrome preference exists yet.
|
||||
rightSidebarOpen,
|
||||
@@ -3175,9 +3199,10 @@ export class Store {
|
||||
// ── UI State ───────────────────────────────────────────────────────
|
||||
|
||||
getUI(): PersistedState['ui'] {
|
||||
const uiState = stripMainOwnedTelemetryMarkerFromUI(this.state.ui)
|
||||
return {
|
||||
...getDefaultUIState(),
|
||||
...this.state.ui,
|
||||
...uiState,
|
||||
groupBy: normalizeGroupBy(this.state.ui?.groupBy),
|
||||
sortBy: normalizeSortBy(this.state.ui?.sortBy),
|
||||
projectOrderBy: normalizeProjectOrderBy(this.state.ui?.projectOrderBy),
|
||||
@@ -3206,39 +3231,44 @@ export class Store {
|
||||
}
|
||||
|
||||
updateUI(updates: Partial<PersistedState['ui']>): void {
|
||||
const sanitizedUpdates = stripMainOwnedTelemetryMarkerFromUI(updates)
|
||||
const currentUI = {
|
||||
...getDefaultUIState(),
|
||||
...stripMainOwnedTelemetryMarkerFromUI(this.state.ui)
|
||||
}
|
||||
this.state.ui = {
|
||||
...this.state.ui,
|
||||
...updates,
|
||||
groupBy: updates.groupBy
|
||||
? normalizeGroupBy(updates.groupBy)
|
||||
...currentUI,
|
||||
...sanitizedUpdates,
|
||||
groupBy: sanitizedUpdates.groupBy
|
||||
? normalizeGroupBy(sanitizedUpdates.groupBy)
|
||||
: normalizeGroupBy(this.state.ui?.groupBy),
|
||||
sortBy: updates.sortBy
|
||||
? normalizeSortBy(updates.sortBy)
|
||||
sortBy: sanitizedUpdates.sortBy
|
||||
? normalizeSortBy(sanitizedUpdates.sortBy)
|
||||
: normalizeSortBy(this.state.ui?.sortBy),
|
||||
projectOrderBy: updates.projectOrderBy
|
||||
? normalizeProjectOrderBy(updates.projectOrderBy)
|
||||
: normalizeProjectOrderBy(this.state.ui?.projectOrderBy),
|
||||
rightSidebarTab:
|
||||
updates.rightSidebarTab !== undefined
|
||||
? normalizeRightSidebarTab(updates.rightSidebarTab)
|
||||
sanitizedUpdates.rightSidebarTab !== undefined
|
||||
? normalizeRightSidebarTab(sanitizedUpdates.rightSidebarTab)
|
||||
: normalizeRightSidebarTab(this.state.ui?.rightSidebarTab),
|
||||
worktreeCardProperties:
|
||||
updates.worktreeCardProperties !== undefined
|
||||
? normalizeWorktreeCardProperties(updates.worktreeCardProperties)
|
||||
sanitizedUpdates.worktreeCardProperties !== undefined
|
||||
? normalizeWorktreeCardProperties(sanitizedUpdates.worktreeCardProperties)
|
||||
: normalizeWorktreeCardProperties(this.state.ui?.worktreeCardProperties),
|
||||
agentActivityDisplayMode:
|
||||
updates.agentActivityDisplayMode !== undefined
|
||||
? normalizeAgentActivityDisplayMode(updates.agentActivityDisplayMode)
|
||||
: normalizeAgentActivityDisplayMode(this.state.ui?.agentActivityDisplayMode),
|
||||
workspaceStatuses:
|
||||
updates.workspaceStatuses !== undefined
|
||||
? normalizeWorkspaceStatuses(updates.workspaceStatuses)
|
||||
sanitizedUpdates.workspaceStatuses !== undefined
|
||||
? normalizeWorkspaceStatuses(sanitizedUpdates.workspaceStatuses)
|
||||
: normalizeWorkspaceStatuses(this.state.ui?.workspaceStatuses),
|
||||
workspaceBoardOpacity: clampWorkspaceBoardOpacity(
|
||||
updates.workspaceBoardOpacity ?? this.state.ui?.workspaceBoardOpacity
|
||||
sanitizedUpdates.workspaceBoardOpacity ?? this.state.ui?.workspaceBoardOpacity
|
||||
),
|
||||
workspaceBoardColumnWidth: clampWorkspaceBoardColumnWidth(
|
||||
updates.workspaceBoardColumnWidth ?? this.state.ui?.workspaceBoardColumnWidth
|
||||
sanitizedUpdates.workspaceBoardColumnWidth ?? this.state.ui?.workspaceBoardColumnWidth
|
||||
),
|
||||
browserDefaultZoomLevel: normalizeBrowserPageZoomLevel(
|
||||
updates.browserDefaultZoomLevel ?? this.state.ui?.browserDefaultZoomLevel
|
||||
@@ -3248,8 +3278,8 @@ export class Store {
|
||||
? normalizeShowDotfilesByWorktree(updates.showDotfilesByWorktree)
|
||||
: normalizeShowDotfilesByWorktree(this.state.ui?.showDotfilesByWorktree),
|
||||
featureTipsSeenIds:
|
||||
updates.featureTipsSeenIds !== undefined
|
||||
? normalizeFeatureTipIds(updates.featureTipsSeenIds)
|
||||
sanitizedUpdates.featureTipsSeenIds !== undefined
|
||||
? normalizeFeatureTipIds(sanitizedUpdates.featureTipsSeenIds)
|
||||
: normalizeFeatureTipIds(this.state.ui?.featureTipsSeenIds),
|
||||
// Why: renderer and paired clients can mark different tours seen from
|
||||
// stale UI snapshots; union them so completed tours stay suppressed.
|
||||
@@ -3264,10 +3294,10 @@ export class Store {
|
||||
// Merge instead of replacing so a stale renderer snapshot cannot erase
|
||||
// runtime-only feature interactions.
|
||||
featureInteractions:
|
||||
updates.featureInteractions !== undefined
|
||||
sanitizedUpdates.featureInteractions !== undefined
|
||||
? mergeFeatureInteractions(
|
||||
this.state.ui?.featureInteractions,
|
||||
updates.featureInteractions
|
||||
sanitizedUpdates.featureInteractions
|
||||
)
|
||||
: normalizeFeatureInteractions(this.state.ui?.featureInteractions)
|
||||
}
|
||||
@@ -3276,16 +3306,46 @@ export class Store {
|
||||
|
||||
recordFeatureInteraction(id: FeatureInteractionId): PersistedState['ui'] {
|
||||
const featureInteractions = normalizeFeatureInteractions(this.state.ui?.featureInteractions)
|
||||
const telemetryBuckets = normalizeFeatureInteractionTelemetryBuckets(
|
||||
this.state.featureInteractionTelemetryBuckets
|
||||
)
|
||||
const existing = featureInteractions[id]
|
||||
const previousCount = existing?.interactionCount ?? 0
|
||||
const nextCount = previousCount + 1
|
||||
const previousBucket = getFeatureInteractionUsageBucket(previousCount)
|
||||
const nextBucket = getFeatureInteractionUsageBucket(nextCount)
|
||||
const lastEmittedBucket = telemetryBuckets[id] ?? null
|
||||
const shouldEmit =
|
||||
nextBucket !== null &&
|
||||
(lastEmittedBucket === null ||
|
||||
compareFeatureInteractionUsageBuckets(nextBucket, lastEmittedBucket) > 0)
|
||||
|
||||
this.updateUI({
|
||||
featureInteractions: {
|
||||
...featureInteractions,
|
||||
[id]: {
|
||||
firstInteractedAt: existing?.firstInteractedAt ?? Date.now(),
|
||||
interactionCount: (existing?.interactionCount ?? 0) + 1
|
||||
interactionCount: nextCount
|
||||
}
|
||||
}
|
||||
})
|
||||
this.state.featureInteractionTelemetryBuckets = shouldEmit
|
||||
? { ...telemetryBuckets, [id]: nextBucket }
|
||||
: telemetryBuckets
|
||||
this.scheduleSave()
|
||||
|
||||
if (shouldEmit) {
|
||||
track('feature_interaction_usage_bucket_reached', {
|
||||
feature_id: id,
|
||||
feature_category: getFeatureInteractionCategory(id),
|
||||
count_bucket: nextBucket,
|
||||
bucket_source:
|
||||
lastEmittedBucket === null && previousBucket !== null && previousBucket === nextBucket
|
||||
? 'observed_existing'
|
||||
: 'crossed_now',
|
||||
...getCohortAtEmit()
|
||||
})
|
||||
}
|
||||
return this.getUI()
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,11 @@ export default function CodexRestartChip({
|
||||
<div className="pointer-events-none absolute right-3 top-3 z-20">
|
||||
<div className="pointer-events-auto flex items-center gap-2 rounded-lg border border-border/80 bg-popover/95 px-2 py-1.5 shadow-lg backdrop-blur-sm">
|
||||
<span className="text-[11px] text-muted-foreground">
|
||||
{translate("auto.components.CodexRestartChip.9263e75f49", "Codex is using the previous account")}</span>
|
||||
{translate(
|
||||
'auto.components.CodexRestartChip.9263e75f49',
|
||||
'Codex is using the previous account'
|
||||
)}
|
||||
</span>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<button
|
||||
type="button"
|
||||
@@ -82,13 +86,15 @@ export default function CodexRestartChip({
|
||||
className="inline-flex items-center gap-1.5 rounded-md bg-foreground px-2 py-1 text-[11px] font-medium text-background transition-colors hover:opacity-90"
|
||||
>
|
||||
<RefreshCw className="size-3" />
|
||||
{translate("auto.components.CodexRestartChip.c72a5fb234", "Restart")}</button>
|
||||
{translate('auto.components.CodexRestartChip.c72a5fb234', 'Restart')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => dismissStaleWorktreePtyIds(staleWorktreePtyIds, clearCodexRestartNotice)}
|
||||
className="rounded-md px-1.5 py-1 text-[11px] text-muted-foreground transition-colors hover:bg-accent/60 hover:text-foreground"
|
||||
>
|
||||
{translate("auto.components.CodexRestartChip.9132779820", "Dismiss")}</button>
|
||||
{translate('auto.components.CodexRestartChip.9132779820', 'Dismiss')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -128,21 +128,30 @@ export function FirstLaunchBanner({
|
||||
<div
|
||||
className="fixed left-1/2 top-2 z-40 flex w-[min(44.625rem,calc(100vw-2rem))] -translate-x-1/2 items-start gap-4 rounded-lg border border-border bg-card/95 py-3 pl-4 pr-3 shadow-lg backdrop-blur"
|
||||
role="region"
|
||||
aria-label={translate("auto.components.FirstLaunchBanner.fcbee32f08", "Telemetry notice")}
|
||||
aria-label={translate('auto.components.FirstLaunchBanner.fcbee32f08', 'Telemetry notice')}
|
||||
aria-live="polite"
|
||||
>
|
||||
{/* Text column — title + body stack on the left, takes remaining
|
||||
width so the action column never pushes copy into a wrap. */}
|
||||
<div className="flex-1 space-y-0.5 pr-1 text-sm">
|
||||
<p className="font-medium leading-snug">{translate("auto.components.FirstLaunchBanner.9784b4d7bc", "Help us decide what to build next")}</p>
|
||||
<p className="font-medium leading-snug">
|
||||
{translate(
|
||||
'auto.components.FirstLaunchBanner.9784b4d7bc',
|
||||
'Help us decide what to build next'
|
||||
)}
|
||||
</p>
|
||||
<p className="text-xs leading-snug text-muted-foreground">
|
||||
{translate("auto.components.FirstLaunchBanner.958d2cc31b", "Anonymous counts of which features you use help us prioritize what to build. No file contents, prompts, terminal output, or anything that identifies you. Change anytime in Settings -> Privacy & Telemetry.")}{' '}
|
||||
{translate(
|
||||
'auto.components.FirstLaunchBanner.958d2cc31b',
|
||||
'Anonymous counts of which features you use help us prioritize what to build. No file contents, prompts, terminal output, or anything that identifies you. Change anytime in Settings -> Privacy & Telemetry.'
|
||||
)}{' '}
|
||||
<button
|
||||
type="button"
|
||||
className="underline underline-offset-2 hover:text-foreground"
|
||||
onClick={() => void window.api.shell.openUrl(PRIVACY_URL)}
|
||||
>
|
||||
{translate("auto.components.FirstLaunchBanner.d1deebb050", "Privacy policy")}</button>
|
||||
{translate('auto.components.FirstLaunchBanner.d1deebb050', 'Privacy policy')}
|
||||
</button>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
@@ -160,15 +169,17 @@ export function FirstLaunchBanner({
|
||||
disabled={inFlight}
|
||||
className="border-border/60 text-muted-foreground"
|
||||
>
|
||||
{translate("auto.components.FirstLaunchBanner.fc5cc29955", "Opt out")}</Button>
|
||||
{translate('auto.components.FirstLaunchBanner.fc5cc29955', 'Opt out')}
|
||||
</Button>
|
||||
<Button size="sm" onClick={handleAcknowledge} disabled={inFlight}>
|
||||
{translate("auto.components.FirstLaunchBanner.94cc673726", "Got it")}</Button>
|
||||
{translate('auto.components.FirstLaunchBanner.94cc673726', 'Got it')}
|
||||
</Button>
|
||||
</div>
|
||||
{/* aria-label says "Dismiss" — the action persists silent opt-in,
|
||||
not just hides the UI. */}
|
||||
<button
|
||||
type="button"
|
||||
aria-label={translate("auto.components.FirstLaunchBanner.b9e1b966c7", "Dismiss notice")}
|
||||
aria-label={translate('auto.components.FirstLaunchBanner.b9e1b966c7', 'Dismiss notice')}
|
||||
onClick={handleAcknowledge}
|
||||
disabled={inFlight}
|
||||
className="absolute right-1.5 top-1.5 rounded p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:cursor-not-allowed disabled:opacity-50"
|
||||
|
||||
@@ -716,6 +716,7 @@ function PRReviewersPanel({
|
||||
patchWorkItem(item.id, { reviewRequests: nextReviewRequests }, item.repoId)
|
||||
onReviewersRequested(nextReviewRequests)
|
||||
setReviewerInput('')
|
||||
useAppStore.getState().recordFeatureInteraction('github-tasks')
|
||||
toast.success(
|
||||
logins.length === 1
|
||||
? translate('auto.components.GitHubItemDialog.ea985e657f', 'Reviewer requested')
|
||||
@@ -789,6 +790,7 @@ function PRReviewersPanel({
|
||||
patchWorkItem(item.id, { reviewRequests: nextReviewRequests }, item.repoId)
|
||||
onReviewersRequested(nextReviewRequests)
|
||||
setReviewerInput('')
|
||||
useAppStore.getState().recordFeatureInteraction('github-tasks')
|
||||
toast.success(
|
||||
logins.length === 1
|
||||
? translate('auto.components.GitHubItemDialog.69515bff81', 'Reviewer removed')
|
||||
@@ -2571,6 +2573,7 @@ function ConversationTab({
|
||||
})
|
||||
onBodyUpdated(resolvedBodyDraft)
|
||||
setBodyEditing(false)
|
||||
useAppStore.getState().recordFeatureInteraction('github-tasks')
|
||||
toast.success(
|
||||
translate('auto.components.GitHubItemDialog.5221548274', 'Description updated.')
|
||||
)
|
||||
@@ -3116,6 +3119,7 @@ function PRActionsPanel({
|
||||
number: item.number,
|
||||
updates: { state: nextState }
|
||||
})
|
||||
useAppStore.getState().recordFeatureInteraction('github-tasks')
|
||||
toast.success(
|
||||
nextState === 'closed'
|
||||
? translate('auto.components.GitHubItemDialog.9f88657c4e', 'Pull request closed')
|
||||
@@ -3170,6 +3174,7 @@ function PRActionsPanel({
|
||||
return
|
||||
}
|
||||
applyStatePatch('merged')
|
||||
useAppStore.getState().recordFeatureInteraction('github-tasks')
|
||||
toast.success(translate('auto.components.GitHubItemDialog.dbe5e2448e', 'Pull request merged'))
|
||||
onMutated()
|
||||
} catch {
|
||||
@@ -3199,6 +3204,7 @@ function PRActionsPanel({
|
||||
toast.error(result.error)
|
||||
return
|
||||
}
|
||||
useAppStore.getState().recordFeatureInteraction('github-tasks')
|
||||
toast.success(
|
||||
enabled
|
||||
? translate('auto.components.GitHubItemDialog.a35ea5a0f6', 'Auto-merge enabled')
|
||||
@@ -4550,6 +4556,7 @@ function GHEditSection({
|
||||
patchProjectRowIfNeeded({ state: prevState })
|
||||
},
|
||||
onSuccess: () => {
|
||||
useAppStore.getState().recordFeatureInteraction('github-tasks')
|
||||
patchWorkItem(item.id, { state: newState }, item.repoId)
|
||||
patchProjectRowIfNeeded({ state: newState })
|
||||
onMutated()
|
||||
@@ -4594,6 +4601,7 @@ function GHEditSection({
|
||||
patchProjectRowIfNeeded({ labels: newLabels })
|
||||
},
|
||||
onSuccess: () => {
|
||||
useAppStore.getState().recordFeatureInteraction('github-tasks')
|
||||
onMutated()
|
||||
},
|
||||
onRevert: () => {
|
||||
@@ -4624,6 +4632,7 @@ function GHEditSection({
|
||||
patchProjectRowIfNeeded({ labels: prevLabels })
|
||||
},
|
||||
onSuccess: () => {
|
||||
useAppStore.getState().recordFeatureInteraction('github-tasks')
|
||||
onMutated()
|
||||
},
|
||||
onError: (err) => toast.error(err)
|
||||
@@ -4675,6 +4684,7 @@ function GHEditSection({
|
||||
patchProjectRowIfNeeded({ assignees: prevAssignees })
|
||||
},
|
||||
onSuccess: () => {
|
||||
useAppStore.getState().recordFeatureInteraction('github-tasks')
|
||||
onMutated()
|
||||
},
|
||||
onError: (err) => toast.error(err)
|
||||
@@ -4694,6 +4704,7 @@ function GHEditSection({
|
||||
patchProjectRowIfNeeded({ assignees: newAssignees })
|
||||
},
|
||||
onSuccess: () => {
|
||||
useAppStore.getState().recordFeatureInteraction('github-tasks')
|
||||
onMutated()
|
||||
},
|
||||
onRevert: () => {
|
||||
@@ -5798,6 +5809,7 @@ export default function GitHubItemDialog({
|
||||
|
||||
const appendOptimisticComment = useCallback(
|
||||
(comment: PRComment) => {
|
||||
useAppStore.getState().recordFeatureInteraction('github-tasks')
|
||||
// Why: skip refreshDetails() — gh api --cache 60s returns stale data
|
||||
// that overwrites the optimistic comment. The next dialog open (after
|
||||
// cache expiry) will pick up the server-confirmed version.
|
||||
|
||||
@@ -31,6 +31,7 @@ import CommentMarkdown from '@/components/sidebar/CommentMarkdown'
|
||||
import { isScreenSubmitShortcut } from '@/lib/screen-submit-shortcut'
|
||||
import { useMountedRef } from '@/hooks/useMountedRef'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useAppStore } from '@/store'
|
||||
import type {
|
||||
GitLabAssignableUser,
|
||||
GitLabPipelineJob,
|
||||
@@ -174,7 +175,8 @@ function CommentCard({
|
||||
<span className="font-medium text-foreground">{comment.author}</span>
|
||||
{comment.isResolved ? (
|
||||
<span className="rounded-full bg-emerald-500/15 px-1.5 py-0.5 text-[10px] font-medium text-emerald-700 dark:text-emerald-300">
|
||||
{translate("auto.components.GitLabItemDialog.f23ea85341", "resolved")}</span>
|
||||
{translate('auto.components.GitLabItemDialog.f23ea85341', 'resolved')}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -188,7 +190,9 @@ function CommentCard({
|
||||
className="h-6"
|
||||
>
|
||||
{resolving ? <LoaderCircle className="size-3 animate-spin" /> : null}
|
||||
{comment.isResolved ? translate("auto.components.GitLabItemDialog.65e784c1f1", "Reopen") : translate("auto.components.GitLabItemDialog.4168eb2c51", "Resolve")}
|
||||
{comment.isResolved
|
||||
? translate('auto.components.GitLabItemDialog.65e784c1f1', 'Reopen')
|
||||
: translate('auto.components.GitLabItemDialog.4168eb2c51', 'Resolve')}
|
||||
</Button>
|
||||
) : null}
|
||||
<span>{comment.createdAt ? new Date(comment.createdAt).toLocaleDateString() : ''}</span>
|
||||
@@ -260,7 +264,8 @@ function PipelineJobRow({
|
||||
className="h-6"
|
||||
>
|
||||
{retrying ? <LoaderCircle className="size-3 animate-spin" /> : null}
|
||||
{translate("auto.components.GitLabItemDialog.fa3e042203", "Retry")}</Button>
|
||||
{translate('auto.components.GitLabItemDialog.fa3e042203', 'Retry')}
|
||||
</Button>
|
||||
) : null}
|
||||
{job.webUrl ? (
|
||||
<Button
|
||||
@@ -268,7 +273,7 @@ function PipelineJobRow({
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
onClick={() => void window.api.shell.openUrl(job.webUrl)}
|
||||
title={translate("auto.components.GitLabItemDialog.032ae1312b", "Open job in GitLab")}
|
||||
title={translate('auto.components.GitLabItemDialog.032ae1312b', 'Open job in GitLab')}
|
||||
>
|
||||
<ExternalLink className="size-3" />
|
||||
</Button>
|
||||
@@ -278,19 +283,23 @@ function PipelineJobRow({
|
||||
{expanded ? (
|
||||
<div className="mx-3 mb-2 rounded-md border border-border/50 bg-muted/20">
|
||||
<div className="flex items-center justify-between border-b border-border/40 px-2.5 py-1.5 text-[11px] text-muted-foreground">
|
||||
<span>{translate("auto.components.GitLabItemDialog.2f9b27f838", "Job log")}</span>
|
||||
<span>{translate('auto.components.GitLabItemDialog.2f9b27f838', 'Job log')}</span>
|
||||
<Button type="button" variant="ghost" size="xs" onClick={() => onToggleTrace(job)}>
|
||||
{translate("auto.components.GitLabItemDialog.028bde664e", "Hide")}</Button>
|
||||
{translate('auto.components.GitLabItemDialog.028bde664e', 'Hide')}
|
||||
</Button>
|
||||
</div>
|
||||
{traceState?.loading ? (
|
||||
<div className="flex items-center gap-2 px-2.5 py-3 text-xs text-muted-foreground">
|
||||
<LoaderCircle className="size-3.5 animate-spin" />
|
||||
{translate("auto.components.GitLabItemDialog.d600c2619a", "Loading log")}</div>
|
||||
{translate('auto.components.GitLabItemDialog.d600c2619a', 'Loading log')}
|
||||
</div>
|
||||
) : traceState?.error ? (
|
||||
<div className="px-2.5 py-3 text-xs text-destructive">{traceState.error}</div>
|
||||
) : (
|
||||
<pre className="max-h-64 overflow-auto whitespace-pre-wrap break-words px-2.5 py-2 font-mono text-[11px] leading-4 text-foreground scrollbar-sleek">
|
||||
{traceState?.trace?.trim() ? traceState.trace : translate("auto.components.GitLabItemDialog.32f8bef818", "No log output.")}
|
||||
{traceState?.trace?.trim()
|
||||
? traceState.trace
|
||||
: translate('auto.components.GitLabItemDialog.32f8bef818', 'No log output.')}
|
||||
</pre>
|
||||
)}
|
||||
</div>
|
||||
@@ -484,7 +493,7 @@ export default function GitLabItemDialog({
|
||||
const nextBody = bodyDraft
|
||||
const nextLabels = parseGitLabLabelDraft(labelDraft)
|
||||
if (!nextTitle) {
|
||||
toast.error(translate("auto.components.GitLabItemDialog.98718490e4", "MR title is required."))
|
||||
toast.error(translate('auto.components.GitLabItemDialog.98718490e4', 'MR title is required.'))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -531,6 +540,7 @@ export default function GitLabItemDialog({
|
||||
setTitleDraft('')
|
||||
setBodyDraft('')
|
||||
setLabelDraft('')
|
||||
useAppStore.getState().recordFeatureInteraction('gitlab-tasks')
|
||||
}
|
||||
} else if (mountedRef.current) {
|
||||
toast.error(res.error)
|
||||
@@ -611,7 +621,11 @@ export default function GitLabItemDialog({
|
||||
return
|
||||
}
|
||||
if (result.ok) {
|
||||
toast.success(translate("auto.components.GitLabItemDialog.f7cb495a12", "Retried {{value0}}", { value0: job.name }))
|
||||
toast.success(
|
||||
translate('auto.components.GitLabItemDialog.f7cb495a12', 'Retried {{value0}}', {
|
||||
value0: job.name
|
||||
})
|
||||
)
|
||||
if (result.job) {
|
||||
setDetails((current) =>
|
||||
current
|
||||
@@ -646,7 +660,12 @@ export default function GitLabItemDialog({
|
||||
.map((reviewer) => reviewer.id)
|
||||
.filter((id): id is number => typeof id === 'number')
|
||||
if (reviewerIds.length !== nextReviewers.length) {
|
||||
toast.error(translate("auto.components.GitLabItemDialog.ceaf7c30c7", "Reviewer id is unavailable for this GitLab user."))
|
||||
toast.error(
|
||||
translate(
|
||||
'auto.components.GitLabItemDialog.ceaf7c30c7',
|
||||
'Reviewer id is unavailable for this GitLab user.'
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
setReviewerUpdating(true)
|
||||
@@ -668,6 +687,7 @@ export default function GitLabItemDialog({
|
||||
setReviewerOptions((current) =>
|
||||
current ? dedupeGitLabUsers([...current, ...result.reviewers]) : current
|
||||
)
|
||||
useAppStore.getState().recordFeatureInteraction('gitlab-tasks')
|
||||
} else {
|
||||
toast.error(result.error)
|
||||
}
|
||||
@@ -688,11 +708,21 @@ export default function GitLabItemDialog({
|
||||
const line = Number.parseInt(inlineCommentLine, 10)
|
||||
const body = inlineCommentBody.trim()
|
||||
if (!file || !Number.isFinite(line) || line <= 0 || !body) {
|
||||
toast.error(translate("auto.components.GitLabItemDialog.00d0d25825", "File, line, and comment are required."))
|
||||
toast.error(
|
||||
translate(
|
||||
'auto.components.GitLabItemDialog.00d0d25825',
|
||||
'File, line, and comment are required.'
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
if (!details.baseSha || !details.startSha || !details.headSha) {
|
||||
toast.error(translate("auto.components.GitLabItemDialog.ffdd9a78e1", "MR diff refs are unavailable for inline comments."))
|
||||
toast.error(
|
||||
translate(
|
||||
'auto.components.GitLabItemDialog.ffdd9a78e1',
|
||||
'MR diff refs are unavailable for inline comments.'
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
setInlineCommentSubmitting(true)
|
||||
@@ -719,7 +749,10 @@ export default function GitLabItemDialog({
|
||||
current ? { ...current, comments: [...current.comments, result.comment] } : current
|
||||
)
|
||||
setInlineCommentBody('')
|
||||
toast.success(translate("auto.components.GitLabItemDialog.60c13320c4", "Inline comment added"))
|
||||
useAppStore.getState().recordFeatureInteraction('gitlab-tasks')
|
||||
toast.success(
|
||||
translate('auto.components.GitLabItemDialog.60c13320c4', 'Inline comment added')
|
||||
)
|
||||
} else {
|
||||
toast.error(result.error)
|
||||
}
|
||||
@@ -747,7 +780,12 @@ export default function GitLabItemDialog({
|
||||
const res = await window.api.gl.closeMR({ repoPath, iid: item.number })
|
||||
if (res.ok) {
|
||||
if (mountedRef.current) {
|
||||
toast.success(translate("auto.components.GitLabItemDialog.9b11cd233f", "Closed MR !{{value0}}", { value0: item.number }))
|
||||
useAppStore.getState().recordFeatureInteraction('gitlab-tasks')
|
||||
toast.success(
|
||||
translate('auto.components.GitLabItemDialog.9b11cd233f', 'Closed MR !{{value0}}', {
|
||||
value0: item.number
|
||||
})
|
||||
)
|
||||
handleRefresh()
|
||||
}
|
||||
} else {
|
||||
@@ -771,7 +809,12 @@ export default function GitLabItemDialog({
|
||||
const res = await window.api.gl.reopenMR({ repoPath, iid: item.number })
|
||||
if (res.ok) {
|
||||
if (mountedRef.current) {
|
||||
toast.success(translate("auto.components.GitLabItemDialog.865ea2703e", "Reopened MR !{{value0}}", { value0: item.number }))
|
||||
useAppStore.getState().recordFeatureInteraction('gitlab-tasks')
|
||||
toast.success(
|
||||
translate('auto.components.GitLabItemDialog.865ea2703e', 'Reopened MR !{{value0}}', {
|
||||
value0: item.number
|
||||
})
|
||||
)
|
||||
handleRefresh()
|
||||
}
|
||||
} else {
|
||||
@@ -795,7 +838,12 @@ export default function GitLabItemDialog({
|
||||
const res = await window.api.gl.mergeMR({ repoPath, iid: item.number })
|
||||
if (res.ok) {
|
||||
if (mountedRef.current) {
|
||||
toast.success(translate("auto.components.GitLabItemDialog.e089f62594", "Merged MR !{{value0}}", { value0: item.number }))
|
||||
useAppStore.getState().recordFeatureInteraction('gitlab-tasks')
|
||||
toast.success(
|
||||
translate('auto.components.GitLabItemDialog.e089f62594', 'Merged MR !{{value0}}', {
|
||||
value0: item.number
|
||||
})
|
||||
)
|
||||
handleRefresh()
|
||||
}
|
||||
} else {
|
||||
@@ -828,6 +876,7 @@ export default function GitLabItemDialog({
|
||||
setCommentDraftState((current) =>
|
||||
current.itemId === itemId ? { itemId, value: '' } : current
|
||||
)
|
||||
useAppStore.getState().recordFeatureInteraction('gitlab-tasks')
|
||||
handleRefresh()
|
||||
}
|
||||
} else {
|
||||
@@ -867,6 +916,7 @@ export default function GitLabItemDialog({
|
||||
}
|
||||
: current
|
||||
)
|
||||
useAppStore.getState().recordFeatureInteraction('gitlab-tasks')
|
||||
}
|
||||
} else if (mountedRef.current) {
|
||||
toast.error(res.error)
|
||||
@@ -907,8 +957,14 @@ export default function GitLabItemDialog({
|
||||
<Sheet open={item !== null} onOpenChange={(open) => !open && onClose()}>
|
||||
<SheetContent side="right" className="flex w-full flex-col gap-0 p-0 sm:max-w-2xl">
|
||||
<VisuallyHidden.Root>
|
||||
<SheetTitle>{item ? visibleTitle : translate("auto.components.GitLabItemDialog.3a051b8ade", "Work item")}</SheetTitle>
|
||||
<SheetDescription>{translate("auto.components.GitLabItemDialog.30c97083c2", "GitLab work item detail")}</SheetDescription>
|
||||
<SheetTitle>
|
||||
{item
|
||||
? visibleTitle
|
||||
: translate('auto.components.GitLabItemDialog.3a051b8ade', 'Work item')}
|
||||
</SheetTitle>
|
||||
<SheetDescription>
|
||||
{translate('auto.components.GitLabItemDialog.30c97083c2', 'GitLab work item detail')}
|
||||
</SheetDescription>
|
||||
</VisuallyHidden.Root>
|
||||
|
||||
{item ? (
|
||||
@@ -923,7 +979,12 @@ export default function GitLabItemDialog({
|
||||
{item.number}
|
||||
</span>
|
||||
<StateBadge state={item.state} />
|
||||
{item.author ? <span>{translate("auto.components.GitLabItemDialog.9bfb4a24d7", "by")}{item.author}</span> : null}
|
||||
{item.author ? (
|
||||
<span>
|
||||
{translate('auto.components.GitLabItemDialog.9bfb4a24d7', 'by')}
|
||||
{item.author}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
<h2 className="mt-1.5 text-lg font-semibold leading-tight text-foreground">
|
||||
{visibleTitle}
|
||||
@@ -944,7 +1005,7 @@ export default function GitLabItemDialog({
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
aria-label={translate("auto.components.GitLabItemDialog.b3c156dd51", "Refresh")}
|
||||
aria-label={translate('auto.components.GitLabItemDialog.b3c156dd51', 'Refresh')}
|
||||
disabled={loading}
|
||||
onClick={handleRefresh}
|
||||
className="size-7"
|
||||
@@ -960,9 +1021,12 @@ export default function GitLabItemDialog({
|
||||
|
||||
<Tabs defaultValue="description" className="flex min-h-0 flex-1 flex-col">
|
||||
<TabsList className="mx-5 mt-3 self-start">
|
||||
<TabsTrigger value="description">{translate("auto.components.GitLabItemDialog.908d8d2a73", "Description")}</TabsTrigger>
|
||||
<TabsTrigger value="description">
|
||||
{translate('auto.components.GitLabItemDialog.908d8d2a73', 'Description')}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="conversation">
|
||||
{translate("auto.components.GitLabItemDialog.c996e2962c", "Conversation")}{details?.comments?.length ? (
|
||||
{translate('auto.components.GitLabItemDialog.c996e2962c', 'Conversation')}
|
||||
{details?.comments?.length ? (
|
||||
<span className="ml-1.5 rounded-full bg-muted px-1.5 text-[10px] font-medium">
|
||||
{details.comments.length}
|
||||
</span>
|
||||
@@ -970,7 +1034,8 @@ export default function GitLabItemDialog({
|
||||
</TabsTrigger>
|
||||
{isMR ? (
|
||||
<TabsTrigger value="files">
|
||||
{translate("auto.components.GitLabItemDialog.be3d291837", "Files")}{details?.files?.length ? (
|
||||
{translate('auto.components.GitLabItemDialog.be3d291837', 'Files')}
|
||||
{details?.files?.length ? (
|
||||
<span className="ml-1.5 rounded-full bg-muted px-1.5 text-[10px] font-medium">
|
||||
{details.files.length}
|
||||
</span>
|
||||
@@ -979,7 +1044,8 @@ export default function GitLabItemDialog({
|
||||
) : null}
|
||||
{isMR ? (
|
||||
<TabsTrigger value="pipeline">
|
||||
{translate("auto.components.GitLabItemDialog.02cbe2de44", "Pipeline")}{details?.pipelineJobs?.length ? (
|
||||
{translate('auto.components.GitLabItemDialog.02cbe2de44', 'Pipeline')}
|
||||
{details?.pipelineJobs?.length ? (
|
||||
<span className="ml-1.5 rounded-full bg-muted px-1.5 text-[10px] font-medium">
|
||||
{details.pipelineJobs.length}
|
||||
</span>
|
||||
@@ -1000,14 +1066,30 @@ export default function GitLabItemDialog({
|
||||
<div className="mb-4 rounded-md border border-border/50 bg-muted/20 p-3">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div>
|
||||
<div className="text-xs font-medium text-foreground">{translate("auto.components.GitLabItemDialog.4f9313984d", "Reviewers")}</div>
|
||||
<div className="text-xs font-medium text-foreground">
|
||||
{translate('auto.components.GitLabItemDialog.4f9313984d', 'Reviewers')}
|
||||
</div>
|
||||
{approvalState ? (
|
||||
<div className="mt-0.5 text-[11px] text-muted-foreground">
|
||||
{approvalState.approvalsLeft === 0
|
||||
? translate("auto.components.GitLabItemDialog.22511537d2", "Approved")
|
||||
: translate("auto.components.GitLabItemDialog.40c56b95e2", "{{value0}} approval{{value1}} remaining", { value0: approvalState.approvalsLeft ?? 0, value1: approvalState.approvalsLeft === 1 ? '' : 's' })}
|
||||
{typeof approvalState.approvalsRequired === "number"
|
||||
? translate("auto.components.GitLabItemDialog.00f3bab87b", " of {{value0}} required", { value0: approvalState.approvalsRequired })
|
||||
? translate(
|
||||
'auto.components.GitLabItemDialog.22511537d2',
|
||||
'Approved'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.GitLabItemDialog.40c56b95e2',
|
||||
'{{value0}} approval{{value1}} remaining',
|
||||
{
|
||||
value0: approvalState.approvalsLeft ?? 0,
|
||||
value1: approvalState.approvalsLeft === 1 ? '' : 's'
|
||||
}
|
||||
)}
|
||||
{typeof approvalState.approvalsRequired === 'number'
|
||||
? translate(
|
||||
'auto.components.GitLabItemDialog.00f3bab87b',
|
||||
' of {{value0}} required',
|
||||
{ value0: approvalState.approvalsRequired }
|
||||
)
|
||||
: ''}
|
||||
</div>
|
||||
) : null}
|
||||
@@ -1022,7 +1104,8 @@ export default function GitLabItemDialog({
|
||||
{reviewerOptionsLoading ? (
|
||||
<LoaderCircle className="size-3 animate-spin" />
|
||||
) : null}
|
||||
{translate("auto.components.GitLabItemDialog.cb55b0390f", "Manage")}</Button>
|
||||
{translate('auto.components.GitLabItemDialog.cb55b0390f', 'Manage')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-2 flex flex-wrap gap-1.5">
|
||||
{currentReviewers.length > 0 ? (
|
||||
@@ -1043,14 +1126,23 @@ export default function GitLabItemDialog({
|
||||
)
|
||||
}
|
||||
className="rounded-full p-0.5 text-muted-foreground hover:bg-muted hover:text-foreground disabled:opacity-50"
|
||||
aria-label={translate("auto.components.GitLabItemDialog.1b19cdc510", "Remove reviewer {{value0}}", { value0: reviewer.username })}
|
||||
aria-label={translate(
|
||||
'auto.components.GitLabItemDialog.1b19cdc510',
|
||||
'Remove reviewer {{value0}}',
|
||||
{ value0: reviewer.username }
|
||||
)}
|
||||
>
|
||||
<X className="size-3" />
|
||||
</button>
|
||||
</span>
|
||||
))
|
||||
) : (
|
||||
<span className="text-[11px] text-muted-foreground">{translate("auto.components.GitLabItemDialog.474b50d988", "No reviewers.")}</span>
|
||||
<span className="text-[11px] text-muted-foreground">
|
||||
{translate(
|
||||
'auto.components.GitLabItemDialog.474b50d988',
|
||||
'No reviewers.'
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{reviewerOptions ? (
|
||||
@@ -1061,7 +1153,12 @@ export default function GitLabItemDialog({
|
||||
onChange={(event) => setReviewerDraftId(event.target.value)}
|
||||
className="h-8 min-w-0 flex-1 rounded-md border border-input bg-background px-2 text-xs text-foreground"
|
||||
>
|
||||
<option value="">{translate("auto.components.GitLabItemDialog.05939e977d", "Add reviewer")}</option>
|
||||
<option value="">
|
||||
{translate(
|
||||
'auto.components.GitLabItemDialog.05939e977d',
|
||||
'Add reviewer'
|
||||
)}
|
||||
</option>
|
||||
{reviewerOptionRows.map((reviewer) => (
|
||||
<option key={gitLabUserKey(reviewer)} value={gitLabUserKey(reviewer)}>
|
||||
{reviewer.username}
|
||||
@@ -1084,7 +1181,8 @@ export default function GitLabItemDialog({
|
||||
{reviewerUpdating ? (
|
||||
<LoaderCircle className="size-3 animate-spin" />
|
||||
) : null}
|
||||
{translate("auto.components.GitLabItemDialog.7a2117129a", "Add")}</Button>
|
||||
{translate('auto.components.GitLabItemDialog.7a2117129a', 'Add')}
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
{approvalState?.rules.length ? (
|
||||
@@ -1096,7 +1194,16 @@ export default function GitLabItemDialog({
|
||||
>
|
||||
<span className="min-w-0 truncate">{rule.name}</span>
|
||||
<span>
|
||||
{rule.approved ? translate("auto.components.GitLabItemDialog.22511537d2", "Approved") : translate("auto.components.GitLabItemDialog.6de8ce0cc6", "{{value0}} required", { value0: rule.approvalsRequired })}
|
||||
{rule.approved
|
||||
? translate(
|
||||
'auto.components.GitLabItemDialog.22511537d2',
|
||||
'Approved'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.GitLabItemDialog.6de8ce0cc6',
|
||||
'{{value0}} required',
|
||||
{ value0: rule.approvalsRequired }
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
@@ -1112,7 +1219,8 @@ export default function GitLabItemDialog({
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<label className="mb-1 block text-xs font-medium text-muted-foreground">
|
||||
{translate("auto.components.GitLabItemDialog.89f3f19368", "Title")}</label>
|
||||
{translate('auto.components.GitLabItemDialog.89f3f19368', 'Title')}
|
||||
</label>
|
||||
<input
|
||||
value={titleDraft}
|
||||
onChange={(event) => setTitleDraft(event.target.value)}
|
||||
@@ -1122,7 +1230,8 @@ export default function GitLabItemDialog({
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-xs font-medium text-muted-foreground">
|
||||
{translate("auto.components.GitLabItemDialog.908d8d2a73", "Description")}</label>
|
||||
{translate('auto.components.GitLabItemDialog.908d8d2a73', 'Description')}
|
||||
</label>
|
||||
<textarea
|
||||
value={bodyDraft}
|
||||
onChange={(event) => setBodyDraft(event.target.value)}
|
||||
@@ -1133,12 +1242,16 @@ export default function GitLabItemDialog({
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-xs font-medium text-muted-foreground">
|
||||
{translate("auto.components.GitLabItemDialog.dde24ade55", "Labels")}</label>
|
||||
{translate('auto.components.GitLabItemDialog.dde24ade55', 'Labels')}
|
||||
</label>
|
||||
<input
|
||||
value={labelDraft}
|
||||
onChange={(event) => setLabelDraft(event.target.value)}
|
||||
disabled={detailsSaving}
|
||||
placeholder={translate("auto.components.GitLabItemDialog.3c0b6ccca7", "bug, backend")}
|
||||
placeholder={translate(
|
||||
'auto.components.GitLabItemDialog.3c0b6ccca7',
|
||||
'bug, backend'
|
||||
)}
|
||||
className="h-9 w-full rounded-md border border-input bg-transparent px-2.5 text-sm shadow-xs focus:border-ring focus:outline-none focus:ring-[3px] focus:ring-ring/50"
|
||||
/>
|
||||
{labelOptionsLoading || labelSuggestionOptions.length > 0 ? (
|
||||
@@ -1146,7 +1259,11 @@ export default function GitLabItemDialog({
|
||||
{labelOptionsLoading ? (
|
||||
<span className="inline-flex h-6 items-center gap-1 rounded-full border border-border/50 px-2 text-[11px] text-muted-foreground">
|
||||
<LoaderCircle className="size-3 animate-spin" />
|
||||
{translate("auto.components.GitLabItemDialog.717b706849", "Loading labels")}</span>
|
||||
{translate(
|
||||
'auto.components.GitLabItemDialog.717b706849',
|
||||
'Loading labels'
|
||||
)}
|
||||
</span>
|
||||
) : null}
|
||||
{labelSuggestionOptions.map((label) => {
|
||||
const selected = parseGitLabLabelDraft(labelDraft).some(
|
||||
@@ -1184,7 +1301,8 @@ export default function GitLabItemDialog({
|
||||
onClick={handleCancelDetailsEdit}
|
||||
>
|
||||
<X className="size-3.5" />
|
||||
{translate("auto.components.GitLabItemDialog.f72fad3b16", "Cancel")}</Button>
|
||||
{translate('auto.components.GitLabItemDialog.f72fad3b16', 'Cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
@@ -1196,7 +1314,8 @@ export default function GitLabItemDialog({
|
||||
) : (
|
||||
<Check className="size-3.5" />
|
||||
)}
|
||||
{translate("auto.components.GitLabItemDialog.93f79a3fc1", "Save")}</Button>
|
||||
{translate('auto.components.GitLabItemDialog.93f79a3fc1', 'Save')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : details?.body ? (
|
||||
@@ -1211,7 +1330,8 @@ export default function GitLabItemDialog({
|
||||
className="gap-1.5"
|
||||
>
|
||||
<Pencil className="size-3.5" />
|
||||
{translate("auto.components.GitLabItemDialog.da4174b00f", "Edit")}</Button>
|
||||
{translate('auto.components.GitLabItemDialog.da4174b00f', 'Edit')}
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
<CommentMarkdown content={details.body} />
|
||||
@@ -1228,10 +1348,16 @@ export default function GitLabItemDialog({
|
||||
className="gap-1.5"
|
||||
>
|
||||
<Pencil className="size-3.5" />
|
||||
{translate("auto.components.GitLabItemDialog.da4174b00f", "Edit")}</Button>
|
||||
{translate('auto.components.GitLabItemDialog.da4174b00f', 'Edit')}
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
<p className="text-sm text-muted-foreground">{translate("auto.components.GitLabItemDialog.14423484db", "No description.")}</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{translate(
|
||||
'auto.components.GitLabItemDialog.14423484db',
|
||||
'No description.'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
@@ -1254,7 +1380,9 @@ export default function GitLabItemDialog({
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">{translate("auto.components.GitLabItemDialog.85a8170279", "No comments yet.")}</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{translate('auto.components.GitLabItemDialog.85a8170279', 'No comments yet.')}
|
||||
</p>
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
@@ -1273,7 +1401,9 @@ export default function GitLabItemDialog({
|
||||
onChange={(event) => setInlineCommentFilePath(event.target.value)}
|
||||
className="h-8 min-w-0 rounded-md border border-input bg-background px-2 text-xs text-foreground"
|
||||
>
|
||||
<option value="">{translate("auto.components.GitLabItemDialog.ceb08a733d", "File")}</option>
|
||||
<option value="">
|
||||
{translate('auto.components.GitLabItemDialog.ceb08a733d', 'File')}
|
||||
</option>
|
||||
{details.files.map((file) => (
|
||||
<option key={file.path} value={file.path}>
|
||||
{file.path}
|
||||
@@ -1284,7 +1414,10 @@ export default function GitLabItemDialog({
|
||||
value={inlineCommentLine}
|
||||
onChange={(event) => setInlineCommentLine(event.target.value)}
|
||||
inputMode="numeric"
|
||||
placeholder={translate("auto.components.GitLabItemDialog.7a7204417f", "Line")}
|
||||
placeholder={translate(
|
||||
'auto.components.GitLabItemDialog.7a7204417f',
|
||||
'Line'
|
||||
)}
|
||||
className="h-8 rounded-md border border-input bg-background px-2 text-xs text-foreground"
|
||||
/>
|
||||
</div>
|
||||
@@ -1292,7 +1425,10 @@ export default function GitLabItemDialog({
|
||||
value={inlineCommentBody}
|
||||
onChange={(event) => setInlineCommentBody(event.target.value)}
|
||||
rows={2}
|
||||
placeholder={translate("auto.components.GitLabItemDialog.21f8dde18a", "Inline comment")}
|
||||
placeholder={translate(
|
||||
'auto.components.GitLabItemDialog.21f8dde18a',
|
||||
'Inline comment'
|
||||
)}
|
||||
className="mt-2 w-full resize-none rounded-md border border-input bg-background px-2.5 py-1.5 text-sm shadow-xs focus:border-ring focus:outline-none focus:ring-[3px] focus:ring-ring/50"
|
||||
/>
|
||||
<div className="mt-2 flex justify-end">
|
||||
@@ -1312,7 +1448,8 @@ export default function GitLabItemDialog({
|
||||
) : (
|
||||
<Send className="size-3.5" />
|
||||
)}
|
||||
{translate("auto.components.GitLabItemDialog.84012fa8fb", "Comment")}</Button>
|
||||
{translate('auto.components.GitLabItemDialog.84012fa8fb', 'Comment')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
@@ -1328,7 +1465,11 @@ export default function GitLabItemDialog({
|
||||
</div>
|
||||
{file.oldPath ? (
|
||||
<div className="break-all font-mono text-[11px] text-muted-foreground">
|
||||
{translate("auto.components.GitLabItemDialog.a7eb4f4916", "from")}{file.oldPath}
|
||||
{translate(
|
||||
'auto.components.GitLabItemDialog.a7eb4f4916',
|
||||
'from'
|
||||
)}
|
||||
{file.oldPath}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -1343,14 +1484,23 @@ export default function GitLabItemDialog({
|
||||
</pre>
|
||||
) : (
|
||||
<div className="px-3 py-3 text-xs text-muted-foreground">
|
||||
{translate("auto.components.GitLabItemDialog.007423f585", "Diff content unavailable.")}</div>
|
||||
{translate(
|
||||
'auto.components.GitLabItemDialog.007423f585',
|
||||
'Diff content unavailable.'
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">{translate("auto.components.GitLabItemDialog.808b1ca1ba", "No changed files.")}</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{translate(
|
||||
'auto.components.GitLabItemDialog.808b1ca1ba',
|
||||
'No changed files.'
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
</TabsContent>
|
||||
) : null}
|
||||
@@ -1376,7 +1526,12 @@ export default function GitLabItemDialog({
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">{translate("auto.components.GitLabItemDialog.f11e3e7675", "No pipeline runs for this MR.")}</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{translate(
|
||||
'auto.components.GitLabItemDialog.f11e3e7675',
|
||||
'No pipeline runs for this MR.'
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
</TabsContent>
|
||||
) : null}
|
||||
@@ -1390,7 +1545,11 @@ export default function GitLabItemDialog({
|
||||
<textarea
|
||||
value={commentDraft}
|
||||
onChange={(e) => updateCommentDraft(e.target.value)}
|
||||
placeholder={translate("auto.components.GitLabItemDialog.c08e1d5a57", "Comment on {{value0}}{{value1}}…", { value0: prefix, value1: item.number })}
|
||||
placeholder={translate(
|
||||
'auto.components.GitLabItemDialog.c08e1d5a57',
|
||||
'Comment on {{value0}}{{value1}}…',
|
||||
{ value0: prefix, value1: item.number }
|
||||
)}
|
||||
rows={2}
|
||||
disabled={commentSubmitting}
|
||||
className="min-h-9 w-full resize-none rounded-md border border-input bg-transparent px-2.5 py-1.5 text-sm shadow-xs focus:border-ring focus:outline-none focus:ring-[3px] focus:ring-ring/50"
|
||||
@@ -1414,7 +1573,8 @@ export default function GitLabItemDialog({
|
||||
) : (
|
||||
<Send className="size-3.5" />
|
||||
)}
|
||||
{translate("auto.components.GitLabItemDialog.84012fa8fb", "Comment")}</Button>
|
||||
{translate('auto.components.GitLabItemDialog.84012fa8fb', 'Comment')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
@@ -1425,11 +1585,13 @@ export default function GitLabItemDialog({
|
||||
className="gap-1.5"
|
||||
>
|
||||
<ExternalLink className="size-3.5" />
|
||||
{translate("auto.components.GitLabItemDialog.f2e64d1c20", "Open in GitLab")}</Button>
|
||||
{translate('auto.components.GitLabItemDialog.f2e64d1c20', 'Open in GitLab')}
|
||||
</Button>
|
||||
<div className="flex items-center gap-2">
|
||||
{onCreateWorkspace ? (
|
||||
<Button variant="outline" size="sm" onClick={() => onCreateWorkspace(item)}>
|
||||
{translate("auto.components.GitLabItemDialog.131865e231", "Create workspace")}</Button>
|
||||
{translate('auto.components.GitLabItemDialog.131865e231', 'Create workspace')}
|
||||
</Button>
|
||||
) : null}
|
||||
{canMerge ? (
|
||||
<Button
|
||||
@@ -1437,10 +1599,11 @@ export default function GitLabItemDialog({
|
||||
disabled={actionInFlight !== null}
|
||||
onClick={() => void handleMerge()}
|
||||
>
|
||||
{actionInFlight === "merge" ? (
|
||||
{actionInFlight === 'merge' ? (
|
||||
<LoaderCircle className="size-3.5 animate-spin" />
|
||||
) : null}
|
||||
{translate("auto.components.GitLabItemDialog.16b3412570", "Merge")}</Button>
|
||||
{translate('auto.components.GitLabItemDialog.16b3412570', 'Merge')}
|
||||
</Button>
|
||||
) : null}
|
||||
{canClose ? (
|
||||
<Button
|
||||
@@ -1449,10 +1612,11 @@ export default function GitLabItemDialog({
|
||||
disabled={actionInFlight !== null}
|
||||
onClick={() => void handleClose()}
|
||||
>
|
||||
{actionInFlight === "close" ? (
|
||||
{actionInFlight === 'close' ? (
|
||||
<LoaderCircle className="size-3.5 animate-spin" />
|
||||
) : null}
|
||||
{translate("auto.components.GitLabItemDialog.a199eb364b", "Close")}</Button>
|
||||
{translate('auto.components.GitLabItemDialog.a199eb364b', 'Close')}
|
||||
</Button>
|
||||
) : null}
|
||||
{canReopen ? (
|
||||
<Button
|
||||
@@ -1461,10 +1625,11 @@ export default function GitLabItemDialog({
|
||||
disabled={actionInFlight !== null}
|
||||
onClick={() => void handleReopen()}
|
||||
>
|
||||
{actionInFlight === "reopen" ? (
|
||||
{actionInFlight === 'reopen' ? (
|
||||
<LoaderCircle className="size-3.5 animate-spin" />
|
||||
) : null}
|
||||
{translate("auto.components.GitLabItemDialog.65e784c1f1", "Reopen")}</Button>
|
||||
{translate('auto.components.GitLabItemDialog.65e784c1f1', 'Reopen')}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -94,9 +94,17 @@ function jiraStatusClass(categoryKey: string): string {
|
||||
async function copyTextToClipboard(text: string, label: string): Promise<void> {
|
||||
try {
|
||||
await window.api.ui.writeClipboardText(text)
|
||||
toast.success(translate("auto.components.JiraIssueWorkspace.2ff69a3545", "{{value0}} copied", { value0: label }))
|
||||
toast.success(
|
||||
translate('auto.components.JiraIssueWorkspace.2ff69a3545', '{{value0}} copied', {
|
||||
value0: label
|
||||
})
|
||||
)
|
||||
} catch {
|
||||
toast.error(translate("auto.components.JiraIssueWorkspace.6c41a9bcea", "Failed to copy {{value0}}", { value0: label.toLowerCase() }))
|
||||
toast.error(
|
||||
translate('auto.components.JiraIssueWorkspace.6c41a9bcea', 'Failed to copy {{value0}}', {
|
||||
value0: label.toLowerCase()
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,7 +261,14 @@ export default function JiraIssueWorkspace({
|
||||
} catch (error) {
|
||||
setFullIssue(previous)
|
||||
patchJiraIssue(previous.key, previous)
|
||||
toast.error(error instanceof Error ? error.message : translate("auto.components.JiraIssueWorkspace.ea21952aa3", "Failed to update Jira issue."))
|
||||
toast.error(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: translate(
|
||||
'auto.components.JiraIssueWorkspace.ea21952aa3',
|
||||
'Failed to update Jira issue.'
|
||||
)
|
||||
)
|
||||
} finally {
|
||||
setPendingField(null)
|
||||
}
|
||||
@@ -308,7 +323,11 @@ export default function JiraIssueWorkspace({
|
||||
setComments((prev) => [...prev, comment])
|
||||
setCommentDraft('')
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : translate("auto.components.JiraIssueWorkspace.fa132c8aed", "Failed to add comment."))
|
||||
toast.error(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: translate('auto.components.JiraIssueWorkspace.fa132c8aed', 'Failed to add comment.')
|
||||
)
|
||||
} finally {
|
||||
setCommentSubmitting(false)
|
||||
}
|
||||
@@ -320,27 +339,30 @@ export default function JiraIssueWorkspace({
|
||||
}
|
||||
return [
|
||||
{
|
||||
label: translate("auto.components.JiraIssueWorkspace.69da9a208c", "Open in Jira"),
|
||||
label: translate('auto.components.JiraIssueWorkspace.69da9a208c', 'Open in Jira'),
|
||||
icon: ExternalLink,
|
||||
action: () => window.api.shell.openUrl(displayed.url)
|
||||
},
|
||||
{
|
||||
label: translate("auto.components.JiraIssueWorkspace.779bb91ee0", "Copy URL"),
|
||||
label: translate('auto.components.JiraIssueWorkspace.779bb91ee0', 'Copy URL'),
|
||||
icon: Clipboard,
|
||||
action: () => void copyTextToClipboard(displayed.url, 'URL')
|
||||
},
|
||||
{
|
||||
label: translate("auto.components.JiraIssueWorkspace.38839801e8", "Copy key"),
|
||||
label: translate('auto.components.JiraIssueWorkspace.38839801e8', 'Copy key'),
|
||||
icon: Clipboard,
|
||||
action: () => void copyTextToClipboard(displayed.key, 'Key')
|
||||
},
|
||||
{
|
||||
label: translate("auto.components.JiraIssueWorkspace.80efa101c5", "Copy suggested branch name"),
|
||||
label: translate(
|
||||
'auto.components.JiraIssueWorkspace.80efa101c5',
|
||||
'Copy suggested branch name'
|
||||
),
|
||||
icon: GitBranch,
|
||||
action: () => void copyTextToClipboard(buildJiraBranchName(displayed), 'Branch name')
|
||||
},
|
||||
{
|
||||
label: translate("auto.components.JiraIssueWorkspace.0cc62bd690", "Copy prompt"),
|
||||
label: translate('auto.components.JiraIssueWorkspace.0cc62bd690', 'Copy prompt'),
|
||||
icon: Clipboard,
|
||||
action: () => void copyTextToClipboard(buildJiraPrompt(displayed), 'Prompt')
|
||||
}
|
||||
@@ -356,11 +378,18 @@ export default function JiraIssueWorkspace({
|
||||
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||
>
|
||||
<VisuallyHidden.Root asChild>
|
||||
<SheetTitle>{displayed?.title ?? translate("auto.components.JiraIssueWorkspace.ef21405c6d", "Jira issue")}</SheetTitle>
|
||||
<SheetTitle>
|
||||
{displayed?.title ??
|
||||
translate('auto.components.JiraIssueWorkspace.ef21405c6d', 'Jira issue')}
|
||||
</SheetTitle>
|
||||
</VisuallyHidden.Root>
|
||||
<VisuallyHidden.Root asChild>
|
||||
<SheetDescription>
|
||||
{translate("auto.components.JiraIssueWorkspace.857bd2f88f", "Preview, edit, and start work from the selected issue.")}</SheetDescription>
|
||||
{translate(
|
||||
'auto.components.JiraIssueWorkspace.857bd2f88f',
|
||||
'Preview, edit, and start work from the selected issue.'
|
||||
)}
|
||||
</SheetDescription>
|
||||
</VisuallyHidden.Root>
|
||||
|
||||
{displayed ? (
|
||||
@@ -384,7 +413,8 @@ export default function JiraIssueWorkspace({
|
||||
className="hidden shrink-0 gap-2 sm:inline-flex"
|
||||
size="sm"
|
||||
>
|
||||
{translate("auto.components.JiraIssueWorkspace.2441be6f9f", "Start workspace")}<ArrowRight className="size-4" />
|
||||
{translate('auto.components.JiraIssueWorkspace.2441be6f9f', 'Start workspace')}
|
||||
<ArrowRight className="size-4" />
|
||||
</Button>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
@@ -393,13 +423,17 @@ export default function JiraIssueWorkspace({
|
||||
size="icon-sm"
|
||||
className="shrink-0"
|
||||
onClick={onClose}
|
||||
aria-label={translate("auto.components.JiraIssueWorkspace.76513c7898", "Close Jira issue preview")}
|
||||
aria-label={translate(
|
||||
'auto.components.JiraIssueWorkspace.76513c7898',
|
||||
'Close Jira issue preview'
|
||||
)}
|
||||
>
|
||||
<X className="size-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={6}>
|
||||
{translate("auto.components.JiraIssueWorkspace.7a96985ca0", "Close")}</TooltipContent>
|
||||
{translate('auto.components.JiraIssueWorkspace.7a96985ca0', 'Close')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
@@ -416,7 +450,7 @@ export default function JiraIssueWorkspace({
|
||||
)}
|
||||
>
|
||||
{displayed.status.name}
|
||||
{pendingField === "transition" ? (
|
||||
{pendingField === 'transition' ? (
|
||||
<LoaderCircle className="size-3 animate-spin" />
|
||||
) : null}
|
||||
</button>
|
||||
@@ -451,8 +485,9 @@ export default function JiraIssueWorkspace({
|
||||
disabled={pendingField === 'priority'}
|
||||
className="rounded-md px-1.5 py-0.5 text-[11px] text-muted-foreground transition hover:bg-muted/40 disabled:opacity-50"
|
||||
>
|
||||
{displayed.priority?.name ?? translate("auto.components.JiraIssueWorkspace.51bed73f88", "No priority")}
|
||||
{pendingField === "priority" ? (
|
||||
{displayed.priority?.name ??
|
||||
translate('auto.components.JiraIssueWorkspace.51bed73f88', 'No priority')}
|
||||
{pendingField === 'priority' ? (
|
||||
<LoaderCircle className="ml-1 inline size-3 animate-spin" />
|
||||
) : null}
|
||||
</button>
|
||||
@@ -468,7 +503,8 @@ export default function JiraIssueWorkspace({
|
||||
}
|
||||
className="flex w-full items-center rounded-sm px-2 py-1.5 text-left text-[12px] hover:bg-accent"
|
||||
>
|
||||
{translate("auto.components.JiraIssueWorkspace.51bed73f88", "No priority")}</button>
|
||||
{translate('auto.components.JiraIssueWorkspace.51bed73f88', 'No priority')}
|
||||
</button>
|
||||
{priorities.map((priority) => (
|
||||
<button
|
||||
key={priority.id}
|
||||
@@ -491,8 +527,9 @@ export default function JiraIssueWorkspace({
|
||||
disabled={pendingField === 'assignee'}
|
||||
className="flex items-center gap-1 rounded-md px-1.5 py-0.5 text-[11px] text-muted-foreground transition hover:bg-muted/40 disabled:opacity-50"
|
||||
>
|
||||
{displayed.assignee?.displayName ?? translate("auto.components.JiraIssueWorkspace.54649eaeab", "+ Assignee")}
|
||||
{pendingField === "assignee" ? (
|
||||
{displayed.assignee?.displayName ??
|
||||
translate('auto.components.JiraIssueWorkspace.54649eaeab', '+ Assignee')}
|
||||
{pendingField === 'assignee' ? (
|
||||
<LoaderCircle className="size-3 animate-spin" />
|
||||
) : null}
|
||||
</button>
|
||||
@@ -512,7 +549,8 @@ export default function JiraIssueWorkspace({
|
||||
}
|
||||
className="flex w-full items-center rounded-sm px-2 py-1.5 text-left text-[12px] hover:bg-accent"
|
||||
>
|
||||
{translate("auto.components.JiraIssueWorkspace.0b6b5646ed", "Unassigned")}</button>
|
||||
{translate('auto.components.JiraIssueWorkspace.0b6b5646ed', 'Unassigned')}
|
||||
</button>
|
||||
{users.map((user) => (
|
||||
<button
|
||||
key={user.accountId}
|
||||
@@ -540,7 +578,9 @@ export default function JiraIssueWorkspace({
|
||||
<div className="min-h-0 overflow-y-auto scrollbar-sleek">
|
||||
<section className="border-b border-border/40 px-4 py-4">
|
||||
<div className="grid gap-2">
|
||||
<label className="text-[11px] font-medium text-muted-foreground">{translate("auto.components.JiraIssueWorkspace.444865b4a8", "Title")}</label>
|
||||
<label className="text-[11px] font-medium text-muted-foreground">
|
||||
{translate('auto.components.JiraIssueWorkspace.444865b4a8', 'Title')}
|
||||
</label>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
value={titleDraft}
|
||||
@@ -559,7 +599,7 @@ export default function JiraIssueWorkspace({
|
||||
onClick={handleSaveTitle}
|
||||
disabled={pendingField === 'title'}
|
||||
>
|
||||
{pendingField === "title" ? (
|
||||
{pendingField === 'title' ? (
|
||||
<LoaderCircle className="size-4 animate-spin" />
|
||||
) : (
|
||||
<Save className="size-4" />
|
||||
@@ -567,12 +607,16 @@ export default function JiraIssueWorkspace({
|
||||
</Button>
|
||||
</div>
|
||||
<label className="mt-2 text-[11px] font-medium text-muted-foreground">
|
||||
{translate("auto.components.JiraIssueWorkspace.aee97b6913", "Labels")}</label>
|
||||
{translate('auto.components.JiraIssueWorkspace.aee97b6913', 'Labels')}
|
||||
</label>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
value={labelsDraft}
|
||||
onChange={(event) => setLabelsDraft(event.target.value)}
|
||||
placeholder={translate("auto.components.JiraIssueWorkspace.0f3c07a901", "backend, bug")}
|
||||
placeholder={translate(
|
||||
'auto.components.JiraIssueWorkspace.0f3c07a901',
|
||||
'backend, bug'
|
||||
)}
|
||||
className="h-8 text-xs"
|
||||
/>
|
||||
<Button
|
||||
@@ -581,7 +625,7 @@ export default function JiraIssueWorkspace({
|
||||
onClick={handleSaveLabels}
|
||||
disabled={pendingField === 'labels'}
|
||||
>
|
||||
{pendingField === "labels" ? (
|
||||
{pendingField === 'labels' ? (
|
||||
<LoaderCircle className="size-4 animate-spin" />
|
||||
) : (
|
||||
<Save className="size-4" />
|
||||
@@ -598,7 +642,9 @@ export default function JiraIssueWorkspace({
|
||||
{displayed.issueType.name}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{displayed.project.key} · {displayed.assignee?.displayName ?? translate("auto.components.JiraIssueWorkspace.0b6b5646ed", "Unassigned")}
|
||||
{displayed.project.key} ·{' '}
|
||||
{displayed.assignee?.displayName ??
|
||||
translate('auto.components.JiraIssueWorkspace.0b6b5646ed', 'Unassigned')}
|
||||
</span>
|
||||
</div>
|
||||
{displayed.description?.trim() ? (
|
||||
@@ -607,14 +653,21 @@ export default function JiraIssueWorkspace({
|
||||
className="text-[14px] leading-relaxed"
|
||||
/>
|
||||
) : (
|
||||
<p className="text-sm italic text-muted-foreground">{translate("auto.components.JiraIssueWorkspace.c4889a47e4", "No description provided.")}</p>
|
||||
<p className="text-sm italic text-muted-foreground">
|
||||
{translate(
|
||||
'auto.components.JiraIssueWorkspace.c4889a47e4',
|
||||
'No description provided.'
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="px-4 py-4">
|
||||
<div className="mb-3 flex items-center justify-between gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[13px] font-medium text-foreground">{translate("auto.components.JiraIssueWorkspace.9a980b06b9", "Comments")}</span>
|
||||
<span className="text-[13px] font-medium text-foreground">
|
||||
{translate('auto.components.JiraIssueWorkspace.9a980b06b9', 'Comments')}
|
||||
</span>
|
||||
{comments.length > 0 ? (
|
||||
<span className="text-[12px] text-muted-foreground">{comments.length}</span>
|
||||
) : null}
|
||||
@@ -632,7 +685,8 @@ export default function JiraIssueWorkspace({
|
||||
) : (
|
||||
<RefreshCw className="size-3" />
|
||||
)}
|
||||
{translate("auto.components.JiraIssueWorkspace.5cd09beaf9", "Retry")}</Button>
|
||||
{translate('auto.components.JiraIssueWorkspace.5cd09beaf9', 'Retry')}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
{commentsError ? (
|
||||
@@ -644,7 +698,12 @@ export default function JiraIssueWorkspace({
|
||||
<LoaderCircle className="size-4 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
) : comments.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">{translate("auto.components.JiraIssueWorkspace.9178090e26", "No comments yet.")}</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{translate(
|
||||
'auto.components.JiraIssueWorkspace.9178090e26',
|
||||
'No comments yet.'
|
||||
)}
|
||||
</p>
|
||||
) : (
|
||||
<div className="flex flex-col gap-3">
|
||||
{comments.map((comment) => (
|
||||
@@ -661,7 +720,11 @@ export default function JiraIssueWorkspace({
|
||||
/>
|
||||
) : null}
|
||||
<span className="truncate text-[13px] font-semibold text-foreground">
|
||||
{comment.user?.displayName ?? translate("auto.components.JiraIssueWorkspace.666cfdd835", "Unknown")}
|
||||
{comment.user?.displayName ??
|
||||
translate(
|
||||
'auto.components.JiraIssueWorkspace.666cfdd835',
|
||||
'Unknown'
|
||||
)}
|
||||
</span>
|
||||
<span className="shrink-0 text-[12px] text-muted-foreground">
|
||||
{formatRelativeTime(comment.createdAt)}
|
||||
@@ -685,7 +748,8 @@ export default function JiraIssueWorkspace({
|
||||
onClick={() => onUse(displayed)}
|
||||
className="mb-3 w-full justify-center gap-2 sm:hidden"
|
||||
>
|
||||
{translate("auto.components.JiraIssueWorkspace.2441be6f9f", "Start workspace")}<ArrowRight className="size-4" />
|
||||
{translate('auto.components.JiraIssueWorkspace.2441be6f9f', 'Start workspace')}
|
||||
<ArrowRight className="size-4" />
|
||||
</Button>
|
||||
<div className="grid gap-1">
|
||||
{actionItems.map((item) => {
|
||||
@@ -717,7 +781,10 @@ export default function JiraIssueWorkspace({
|
||||
<textarea
|
||||
value={commentDraft}
|
||||
onChange={(event) => setCommentDraft(event.target.value)}
|
||||
placeholder={translate("auto.components.JiraIssueWorkspace.a585fd204e", "Add a Jira comment...")}
|
||||
placeholder={translate(
|
||||
'auto.components.JiraIssueWorkspace.a585fd204e',
|
||||
'Add a Jira comment...'
|
||||
)}
|
||||
rows={2}
|
||||
disabled={commentSubmitting}
|
||||
className="min-h-10 flex-1 resize-none rounded-md border border-input bg-transparent px-3 py-2 text-sm outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50"
|
||||
@@ -732,7 +799,8 @@ export default function JiraIssueWorkspace({
|
||||
) : (
|
||||
<Send className="size-4" />
|
||||
)}
|
||||
{translate("auto.components.JiraIssueWorkspace.b0b92666c9", "Comment")}</Button>
|
||||
{translate('auto.components.JiraIssueWorkspace.b0b92666c9', 'Comment')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -45,7 +45,10 @@ type LinearIssueMarkdownToolbarButtonProps = {
|
||||
const linearIssueMarkdownExtensions = [
|
||||
...createRichMarkdownExtensions(),
|
||||
Placeholder.configure({
|
||||
placeholder: translate("auto.components.LinearIssueMarkdownDescriptionEditor.4f2fddc2b7", "No description provided.")
|
||||
placeholder: translate(
|
||||
'auto.components.LinearIssueMarkdownDescriptionEditor.4f2fddc2b7',
|
||||
'No description provided.'
|
||||
)
|
||||
})
|
||||
]
|
||||
|
||||
@@ -91,7 +94,10 @@ function applyLinearIssueLink(editor: Editor | null): void {
|
||||
}
|
||||
|
||||
const previousHref = editor.getAttributes('link').href as string | undefined
|
||||
const href = window.prompt(translate("auto.components.LinearIssueMarkdownDescriptionEditor.5c16ec8f14", "Link URL"), previousHref ?? '')
|
||||
const href = window.prompt(
|
||||
translate('auto.components.LinearIssueMarkdownDescriptionEditor.5c16ec8f14', 'Link URL'),
|
||||
previousHref ?? ''
|
||||
)
|
||||
if (href === null) {
|
||||
editor.chain().focus().run()
|
||||
return
|
||||
@@ -124,16 +130,28 @@ function LinearIssueMarkdownToolbar({
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="linear-issue-markdown-toolbar" aria-label={translate("auto.components.LinearIssueMarkdownDescriptionEditor.7c52151156", "Issue description formatting")}>
|
||||
<div
|
||||
className="linear-issue-markdown-toolbar"
|
||||
aria-label={translate(
|
||||
'auto.components.LinearIssueMarkdownDescriptionEditor.7c52151156',
|
||||
'Issue description formatting'
|
||||
)}
|
||||
>
|
||||
<LinearIssueMarkdownToolbarButton
|
||||
label={translate("auto.components.LinearIssueMarkdownDescriptionEditor.68a41d5665", "Body text")}
|
||||
label={translate(
|
||||
'auto.components.LinearIssueMarkdownDescriptionEditor.68a41d5665',
|
||||
'Body text'
|
||||
)}
|
||||
disabled={disabled}
|
||||
onClick={() => runCommand((nextEditor) => nextEditor.chain().focus().setParagraph().run())}
|
||||
>
|
||||
<Pilcrow className="size-3.5" />
|
||||
</LinearIssueMarkdownToolbarButton>
|
||||
<LinearIssueMarkdownToolbarButton
|
||||
label={translate("auto.components.LinearIssueMarkdownDescriptionEditor.e3f741d258", "Heading 1")}
|
||||
label={translate(
|
||||
'auto.components.LinearIssueMarkdownDescriptionEditor.e3f741d258',
|
||||
'Heading 1'
|
||||
)}
|
||||
active={editor?.isActive('heading', { level: 1 }) ?? false}
|
||||
disabled={disabled}
|
||||
onClick={() =>
|
||||
@@ -143,7 +161,10 @@ function LinearIssueMarkdownToolbar({
|
||||
<Heading1 className="size-3.5" />
|
||||
</LinearIssueMarkdownToolbarButton>
|
||||
<LinearIssueMarkdownToolbarButton
|
||||
label={translate("auto.components.LinearIssueMarkdownDescriptionEditor.dddaa7a0a6", "Heading 2")}
|
||||
label={translate(
|
||||
'auto.components.LinearIssueMarkdownDescriptionEditor.dddaa7a0a6',
|
||||
'Heading 2'
|
||||
)}
|
||||
active={editor?.isActive('heading', { level: 2 }) ?? false}
|
||||
disabled={disabled}
|
||||
onClick={() =>
|
||||
@@ -154,7 +175,7 @@ function LinearIssueMarkdownToolbar({
|
||||
</LinearIssueMarkdownToolbarButton>
|
||||
<LinearIssueMarkdownToolbarSeparator />
|
||||
<LinearIssueMarkdownToolbarButton
|
||||
label={translate("auto.components.LinearIssueMarkdownDescriptionEditor.caa88f50d0", "Bold")}
|
||||
label={translate('auto.components.LinearIssueMarkdownDescriptionEditor.caa88f50d0', 'Bold')}
|
||||
active={editor?.isActive('bold') ?? false}
|
||||
disabled={disabled}
|
||||
onClick={() => runCommand((nextEditor) => nextEditor.chain().focus().toggleBold().run())}
|
||||
@@ -162,7 +183,10 @@ function LinearIssueMarkdownToolbar({
|
||||
<Bold className="size-3.5" />
|
||||
</LinearIssueMarkdownToolbarButton>
|
||||
<LinearIssueMarkdownToolbarButton
|
||||
label={translate("auto.components.LinearIssueMarkdownDescriptionEditor.5666b4493d", "Italic")}
|
||||
label={translate(
|
||||
'auto.components.LinearIssueMarkdownDescriptionEditor.5666b4493d',
|
||||
'Italic'
|
||||
)}
|
||||
active={editor?.isActive('italic') ?? false}
|
||||
disabled={disabled}
|
||||
onClick={() => runCommand((nextEditor) => nextEditor.chain().focus().toggleItalic().run())}
|
||||
@@ -170,7 +194,10 @@ function LinearIssueMarkdownToolbar({
|
||||
<Italic className="size-3.5" />
|
||||
</LinearIssueMarkdownToolbarButton>
|
||||
<LinearIssueMarkdownToolbarButton
|
||||
label={translate("auto.components.LinearIssueMarkdownDescriptionEditor.28fd951b83", "Strike")}
|
||||
label={translate(
|
||||
'auto.components.LinearIssueMarkdownDescriptionEditor.28fd951b83',
|
||||
'Strike'
|
||||
)}
|
||||
active={editor?.isActive('strike') ?? false}
|
||||
disabled={disabled}
|
||||
onClick={() => runCommand((nextEditor) => nextEditor.chain().focus().toggleStrike().run())}
|
||||
@@ -178,7 +205,10 @@ function LinearIssueMarkdownToolbar({
|
||||
<Strikethrough className="size-3.5" />
|
||||
</LinearIssueMarkdownToolbarButton>
|
||||
<LinearIssueMarkdownToolbarButton
|
||||
label={translate("auto.components.LinearIssueMarkdownDescriptionEditor.ad1869bd54", "Inline code")}
|
||||
label={translate(
|
||||
'auto.components.LinearIssueMarkdownDescriptionEditor.ad1869bd54',
|
||||
'Inline code'
|
||||
)}
|
||||
active={editor?.isActive('code') ?? false}
|
||||
disabled={disabled}
|
||||
onClick={() => runCommand((nextEditor) => nextEditor.chain().focus().toggleCode().run())}
|
||||
@@ -187,7 +217,10 @@ function LinearIssueMarkdownToolbar({
|
||||
</LinearIssueMarkdownToolbarButton>
|
||||
<LinearIssueMarkdownToolbarSeparator />
|
||||
<LinearIssueMarkdownToolbarButton
|
||||
label={translate("auto.components.LinearIssueMarkdownDescriptionEditor.c82917e06e", "Bullet list")}
|
||||
label={translate(
|
||||
'auto.components.LinearIssueMarkdownDescriptionEditor.c82917e06e',
|
||||
'Bullet list'
|
||||
)}
|
||||
active={editor?.isActive('bulletList') ?? false}
|
||||
disabled={disabled}
|
||||
onClick={() =>
|
||||
@@ -197,7 +230,10 @@ function LinearIssueMarkdownToolbar({
|
||||
<List className="size-3.5" />
|
||||
</LinearIssueMarkdownToolbarButton>
|
||||
<LinearIssueMarkdownToolbarButton
|
||||
label={translate("auto.components.LinearIssueMarkdownDescriptionEditor.d6b2f3d35b", "Numbered list")}
|
||||
label={translate(
|
||||
'auto.components.LinearIssueMarkdownDescriptionEditor.d6b2f3d35b',
|
||||
'Numbered list'
|
||||
)}
|
||||
active={editor?.isActive('orderedList') ?? false}
|
||||
disabled={disabled}
|
||||
onClick={() =>
|
||||
@@ -207,7 +243,10 @@ function LinearIssueMarkdownToolbar({
|
||||
<ListOrdered className="size-3.5" />
|
||||
</LinearIssueMarkdownToolbarButton>
|
||||
<LinearIssueMarkdownToolbarButton
|
||||
label={translate("auto.components.LinearIssueMarkdownDescriptionEditor.e2a0267c8c", "Checklist")}
|
||||
label={translate(
|
||||
'auto.components.LinearIssueMarkdownDescriptionEditor.e2a0267c8c',
|
||||
'Checklist'
|
||||
)}
|
||||
active={editor?.isActive('taskList') ?? false}
|
||||
disabled={disabled}
|
||||
onClick={() =>
|
||||
@@ -218,7 +257,10 @@ function LinearIssueMarkdownToolbar({
|
||||
</LinearIssueMarkdownToolbarButton>
|
||||
<LinearIssueMarkdownToolbarSeparator />
|
||||
<LinearIssueMarkdownToolbarButton
|
||||
label={translate("auto.components.LinearIssueMarkdownDescriptionEditor.9eaf02ac01", "Quote")}
|
||||
label={translate(
|
||||
'auto.components.LinearIssueMarkdownDescriptionEditor.9eaf02ac01',
|
||||
'Quote'
|
||||
)}
|
||||
active={editor?.isActive('blockquote') ?? false}
|
||||
disabled={disabled}
|
||||
onClick={() =>
|
||||
@@ -228,7 +270,14 @@ function LinearIssueMarkdownToolbar({
|
||||
<Quote className="size-3.5" />
|
||||
</LinearIssueMarkdownToolbarButton>
|
||||
<LinearIssueMarkdownToolbarButton
|
||||
label={editor?.isActive('link') ? translate("auto.components.LinearIssueMarkdownDescriptionEditor.340160f4e8", "Remove link") : translate("auto.components.LinearIssueMarkdownDescriptionEditor.632096eb1c", "Link")}
|
||||
label={
|
||||
editor?.isActive('link')
|
||||
? translate(
|
||||
'auto.components.LinearIssueMarkdownDescriptionEditor.340160f4e8',
|
||||
'Remove link'
|
||||
)
|
||||
: translate('auto.components.LinearIssueMarkdownDescriptionEditor.632096eb1c', 'Link')
|
||||
}
|
||||
active={editor?.isActive('link') ?? false}
|
||||
disabled={disabled}
|
||||
onClick={() => runCommand(applyLinearIssueLink)}
|
||||
@@ -333,10 +382,14 @@ export function LinearIssueMarkdownDescriptionEditor({
|
||||
<div className="linear-issue-markdown-save-hint pointer-events-none absolute bottom-1.5 right-2 z-10 flex items-center gap-1.5 text-[10px] text-muted-foreground/75">
|
||||
<span className="flex items-center gap-1">
|
||||
<span>{submitShortcutLabel}</span>
|
||||
<span>{translate("auto.components.LinearIssueMarkdownDescriptionEditor.a7301a11f3", "save")}</span>
|
||||
<span>
|
||||
{translate('auto.components.LinearIssueMarkdownDescriptionEditor.a7301a11f3', 'save')}
|
||||
</span>
|
||||
</span>
|
||||
<span className="text-muted-foreground/35">·</span>
|
||||
<span>{translate("auto.components.LinearIssueMarkdownDescriptionEditor.d9c47069ef", "Markdown")}</span>
|
||||
<span>
|
||||
{translate('auto.components.LinearIssueMarkdownDescriptionEditor.d9c47069ef', 'Markdown')}
|
||||
</span>
|
||||
</div>
|
||||
{disabled ? (
|
||||
<LoaderCircle className="absolute right-2 top-2 size-4 animate-spin text-muted-foreground" />
|
||||
|
||||
@@ -97,7 +97,9 @@ export function LinearIssueTextEditor({
|
||||
})
|
||||
if (savePlan.kind === 'empty-title') {
|
||||
updateTitleDraft(issue.title)
|
||||
toast.error(translate("auto.components.LinearIssueTextEditor.1e08a1ec80", "Title is required"))
|
||||
toast.error(
|
||||
translate('auto.components.LinearIssueTextEditor.1e08a1ec80', 'Title is required')
|
||||
)
|
||||
return
|
||||
}
|
||||
if (savePlan.kind === 'unchanged') {
|
||||
@@ -130,7 +132,15 @@ export function LinearIssueTextEditor({
|
||||
updateDescriptionDraft(issue.description ?? '')
|
||||
}
|
||||
}
|
||||
toast.error(error instanceof Error ? error.message : translate("auto.components.LinearIssueTextEditor.e8ff595db3", "Failed to update {{value0}}", { value0: field }))
|
||||
toast.error(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: translate(
|
||||
'auto.components.LinearIssueTextEditor.e8ff595db3',
|
||||
'Failed to update {{value0}}',
|
||||
{ value0: field }
|
||||
)
|
||||
)
|
||||
} finally {
|
||||
if (mountedRef.current && lastIssueIdRef.current === issue.id) {
|
||||
setSavingField(null)
|
||||
@@ -190,7 +200,7 @@ export function LinearIssueTextEditor({
|
||||
: 'text-[15px] font-semibold leading-tight'
|
||||
return (
|
||||
<div className="min-w-0">
|
||||
{fields !== "description" ? (
|
||||
{fields !== 'description' ? (
|
||||
<div className="relative">
|
||||
<textarea
|
||||
ref={titleRef}
|
||||
@@ -200,7 +210,10 @@ export function LinearIssueTextEditor({
|
||||
onKeyDown={handleTitleKeyDown}
|
||||
disabled={savingField === 'title'}
|
||||
rows={1}
|
||||
aria-label={translate("auto.components.LinearIssueTextEditor.04d73b72dc", "Issue title")}
|
||||
aria-label={translate(
|
||||
'auto.components.LinearIssueTextEditor.04d73b72dc',
|
||||
'Issue title'
|
||||
)}
|
||||
className={cn(
|
||||
'peer scrollbar-sleek block w-full resize-none overflow-hidden rounded-md border border-transparent bg-transparent px-1 py-0 text-foreground outline-none transition hover:border-border/50 hover:bg-accent/40 focus-visible:border-border focus-visible:bg-background focus-visible:ring-1 focus-visible:ring-ring disabled:opacity-80',
|
||||
titleClass
|
||||
@@ -210,15 +223,15 @@ export function LinearIssueTextEditor({
|
||||
<kbd className="inline-flex h-4 min-w-4 select-none items-center justify-center rounded border border-border bg-muted/70 px-1 font-mono text-[9px] font-medium shadow-xs">
|
||||
↵
|
||||
</kbd>
|
||||
<span>{translate("auto.components.LinearIssueTextEditor.947ba2d6f4", "to save")}</span>
|
||||
<span>{translate('auto.components.LinearIssueTextEditor.947ba2d6f4', 'to save')}</span>
|
||||
</div>
|
||||
{savingField === "title" ? (
|
||||
{savingField === 'title' ? (
|
||||
<LoaderCircle className="absolute right-2 top-2 size-4 animate-spin text-muted-foreground" />
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{fields !== "title" ? (
|
||||
{fields !== 'title' ? (
|
||||
<div className="relative">
|
||||
<LinearIssueMarkdownDescriptionEditor
|
||||
value={descriptionDraft}
|
||||
|
||||
@@ -71,9 +71,17 @@ type LinearIssueWorkspaceProps = {
|
||||
async function copyTextToClipboard(text: string, label: string): Promise<void> {
|
||||
try {
|
||||
await window.api.ui.writeClipboardText(text)
|
||||
toast.success(translate("auto.components.LinearIssueWorkspace.7835483c43", "{{value0}} copied", { value0: label }))
|
||||
toast.success(
|
||||
translate('auto.components.LinearIssueWorkspace.7835483c43', '{{value0}} copied', {
|
||||
value0: label
|
||||
})
|
||||
)
|
||||
} catch {
|
||||
toast.error(translate("auto.components.LinearIssueWorkspace.9bcbaa2737", "Failed to copy {{value0}}", { value0: label.toLowerCase() }))
|
||||
toast.error(
|
||||
translate('auto.components.LinearIssueWorkspace.9bcbaa2737', 'Failed to copy {{value0}}', {
|
||||
value0: label.toLowerCase()
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,11 +152,20 @@ function LinearIssueSubIssueButton({
|
||||
if (fullIssue) {
|
||||
onOpenIssue(fullIssue)
|
||||
} else {
|
||||
toast.error(translate("auto.components.LinearIssueWorkspace.9a1317cdd3", "Failed to load sub-issue"))
|
||||
toast.error(
|
||||
translate('auto.components.LinearIssueWorkspace.9a1317cdd3', 'Failed to load sub-issue')
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
if (mountedRef.current) {
|
||||
toast.error(error instanceof Error ? error.message : translate("auto.components.LinearIssueWorkspace.9a1317cdd3", "Failed to load sub-issue"))
|
||||
toast.error(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: translate(
|
||||
'auto.components.LinearIssueWorkspace.9a1317cdd3',
|
||||
'Failed to load sub-issue'
|
||||
)
|
||||
)
|
||||
}
|
||||
} finally {
|
||||
if (mountedRef.current) {
|
||||
@@ -190,14 +207,25 @@ function LinearIssueSubIssueButton({
|
||||
}
|
||||
return { issueId: issue.id, subIssues: [...currentSubIssues, child] }
|
||||
})
|
||||
toast.success(translate("auto.components.LinearIssueWorkspace.aeed19d003", "Created {{value0}}", { value0: result.identifier }))
|
||||
toast.success(
|
||||
translate('auto.components.LinearIssueWorkspace.aeed19d003', 'Created {{value0}}', {
|
||||
value0: result.identifier
|
||||
})
|
||||
)
|
||||
setTitle('')
|
||||
setOpen(false)
|
||||
} else {
|
||||
toast.error(result.error)
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : translate("auto.components.LinearIssueWorkspace.b25e453c9d", "Failed to create sub-issue"))
|
||||
toast.error(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: translate(
|
||||
'auto.components.LinearIssueWorkspace.b25e453c9d',
|
||||
'Failed to create sub-issue'
|
||||
)
|
||||
)
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
}
|
||||
@@ -242,7 +270,9 @@ function LinearIssueSubIssueButton({
|
||||
className="flex h-9 items-center gap-2 rounded-md px-1 text-sm font-medium text-muted-foreground transition hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
>
|
||||
<Plus className="size-4" />
|
||||
<span>{translate("auto.components.LinearIssueWorkspace.8c55d6696a", "Add sub-issues")}</span>
|
||||
<span>
|
||||
{translate('auto.components.LinearIssueWorkspace.8c55d6696a', 'Add sub-issues')}
|
||||
</span>
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-80 p-3" align="start">
|
||||
@@ -256,7 +286,10 @@ function LinearIssueSubIssueButton({
|
||||
void handleCreate()
|
||||
}
|
||||
}}
|
||||
placeholder={translate("auto.components.LinearIssueWorkspace.c182e02de5", "Sub-issue title")}
|
||||
placeholder={translate(
|
||||
'auto.components.LinearIssueWorkspace.c182e02de5',
|
||||
'Sub-issue title'
|
||||
)}
|
||||
className="h-9 w-full rounded-md border border-input bg-background px-3 text-sm outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
/>
|
||||
<div className="flex justify-end">
|
||||
@@ -266,7 +299,8 @@ function LinearIssueSubIssueButton({
|
||||
disabled={!title.trim() || submitting}
|
||||
>
|
||||
{submitting ? <LoaderCircle className="size-3.5 animate-spin" /> : null}
|
||||
{translate("auto.components.LinearIssueWorkspace.42589845bc", "Create")}</Button>
|
||||
{translate('auto.components.LinearIssueWorkspace.42589845bc', 'Create')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
@@ -305,7 +339,14 @@ function LinearIssueSidebarProjectCard({
|
||||
})
|
||||
.catch((error) => {
|
||||
if (!cancelled) {
|
||||
toast.error(error instanceof Error ? error.message : translate("auto.components.LinearIssueWorkspace.38b80780c2", "Failed to load projects"))
|
||||
toast.error(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: translate(
|
||||
'auto.components.LinearIssueWorkspace.38b80780c2',
|
||||
'Failed to load projects'
|
||||
)
|
||||
)
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
@@ -333,13 +374,22 @@ function LinearIssueSidebarProjectCard({
|
||||
if (result.ok) {
|
||||
onProjectChanged(project)
|
||||
patchLinearIssue(issue.id, { project })
|
||||
toast.success(translate("auto.components.LinearIssueWorkspace.f9d4ef9807", "Project updated"))
|
||||
toast.success(
|
||||
translate('auto.components.LinearIssueWorkspace.f9d4ef9807', 'Project updated')
|
||||
)
|
||||
setOpen(false)
|
||||
} else {
|
||||
toast.error(result.error)
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : translate("auto.components.LinearIssueWorkspace.8b5b593053", "Failed to update project"))
|
||||
toast.error(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: translate(
|
||||
'auto.components.LinearIssueWorkspace.8b5b593053',
|
||||
'Failed to update project'
|
||||
)
|
||||
)
|
||||
} finally {
|
||||
setSavingProjectId(null)
|
||||
}
|
||||
@@ -350,7 +400,7 @@ function LinearIssueSidebarProjectCard({
|
||||
return (
|
||||
<section className="rounded-xl border border-border/60 bg-card text-card-foreground shadow-xs">
|
||||
<div className="flex h-10 items-center gap-1 border-b border-border/50 px-4 text-sm font-medium text-muted-foreground">
|
||||
<span>{translate("auto.components.LinearIssueWorkspace.b51276c8d6", "Project")}</span>
|
||||
<span>{translate('auto.components.LinearIssueWorkspace.b51276c8d6', 'Project')}</span>
|
||||
<ChevronDown className="size-3.5" />
|
||||
</div>
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
@@ -361,7 +411,8 @@ function LinearIssueSidebarProjectCard({
|
||||
>
|
||||
<FolderKanban className="size-4 shrink-0" />
|
||||
<span className="min-w-0 flex-1 truncate">
|
||||
{issue.project?.name ?? translate("auto.components.LinearIssueWorkspace.519c3587f3", "Add to project")}
|
||||
{issue.project?.name ??
|
||||
translate('auto.components.LinearIssueWorkspace.519c3587f3', 'Add to project')}
|
||||
</span>
|
||||
<ChevronDown className="size-3.5 shrink-0" />
|
||||
</button>
|
||||
@@ -371,14 +422,18 @@ function LinearIssueSidebarProjectCard({
|
||||
<input
|
||||
value={query}
|
||||
onChange={(event) => setQuery(event.target.value)}
|
||||
placeholder={translate("auto.components.LinearIssueWorkspace.db3f269d98", "Search projects")}
|
||||
placeholder={translate(
|
||||
'auto.components.LinearIssueWorkspace.db3f269d98',
|
||||
'Search projects'
|
||||
)}
|
||||
className="h-8 w-full rounded-md border border-input bg-background px-2 text-sm outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
/>
|
||||
<div className="max-h-64 overflow-y-auto scrollbar-sleek">
|
||||
{loading ? (
|
||||
<div className="flex items-center gap-2 px-2 py-3 text-sm text-muted-foreground">
|
||||
<LoaderCircle className="size-3.5 animate-spin" />
|
||||
{translate("auto.components.LinearIssueWorkspace.937ba6ad9a", "Loading projects")}</div>
|
||||
{translate('auto.components.LinearIssueWorkspace.937ba6ad9a', 'Loading projects')}
|
||||
</div>
|
||||
) : projects.length > 0 ? (
|
||||
projects.map((project) => (
|
||||
<button
|
||||
@@ -402,7 +457,15 @@ function LinearIssueSidebarProjectCard({
|
||||
))
|
||||
) : (
|
||||
<div className="px-2 py-3 text-sm text-muted-foreground">
|
||||
{query.trim() ? translate("auto.components.LinearIssueWorkspace.c11b4e3cc2", "No projects found.") : translate("auto.components.LinearIssueWorkspace.76ffd3c937", "Search for a project to add.")}
|
||||
{query.trim()
|
||||
? translate(
|
||||
'auto.components.LinearIssueWorkspace.c11b4e3cc2',
|
||||
'No projects found.'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.LinearIssueWorkspace.76ffd3c937',
|
||||
'Search for a project to add.'
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -585,23 +648,26 @@ export default function LinearIssueWorkspace({
|
||||
}
|
||||
return [
|
||||
{
|
||||
label: translate("auto.components.LinearIssueWorkspace.9a9a884236", "Copy URL"),
|
||||
label: translate('auto.components.LinearIssueWorkspace.9a9a884236', 'Copy URL'),
|
||||
icon: Clipboard,
|
||||
action: () => void copyTextToClipboard(displayed.url, 'URL')
|
||||
},
|
||||
{
|
||||
label: translate("auto.components.LinearIssueWorkspace.30c1242f3a", "Copy identifier"),
|
||||
label: translate('auto.components.LinearIssueWorkspace.30c1242f3a', 'Copy identifier'),
|
||||
icon: Clipboard,
|
||||
action: () => void copyTextToClipboard(displayed.identifier, 'Identifier')
|
||||
},
|
||||
{
|
||||
label: translate("auto.components.LinearIssueWorkspace.5d670ec8dc", "Copy suggested branch name"),
|
||||
label: translate(
|
||||
'auto.components.LinearIssueWorkspace.5d670ec8dc',
|
||||
'Copy suggested branch name'
|
||||
),
|
||||
icon: GitBranch,
|
||||
action: () =>
|
||||
void copyTextToClipboard(buildLinearIssueBranchName(displayed), 'Suggested branch name')
|
||||
},
|
||||
{
|
||||
label: translate("auto.components.LinearIssueWorkspace.f6c6381593", "Copy prompt"),
|
||||
label: translate('auto.components.LinearIssueWorkspace.f6c6381593', 'Copy prompt'),
|
||||
icon: Clipboard,
|
||||
action: () => {
|
||||
const renderedText = buildLinearIssueContextSnapshot(displayed, comments)
|
||||
@@ -621,7 +687,7 @@ export default function LinearIssueWorkspace({
|
||||
<div className="flex h-full min-h-0 flex-col overflow-hidden bg-background">
|
||||
<header className="flex h-[61px] flex-none items-center justify-between gap-4 border-b border-border/60 px-5">
|
||||
<div className="flex min-w-0 items-center gap-2 text-sm text-muted-foreground">
|
||||
{variant === "page" ? (
|
||||
{variant === 'page' ? (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
@@ -636,10 +702,13 @@ export default function LinearIssueWorkspace({
|
||||
) : null}
|
||||
<LinearIcon className="size-4 shrink-0 text-muted-foreground" />
|
||||
<span className="truncate font-medium text-foreground">
|
||||
{displayed.workspaceName ?? translate("auto.components.LinearIssueWorkspace.65239a714b", "Linear")}
|
||||
{displayed.workspaceName ??
|
||||
translate('auto.components.LinearIssueWorkspace.65239a714b', 'Linear')}
|
||||
</span>
|
||||
<ChevronRight className="size-3.5 shrink-0" />
|
||||
<span className="shrink-0">{translate("auto.components.LinearIssueWorkspace.f63ef94ea8", "Issues")}</span>
|
||||
<span className="shrink-0">
|
||||
{translate('auto.components.LinearIssueWorkspace.f63ef94ea8', 'Issues')}
|
||||
</span>
|
||||
<ChevronRight className="size-3.5 shrink-0" />
|
||||
<span className="shrink-0 font-mono">{displayed.identifier}</span>
|
||||
<span className="min-w-0 truncate font-medium text-foreground">{displayed.title}</span>
|
||||
@@ -653,13 +722,17 @@ export default function LinearIssueWorkspace({
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={() => void copyTextToClipboard(displayed.url, 'URL')}
|
||||
aria-label={translate("auto.components.LinearIssueWorkspace.97c19a84f1", "Copy Linear URL")}
|
||||
aria-label={translate(
|
||||
'auto.components.LinearIssueWorkspace.97c19a84f1',
|
||||
'Copy Linear URL'
|
||||
)}
|
||||
>
|
||||
<Link className="size-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={6}>
|
||||
{translate("auto.components.LinearIssueWorkspace.9a9a884236", "Copy URL")}</TooltipContent>
|
||||
{translate('auto.components.LinearIssueWorkspace.9a9a884236', 'Copy URL')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
@@ -667,13 +740,17 @@ export default function LinearIssueWorkspace({
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={() => void copyTextToClipboard(displayed.identifier, 'Identifier')}
|
||||
aria-label={translate("auto.components.LinearIssueWorkspace.9e3c49beb8", "Copy issue identifier")}
|
||||
aria-label={translate(
|
||||
'auto.components.LinearIssueWorkspace.9e3c49beb8',
|
||||
'Copy issue identifier'
|
||||
)}
|
||||
>
|
||||
<Clipboard className="size-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={6}>
|
||||
{translate("auto.components.LinearIssueWorkspace.30c1242f3a", "Copy identifier")}</TooltipContent>
|
||||
{translate('auto.components.LinearIssueWorkspace.30c1242f3a', 'Copy identifier')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
@@ -681,28 +758,36 @@ export default function LinearIssueWorkspace({
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={handleUseIssue}
|
||||
aria-label={translate("auto.components.LinearIssueWorkspace.30a7f56c0a", "Start workspace from issue")}
|
||||
aria-label={translate(
|
||||
'auto.components.LinearIssueWorkspace.30a7f56c0a',
|
||||
'Start workspace from issue'
|
||||
)}
|
||||
>
|
||||
<ArrowRight className="size-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={6}>
|
||||
{translate("auto.components.LinearIssueWorkspace.e1e0a9bca9", "Start workspace")}</TooltipContent>
|
||||
{translate('auto.components.LinearIssueWorkspace.e1e0a9bca9', 'Start workspace')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
{variant === "sheet" ? (
|
||||
{variant === 'sheet' ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={onClose}
|
||||
aria-label={translate("auto.components.LinearIssueWorkspace.7a4997d8bb", "Close Linear issue preview")}
|
||||
aria-label={translate(
|
||||
'auto.components.LinearIssueWorkspace.7a4997d8bb',
|
||||
'Close Linear issue preview'
|
||||
)}
|
||||
>
|
||||
<X className="size-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={6}>
|
||||
{translate("auto.components.LinearIssueWorkspace.df4c86ed12", "Close")}</TooltipContent>
|
||||
{translate('auto.components.LinearIssueWorkspace.df4c86ed12', 'Close')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -717,7 +802,9 @@ export default function LinearIssueWorkspace({
|
||||
|
||||
<section className="mt-12 border-t border-border/60 pt-9">
|
||||
<div className="mb-8 flex items-center justify-between gap-3">
|
||||
<h2 className="text-xl font-semibold text-foreground">{translate("auto.components.LinearIssueWorkspace.543970c87a", "Activity")}</h2>
|
||||
<h2 className="text-xl font-semibold text-foreground">
|
||||
{translate('auto.components.LinearIssueWorkspace.543970c87a', 'Activity')}
|
||||
</h2>
|
||||
<div className="flex items-center gap-3 text-sm text-muted-foreground">
|
||||
<LinearIssueAvatar
|
||||
avatarUrl={displayed.assignee?.avatarUrl}
|
||||
@@ -734,7 +821,12 @@ export default function LinearIssueWorkspace({
|
||||
className="size-5"
|
||||
/>
|
||||
<span>
|
||||
{displayed.assignee?.displayName ?? translate("auto.components.LinearIssueWorkspace.8a33c85e9c", "Someone")} {translate("auto.components.LinearIssueWorkspace.fabbd3f974", "updated the issue ·")}{' '}
|
||||
{displayed.assignee?.displayName ??
|
||||
translate('auto.components.LinearIssueWorkspace.8a33c85e9c', 'Someone')}{' '}
|
||||
{translate(
|
||||
'auto.components.LinearIssueWorkspace.fabbd3f974',
|
||||
'updated the issue ·'
|
||||
)}{' '}
|
||||
{formatLinearIssueRelativeTime(displayed.updatedAt)}
|
||||
</span>
|
||||
</div>
|
||||
@@ -754,7 +846,8 @@ export default function LinearIssueWorkspace({
|
||||
) : (
|
||||
<RefreshCw className="size-3" />
|
||||
)}
|
||||
{translate("auto.components.LinearIssueWorkspace.b0eac92d85", "Retry")}</Button>
|
||||
{translate('auto.components.LinearIssueWorkspace.b0eac92d85', 'Retry')}
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
@@ -774,7 +867,11 @@ export default function LinearIssueWorkspace({
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="mb-1 flex min-w-0 items-center gap-2 text-sm">
|
||||
<span className="truncate font-semibold text-foreground">
|
||||
{comment.user?.displayName ?? translate("auto.components.LinearIssueWorkspace.ca8778c124", "Unknown")}
|
||||
{comment.user?.displayName ??
|
||||
translate(
|
||||
'auto.components.LinearIssueWorkspace.ca8778c124',
|
||||
'Unknown'
|
||||
)}
|
||||
</span>
|
||||
<span className="shrink-0 text-muted-foreground">
|
||||
{formatLinearIssueRelativeTime(comment.createdAt)}
|
||||
@@ -816,7 +913,9 @@ export default function LinearIssueWorkspace({
|
||||
/>
|
||||
<section className="rounded-xl border border-border/60 bg-card text-card-foreground shadow-xs">
|
||||
<div className="flex h-10 items-center gap-1 border-b border-border/50 px-4 text-sm font-medium text-muted-foreground">
|
||||
<span>{translate("auto.components.LinearIssueWorkspace.c23e79e5c0", "Actions")}</span>
|
||||
<span>
|
||||
{translate('auto.components.LinearIssueWorkspace.c23e79e5c0', 'Actions')}
|
||||
</span>
|
||||
<ChevronDown className="size-3.5" />
|
||||
</div>
|
||||
<div className="space-y-1 p-3">
|
||||
@@ -867,11 +966,18 @@ export default function LinearIssueWorkspace({
|
||||
}}
|
||||
>
|
||||
<VisuallyHidden.Root asChild>
|
||||
<SheetTitle>{displayed?.title ?? translate("auto.components.LinearIssueWorkspace.61f424f8ca", "Linear issue")}</SheetTitle>
|
||||
<SheetTitle>
|
||||
{displayed?.title ??
|
||||
translate('auto.components.LinearIssueWorkspace.61f424f8ca', 'Linear issue')}
|
||||
</SheetTitle>
|
||||
</VisuallyHidden.Root>
|
||||
<VisuallyHidden.Root asChild>
|
||||
<SheetDescription>
|
||||
{translate("auto.components.LinearIssueWorkspace.ad5dec37b7", "Preview, edit, and start work from the selected issue.")}</SheetDescription>
|
||||
{translate(
|
||||
'auto.components.LinearIssueWorkspace.ad5dec37b7',
|
||||
'Preview, edit, and start work from the selected issue.'
|
||||
)}
|
||||
</SheetDescription>
|
||||
</VisuallyHidden.Root>
|
||||
|
||||
{content}
|
||||
|
||||
@@ -193,6 +193,9 @@ export function LinearIssueEditSection({
|
||||
onEditStateChange({ state: prevState })
|
||||
patchLinearIssue(issue.id, { state: prevState })
|
||||
},
|
||||
onSuccess: () => {
|
||||
useAppStore.getState().recordFeatureInteraction('linear-tasks')
|
||||
},
|
||||
onError: (err) => toast.error(err)
|
||||
})
|
||||
},
|
||||
@@ -222,6 +225,9 @@ export function LinearIssueEditSection({
|
||||
onEditStateChange({ priority: prevPriority })
|
||||
patchLinearIssue(issue.id, { priority: prevPriority })
|
||||
},
|
||||
onSuccess: () => {
|
||||
useAppStore.getState().recordFeatureInteraction('linear-tasks')
|
||||
},
|
||||
onError: (err) => toast.error(err)
|
||||
})
|
||||
},
|
||||
@@ -242,6 +248,9 @@ export function LinearIssueEditSection({
|
||||
onEditStateChange({ estimate: prevEstimate })
|
||||
patchLinearIssue(issue.id, { estimate: prevEstimate })
|
||||
},
|
||||
onSuccess: () => {
|
||||
useAppStore.getState().recordFeatureInteraction('linear-tasks')
|
||||
},
|
||||
onError: (err) => toast.error(err)
|
||||
})
|
||||
},
|
||||
@@ -257,7 +266,12 @@ export function LinearIssueEditSection({
|
||||
|
||||
const estimate = Number(trimmed)
|
||||
if (!Number.isInteger(estimate) || estimate < 0) {
|
||||
toast.error(translate("auto.components.LinearItemDrawer.0be31fef8e", "Estimate must be a non-negative integer"))
|
||||
toast.error(
|
||||
translate(
|
||||
'auto.components.LinearItemDrawer.0be31fef8e',
|
||||
'Estimate must be a non-negative integer'
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -282,6 +296,9 @@ export function LinearIssueEditSection({
|
||||
onEditStateChange({ assignee: prevAssignee })
|
||||
patchLinearIssue(issue.id, { assignee: prevAssignee })
|
||||
},
|
||||
onSuccess: () => {
|
||||
useAppStore.getState().recordFeatureInteraction('linear-tasks')
|
||||
},
|
||||
onError: (err) => toast.error(err)
|
||||
})
|
||||
},
|
||||
@@ -320,6 +337,9 @@ export function LinearIssueEditSection({
|
||||
onEditStateChange({ labelIds: prevLabelIds, labels: prevLabels })
|
||||
patchLinearIssue(issue.id, { labelIds: prevLabelIds, labels: prevLabels })
|
||||
},
|
||||
onSuccess: () => {
|
||||
useAppStore.getState().recordFeatureInteraction('linear-tasks')
|
||||
},
|
||||
onError: (err) => toast.error(err)
|
||||
})
|
||||
},
|
||||
@@ -372,7 +392,7 @@ export function LinearIssueEditSection({
|
||||
<div className="space-y-3">
|
||||
<section className="rounded-xl border border-border/60 bg-card text-card-foreground shadow-xs">
|
||||
<div className="flex h-10 items-center gap-1 border-b border-border/50 px-4 text-sm font-medium text-muted-foreground">
|
||||
<span>{translate("auto.components.LinearItemDrawer.dd304de85a", "Properties")}</span>
|
||||
<span>{translate('auto.components.LinearItemDrawer.dd304de85a', 'Properties')}</span>
|
||||
<ChevronDown className="size-3.5" />
|
||||
</div>
|
||||
<div className="space-y-1 p-3">
|
||||
@@ -403,7 +423,8 @@ export function LinearIssueEditSection({
|
||||
) : states.loading ? (
|
||||
<div className="flex items-center gap-2 px-2 py-3 text-[12px] text-muted-foreground">
|
||||
<LoaderCircle className="size-3 animate-spin" />
|
||||
{translate("auto.components.LinearItemDrawer.59b6cd3706", "Loading states")}</div>
|
||||
{translate('auto.components.LinearItemDrawer.59b6cd3706', 'Loading states')}
|
||||
</div>
|
||||
) : states.data.length > 0 ? (
|
||||
<div>
|
||||
{states.data.map((s) => (
|
||||
@@ -426,7 +447,8 @@ export function LinearIssueEditSection({
|
||||
</div>
|
||||
) : (
|
||||
<div className="px-2 py-3 text-center text-[12px] text-muted-foreground">
|
||||
{translate("auto.components.LinearItemDrawer.780ea6ed89", "No states found")}</div>
|
||||
{translate('auto.components.LinearItemDrawer.780ea6ed89', 'No states found')}
|
||||
</div>
|
||||
)}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
@@ -482,7 +504,9 @@ export function LinearIssueEditSection({
|
||||
<UserRound className={propertyIconClass} />
|
||||
)}
|
||||
<span className="min-w-0 flex-1 truncate">
|
||||
{localAssignee ? localAssignee.displayName : translate("auto.components.LinearItemDrawer.866316f22c", "Unassigned")}
|
||||
{localAssignee
|
||||
? localAssignee.displayName
|
||||
: translate('auto.components.LinearItemDrawer.866316f22c', 'Unassigned')}
|
||||
</span>
|
||||
<LinearEditChipAdornment loading={members.loading} pending={assigneePending} />
|
||||
</button>
|
||||
@@ -497,7 +521,8 @@ export function LinearIssueEditSection({
|
||||
onClick={() => handleAssigneeChange('__unassign__')}
|
||||
className={cn(LINEAR_EDIT_MENU_ITEM_CLASS, !localAssignee && 'bg-accent/50')}
|
||||
>
|
||||
{translate("auto.components.LinearItemDrawer.866316f22c", "Unassigned")}</button>
|
||||
{translate('auto.components.LinearItemDrawer.866316f22c', 'Unassigned')}
|
||||
</button>
|
||||
{members.error ? (
|
||||
<div className="px-2 py-3 text-center text-[12px] text-destructive">
|
||||
{members.error}
|
||||
@@ -505,7 +530,8 @@ export function LinearIssueEditSection({
|
||||
) : members.loading ? (
|
||||
<div className="flex items-center gap-2 px-2 py-3 text-[12px] text-muted-foreground">
|
||||
<LoaderCircle className="size-3 animate-spin" />
|
||||
{translate("auto.components.LinearItemDrawer.b2376d0179", "Loading members")}</div>
|
||||
{translate('auto.components.LinearItemDrawer.b2376d0179', 'Loading members')}
|
||||
</div>
|
||||
) : (
|
||||
members.data.map((m) => (
|
||||
<button
|
||||
@@ -567,7 +593,10 @@ export function LinearIssueEditSection({
|
||||
}
|
||||
}}
|
||||
inputMode="numeric"
|
||||
placeholder={translate("auto.components.LinearItemDrawer.fbb90300e2", "Custom estimate")}
|
||||
placeholder={translate(
|
||||
'auto.components.LinearItemDrawer.fbb90300e2',
|
||||
'Custom estimate'
|
||||
)}
|
||||
className="h-8 text-sm"
|
||||
/>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
@@ -577,7 +606,8 @@ export function LinearIssueEditSection({
|
||||
size="sm"
|
||||
onClick={() => handleEstimateChange(null)}
|
||||
>
|
||||
{translate("auto.components.LinearItemDrawer.ceeb8c6153", "Clear")}</Button>
|
||||
{translate('auto.components.LinearItemDrawer.ceeb8c6153', 'Clear')}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
@@ -585,7 +615,8 @@ export function LinearIssueEditSection({
|
||||
disabled={estimatePending}
|
||||
>
|
||||
{estimatePending ? <LoaderCircle className="size-3.5 animate-spin" /> : null}
|
||||
{translate("auto.components.LinearItemDrawer.b5675b0694", "Save")}</Button>
|
||||
{translate('auto.components.LinearItemDrawer.b5675b0694', 'Save')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
@@ -595,7 +626,7 @@ export function LinearIssueEditSection({
|
||||
|
||||
<section className="rounded-xl border border-border/60 bg-card text-card-foreground shadow-xs">
|
||||
<div className="flex h-10 items-center gap-1 border-b border-border/50 px-4 text-sm font-medium text-muted-foreground">
|
||||
<span>{translate("auto.components.LinearItemDrawer.64bfffc4dd", "Labels")}</span>
|
||||
<span>{translate('auto.components.LinearItemDrawer.64bfffc4dd', 'Labels')}</span>
|
||||
<ChevronDown className="size-3.5" />
|
||||
</div>
|
||||
<div className="p-3">
|
||||
@@ -606,13 +637,21 @@ export function LinearIssueEditSection({
|
||||
disabled={labelsPending}
|
||||
className={propertyRowClass}
|
||||
aria-label={
|
||||
localLabels.length ? translate("auto.components.LinearItemDrawer.7f7b89b631", "Labels: {{value0}}", { value0: localLabels.join(', ') }) : translate("auto.components.LinearItemDrawer.23886c7eec", "Add label")
|
||||
localLabels.length
|
||||
? translate(
|
||||
'auto.components.LinearItemDrawer.7f7b89b631',
|
||||
'Labels: {{value0}}',
|
||||
{ value0: localLabels.join(', ') }
|
||||
)
|
||||
: translate('auto.components.LinearItemDrawer.23886c7eec', 'Add label')
|
||||
}
|
||||
aria-busy={labelsPending || labels.loading}
|
||||
>
|
||||
<Tag className={propertyIconClass} />
|
||||
<span className="min-w-0 flex-1 truncate">
|
||||
{localLabels.length ? labelSummary : translate("auto.components.LinearItemDrawer.23886c7eec", "Add label")}
|
||||
{localLabels.length
|
||||
? labelSummary
|
||||
: translate('auto.components.LinearItemDrawer.23886c7eec', 'Add label')}
|
||||
</span>
|
||||
<LinearEditChipAdornment loading={labels.loading} pending={labelsPending} />
|
||||
</button>
|
||||
@@ -628,7 +667,8 @@ export function LinearIssueEditSection({
|
||||
) : labels.loading ? (
|
||||
<div className="flex items-center gap-2 px-2 py-3 text-[12px] text-muted-foreground">
|
||||
<LoaderCircle className="size-3 animate-spin" />
|
||||
{translate("auto.components.LinearItemDrawer.cddd9b04a7", "Loading labels")}</div>
|
||||
{translate('auto.components.LinearItemDrawer.cddd9b04a7', 'Loading labels')}
|
||||
</div>
|
||||
) : labels.data.length > 0 ? (
|
||||
<div>
|
||||
{labels.data.map((label) => (
|
||||
@@ -658,7 +698,8 @@ export function LinearIssueEditSection({
|
||||
</div>
|
||||
) : (
|
||||
<div className="px-2 py-3 text-center text-[12px] text-muted-foreground">
|
||||
{translate("auto.components.LinearItemDrawer.367f828482", "No labels found")}</div>
|
||||
{translate('auto.components.LinearItemDrawer.367f828482', 'No labels found')}
|
||||
</div>
|
||||
)}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
@@ -694,7 +735,8 @@ export function LinearIssueEditSection({
|
||||
) : states.loading ? (
|
||||
<div className="flex items-center gap-2 px-2 py-3 text-[12px] text-muted-foreground">
|
||||
<LoaderCircle className="size-3 animate-spin" />
|
||||
{translate("auto.components.LinearItemDrawer.59b6cd3706", "Loading states")}</div>
|
||||
{translate('auto.components.LinearItemDrawer.59b6cd3706', 'Loading states')}
|
||||
</div>
|
||||
) : states.data.length > 0 ? (
|
||||
<div>
|
||||
{states.data.map((s) => (
|
||||
@@ -717,7 +759,8 @@ export function LinearIssueEditSection({
|
||||
</div>
|
||||
) : (
|
||||
<div className="px-2 py-3 text-center text-[12px] text-muted-foreground">
|
||||
{translate("auto.components.LinearItemDrawer.780ea6ed89", "No states found")}</div>
|
||||
{translate('auto.components.LinearItemDrawer.780ea6ed89', 'No states found')}
|
||||
</div>
|
||||
)}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
@@ -796,7 +839,10 @@ export function LinearIssueEditSection({
|
||||
}
|
||||
}}
|
||||
inputMode="numeric"
|
||||
placeholder={translate("auto.components.LinearItemDrawer.fbb90300e2", "Custom estimate")}
|
||||
placeholder={translate(
|
||||
'auto.components.LinearItemDrawer.fbb90300e2',
|
||||
'Custom estimate'
|
||||
)}
|
||||
className="h-8 text-sm"
|
||||
/>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
@@ -806,7 +852,8 @@ export function LinearIssueEditSection({
|
||||
size="sm"
|
||||
onClick={() => handleEstimateChange(null)}
|
||||
>
|
||||
{translate("auto.components.LinearItemDrawer.ceeb8c6153", "Clear")}</Button>
|
||||
{translate('auto.components.LinearItemDrawer.ceeb8c6153', 'Clear')}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
@@ -814,7 +861,8 @@ export function LinearIssueEditSection({
|
||||
disabled={estimatePending}
|
||||
>
|
||||
{estimatePending ? <LoaderCircle className="size-3.5 animate-spin" /> : null}
|
||||
{translate("auto.components.LinearItemDrawer.b5675b0694", "Save")}</Button>
|
||||
{translate('auto.components.LinearItemDrawer.b5675b0694', 'Save')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
@@ -830,7 +878,9 @@ export function LinearIssueEditSection({
|
||||
aria-busy={assigneePending || members.loading}
|
||||
>
|
||||
<span className="truncate">
|
||||
{localAssignee ? localAssignee.displayName : translate("auto.components.LinearItemDrawer.d71cd3003e", "+ Assignee")}
|
||||
{localAssignee
|
||||
? localAssignee.displayName
|
||||
: translate('auto.components.LinearItemDrawer.d71cd3003e', '+ Assignee')}
|
||||
</span>
|
||||
<LinearEditChipAdornment loading={members.loading} pending={assigneePending} />
|
||||
</button>
|
||||
@@ -842,7 +892,8 @@ export function LinearIssueEditSection({
|
||||
onClick={() => handleAssigneeChange('__unassign__')}
|
||||
className={cn(LINEAR_EDIT_MENU_ITEM_CLASS, !localAssignee && 'bg-accent/50')}
|
||||
>
|
||||
{translate("auto.components.LinearItemDrawer.866316f22c", "Unassigned")}</button>
|
||||
{translate('auto.components.LinearItemDrawer.866316f22c', 'Unassigned')}
|
||||
</button>
|
||||
{members.error ? (
|
||||
<div className="px-2 py-3 text-center text-[12px] text-destructive">
|
||||
{members.error}
|
||||
@@ -850,7 +901,8 @@ export function LinearIssueEditSection({
|
||||
) : members.loading ? (
|
||||
<div className="flex items-center gap-2 px-2 py-3 text-[12px] text-muted-foreground">
|
||||
<LoaderCircle className="size-3 animate-spin" />
|
||||
{translate("auto.components.LinearItemDrawer.b2376d0179", "Loading members")}</div>
|
||||
{translate('auto.components.LinearItemDrawer.b2376d0179', 'Loading members')}
|
||||
</div>
|
||||
) : (
|
||||
members.data.map((m) => (
|
||||
<button
|
||||
@@ -877,7 +929,13 @@ export function LinearIssueEditSection({
|
||||
type="button"
|
||||
disabled={labelsPending}
|
||||
className={LINEAR_EDIT_CHIP_CLASS}
|
||||
aria-label={localLabels.length ? translate("auto.components.LinearItemDrawer.7f7b89b631", "Labels: {{value0}}", { value0: localLabels.join(', ') }) : translate("auto.components.LinearItemDrawer.23886c7eec", "Add label")}
|
||||
aria-label={
|
||||
localLabels.length
|
||||
? translate('auto.components.LinearItemDrawer.7f7b89b631', 'Labels: {{value0}}', {
|
||||
value0: localLabels.join(', ')
|
||||
})
|
||||
: translate('auto.components.LinearItemDrawer.23886c7eec', 'Add label')
|
||||
}
|
||||
aria-busy={labelsPending || labels.loading}
|
||||
>
|
||||
<span className="truncate">{labelSummary}</span>
|
||||
@@ -890,7 +948,8 @@ export function LinearIssueEditSection({
|
||||
) : labels.loading ? (
|
||||
<div className="flex items-center gap-2 px-2 py-3 text-[12px] text-muted-foreground">
|
||||
<LoaderCircle className="size-3 animate-spin" />
|
||||
{translate("auto.components.LinearItemDrawer.cddd9b04a7", "Loading labels")}</div>
|
||||
{translate('auto.components.LinearItemDrawer.cddd9b04a7', 'Loading labels')}
|
||||
</div>
|
||||
) : labels.data.length > 0 ? (
|
||||
<div>
|
||||
{labels.data.map((label) => (
|
||||
@@ -920,7 +979,8 @@ export function LinearIssueEditSection({
|
||||
</div>
|
||||
) : (
|
||||
<div className="px-2 py-3 text-center text-[12px] text-muted-foreground">
|
||||
{translate("auto.components.LinearItemDrawer.367f828482", "No labels found")}</div>
|
||||
{translate('auto.components.LinearItemDrawer.367f828482', 'No labels found')}
|
||||
</div>
|
||||
)}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
@@ -977,17 +1037,25 @@ export function LinearIssueCommentFooter({
|
||||
}
|
||||
if (typed.ok) {
|
||||
setBody('')
|
||||
useAppStore.getState().recordFeatureInteraction('linear-tasks')
|
||||
onCommentAdded({
|
||||
id: typed.id ?? createBrowserUuid(),
|
||||
body: trimmed,
|
||||
createdAt: new Date().toISOString()
|
||||
})
|
||||
} else {
|
||||
toast.error(typed.error ?? translate("auto.components.LinearItemDrawer.6ab35eafd5", "Failed to add comment"))
|
||||
toast.error(
|
||||
typed.error ??
|
||||
translate('auto.components.LinearItemDrawer.6ab35eafd5', 'Failed to add comment')
|
||||
)
|
||||
}
|
||||
} catch (err) {
|
||||
if (mountedRef.current) {
|
||||
toast.error(err instanceof Error ? err.message : translate("auto.components.LinearItemDrawer.6ab35eafd5", "Failed to add comment"))
|
||||
toast.error(
|
||||
err instanceof Error
|
||||
? err.message
|
||||
: translate('auto.components.LinearItemDrawer.6ab35eafd5', 'Failed to add comment')
|
||||
)
|
||||
}
|
||||
} finally {
|
||||
if (mountedRef.current) {
|
||||
@@ -1020,19 +1088,26 @@ export function LinearIssueCommentFooter({
|
||||
autoGrow()
|
||||
}}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder={translate("auto.components.LinearItemDrawer.2820f0f0f0", "Leave a comment...")}
|
||||
placeholder={translate(
|
||||
'auto.components.LinearItemDrawer.2820f0f0f0',
|
||||
'Leave a comment...'
|
||||
)}
|
||||
rows={3}
|
||||
className="scrollbar-sleek min-h-24 max-h-40 w-full resize-none overflow-y-auto rounded-t-xl bg-transparent px-5 py-4 text-sm placeholder:text-muted-foreground focus-visible:outline-none"
|
||||
/>
|
||||
<div className="flex items-center justify-between px-4 pb-3">
|
||||
<span className="text-[11px] text-muted-foreground">
|
||||
{submitShortcutLabel !== "Unassigned" ? translate("auto.components.LinearItemDrawer.fda549766e", "{{value0}} to comment", { value0: submitShortcutLabel }) : ''}
|
||||
{submitShortcutLabel !== 'Unassigned'
|
||||
? translate('auto.components.LinearItemDrawer.fda549766e', '{{value0}} to comment', {
|
||||
value0: submitShortcutLabel
|
||||
})
|
||||
: ''}
|
||||
</span>
|
||||
<Button
|
||||
size="icon-sm"
|
||||
onClick={handleSubmit}
|
||||
disabled={!body.trim() || submitting}
|
||||
aria-label={translate("auto.components.LinearItemDrawer.d369841269", "Send comment")}
|
||||
aria-label={translate('auto.components.LinearItemDrawer.d369841269', 'Send comment')}
|
||||
>
|
||||
{submitting ? (
|
||||
<LoaderCircle className="size-3.5 animate-spin" />
|
||||
@@ -1058,7 +1133,7 @@ export function LinearIssueCommentFooter({
|
||||
autoGrow()
|
||||
}}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder={translate("auto.components.LinearItemDrawer.2fcff829a8", "Add a comment…")}
|
||||
placeholder={translate('auto.components.LinearItemDrawer.2fcff829a8', 'Add a comment…')}
|
||||
rows={1}
|
||||
className="scrollbar-sleek min-h-[32px] max-h-[96px] flex-1 resize-none overflow-y-auto rounded-md border border-input bg-transparent px-3 py-2 text-[13px] placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
/>
|
||||
@@ -1067,7 +1142,7 @@ export function LinearIssueCommentFooter({
|
||||
onClick={handleSubmit}
|
||||
disabled={!body.trim() || submitting}
|
||||
className="size-8 shrink-0"
|
||||
aria-label={translate("auto.components.LinearItemDrawer.d369841269", "Send comment")}
|
||||
aria-label={translate('auto.components.LinearItemDrawer.d369841269', 'Send comment')}
|
||||
>
|
||||
{submitting ? (
|
||||
<LoaderCircle className="size-3.5 animate-spin" />
|
||||
@@ -1238,10 +1313,18 @@ export default function LinearItemDrawer({
|
||||
}}
|
||||
>
|
||||
<VisuallyHidden.Root asChild>
|
||||
<SheetTitle>{displayed?.title ?? translate("auto.components.LinearItemDrawer.39883467f4", "Linear issue")}</SheetTitle>
|
||||
<SheetTitle>
|
||||
{displayed?.title ??
|
||||
translate('auto.components.LinearItemDrawer.39883467f4', 'Linear issue')}
|
||||
</SheetTitle>
|
||||
</VisuallyHidden.Root>
|
||||
<VisuallyHidden.Root asChild>
|
||||
<SheetDescription>{translate("auto.components.LinearItemDrawer.04a442f796", "Preview and edit the selected Linear issue.")}</SheetDescription>
|
||||
<SheetDescription>
|
||||
{translate(
|
||||
'auto.components.LinearItemDrawer.04a442f796',
|
||||
'Preview and edit the selected Linear issue.'
|
||||
)}
|
||||
</SheetDescription>
|
||||
</VisuallyHidden.Root>
|
||||
|
||||
{displayed && (
|
||||
@@ -1276,13 +1359,17 @@ export default function LinearItemDrawer({
|
||||
size="icon"
|
||||
className="size-7"
|
||||
onClick={() => window.api.shell.openUrl(displayed.url)}
|
||||
aria-label={translate("auto.components.LinearItemDrawer.0190b760c1", "Open on Linear")}
|
||||
aria-label={translate(
|
||||
'auto.components.LinearItemDrawer.0190b760c1',
|
||||
'Open on Linear'
|
||||
)}
|
||||
>
|
||||
<ExternalLink className="size-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={6}>
|
||||
{translate("auto.components.LinearItemDrawer.0190b760c1", "Open on Linear")}</TooltipContent>
|
||||
{translate('auto.components.LinearItemDrawer.0190b760c1', 'Open on Linear')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
@@ -1291,13 +1378,17 @@ export default function LinearItemDrawer({
|
||||
size="icon"
|
||||
className="size-7"
|
||||
onClick={onClose}
|
||||
aria-label={translate("auto.components.LinearItemDrawer.858d0630da", "Close preview")}
|
||||
aria-label={translate(
|
||||
'auto.components.LinearItemDrawer.858d0630da',
|
||||
'Close preview'
|
||||
)}
|
||||
>
|
||||
<X className="size-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={6}>
|
||||
{translate("auto.components.LinearItemDrawer.9dc54172db", "Close · Esc")}</TooltipContent>
|
||||
{translate('auto.components.LinearItemDrawer.9dc54172db', 'Close · Esc')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1325,7 +1416,9 @@ export default function LinearItemDrawer({
|
||||
|
||||
<div className="border-t border-border/40 px-4 py-4">
|
||||
<div className="flex items-center gap-2 pb-3">
|
||||
<span className="text-[13px] font-medium text-foreground">{translate("auto.components.LinearItemDrawer.fde849b2b6", "Comments")}</span>
|
||||
<span className="text-[13px] font-medium text-foreground">
|
||||
{translate('auto.components.LinearItemDrawer.fde849b2b6', 'Comments')}
|
||||
</span>
|
||||
{comments.length > 0 && (
|
||||
<span className="text-[12px] text-muted-foreground">{comments.length}</span>
|
||||
)}
|
||||
@@ -1335,7 +1428,9 @@ export default function LinearItemDrawer({
|
||||
<LoaderCircle className="size-4 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
) : comments.length === 0 ? (
|
||||
<p className="text-[13px] text-muted-foreground">{translate("auto.components.LinearItemDrawer.a4fcc57522", "No comments yet.")}</p>
|
||||
<p className="text-[13px] text-muted-foreground">
|
||||
{translate('auto.components.LinearItemDrawer.a4fcc57522', 'No comments yet.')}
|
||||
</p>
|
||||
) : (
|
||||
<div className="flex flex-col gap-3">
|
||||
{comments.map((comment) => (
|
||||
@@ -1352,7 +1447,8 @@ export default function LinearItemDrawer({
|
||||
/>
|
||||
)}
|
||||
<span className="text-[13px] font-semibold text-foreground">
|
||||
{comment.user?.displayName ?? translate("auto.components.LinearItemDrawer.48e17e8cbd", "Unknown")}
|
||||
{comment.user?.displayName ??
|
||||
translate('auto.components.LinearItemDrawer.48e17e8cbd', 'Unknown')}
|
||||
</span>
|
||||
<span className="text-[12px] text-muted-foreground">
|
||||
· {formatRelativeTime(comment.createdAt)}
|
||||
@@ -1381,9 +1477,16 @@ export default function LinearItemDrawer({
|
||||
<Button
|
||||
onClick={() => onUse(displayed)}
|
||||
className="w-full justify-center gap-2"
|
||||
aria-label={translate("auto.components.LinearItemDrawer.04008e6c46", "Start workspace from issue")}
|
||||
aria-label={translate(
|
||||
'auto.components.LinearItemDrawer.04008e6c46',
|
||||
'Start workspace from issue'
|
||||
)}
|
||||
>
|
||||
{translate("auto.components.LinearItemDrawer.04008e6c46", "Start workspace from issue")}<ArrowRight className="size-4" />
|
||||
{translate(
|
||||
'auto.components.LinearItemDrawer.04008e6c46',
|
||||
'Start workspace from issue'
|
||||
)}
|
||||
<ArrowRight className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -229,7 +229,11 @@ function QuickTabBody({
|
||||
<DialogHeader className="gap-1">
|
||||
<DialogTitle className="text-base font-semibold">{primaryActionLabel}</DialogTitle>
|
||||
<DialogDescription className="sr-only">
|
||||
{translate("auto.components.NewWorkspaceComposerModal.fa90f739a5", "Choose the project, workspace name, and agent before creating the workspace.")}</DialogDescription>
|
||||
{translate(
|
||||
'auto.components.NewWorkspaceComposerModal.fa90f739a5',
|
||||
'Choose the project, workspace name, and agent before creating the workspace.'
|
||||
)}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<NewWorkspaceComposerCard
|
||||
contextualTourSource={modalData.contextualTourSource}
|
||||
|
||||
@@ -122,11 +122,21 @@ function InstallRgGuidance({
|
||||
className="flex items-start gap-2.5 rounded-md border border-amber-500/40 bg-amber-500/10 px-3 py-2.5 text-amber-700 dark:text-amber-300"
|
||||
>
|
||||
<AlertTriangle size={16} className="mt-0.5 shrink-0" aria-hidden="true" />
|
||||
<p className="text-[13px] leading-5">{translate("auto.components.QuickOpen.4725b0e931", "Quick Open scan too large (")}{reason}).</p>
|
||||
<p className="text-[13px] leading-5">
|
||||
{translate('auto.components.QuickOpen.4725b0e931', 'Quick Open scan too large (')}
|
||||
{reason}).
|
||||
</p>
|
||||
</div>
|
||||
<p>
|
||||
{translate("auto.components.QuickOpen.2ca749c15d", "Install")}{' '}
|
||||
<code className="rounded bg-muted px-1 py-0.5 font-mono text-foreground">{translate("auto.components.QuickOpen.5d80dc39bb", "ripgrep")}</code> {translate("auto.components.QuickOpen.1cf8561ab4", "on the remote to enable fast, gitignore-aware listing:")}</p>
|
||||
{translate('auto.components.QuickOpen.2ca749c15d', 'Install')}{' '}
|
||||
<code className="rounded bg-muted px-1 py-0.5 font-mono text-foreground">
|
||||
{translate('auto.components.QuickOpen.5d80dc39bb', 'ripgrep')}
|
||||
</code>{' '}
|
||||
{translate(
|
||||
'auto.components.QuickOpen.1cf8561ab4',
|
||||
'on the remote to enable fast, gitignore-aware listing:'
|
||||
)}
|
||||
</p>
|
||||
{command ? (
|
||||
<div className="flex items-center gap-2 rounded border border-border bg-muted/50 px-3 py-2 font-mono text-xs text-foreground">
|
||||
<span className="flex-1 truncate">{command}</span>
|
||||
@@ -135,10 +145,12 @@ function InstallRgGuidance({
|
||||
type="button"
|
||||
onClick={handleCopy}
|
||||
className="flex items-center gap-1 rounded px-2 py-1 text-xs text-muted-foreground hover:bg-muted hover:text-foreground transition-colors"
|
||||
aria-label={translate("auto.components.QuickOpen.73b44e7bde", "Copy install command")}
|
||||
aria-label={translate('auto.components.QuickOpen.73b44e7bde', 'Copy install command')}
|
||||
>
|
||||
{copied ? <Check size={12} /> : <Copy size={12} />}
|
||||
{copied ? translate("auto.components.QuickOpen.cf144856dc", "Copied") : translate("auto.components.QuickOpen.995be8ea22", "Copy")}
|
||||
{copied
|
||||
? translate('auto.components.QuickOpen.cf144856dc', 'Copied')
|
||||
: translate('auto.components.QuickOpen.995be8ea22', 'Copy')}
|
||||
</button>
|
||||
</div>
|
||||
) : guidance ? (
|
||||
@@ -218,13 +230,19 @@ export default function QuickOpen(): React.JSX.Element | null {
|
||||
onOpenChange={handleOpenChange}
|
||||
shouldFilter={false}
|
||||
onCloseAutoFocus={handleCloseAutoFocus}
|
||||
title={translate("auto.components.QuickOpen.ec31e058f7", "Go to file")}
|
||||
description={translate("auto.components.QuickOpen.9e97f08d0f", "Search for a file to open")}
|
||||
title={translate('auto.components.QuickOpen.ec31e058f7', 'Go to file')}
|
||||
description={translate('auto.components.QuickOpen.9e97f08d0f', 'Search for a file to open')}
|
||||
>
|
||||
<CommandInput placeholder={translate("auto.components.QuickOpen.1cb6ef47b7", "Go to file...")} value={query} onValueChange={setQuery} />
|
||||
<CommandInput
|
||||
placeholder={translate('auto.components.QuickOpen.1cb6ef47b7', 'Go to file...')}
|
||||
value={query}
|
||||
onValueChange={setQuery}
|
||||
/>
|
||||
<CommandList className="p-2">
|
||||
{loading ? (
|
||||
<div className="py-6 text-center text-sm text-muted-foreground">{translate("auto.components.QuickOpen.722a21e1a8", "Loading files...")}</div>
|
||||
<div className="py-6 text-center text-sm text-muted-foreground">
|
||||
{translate('auto.components.QuickOpen.722a21e1a8', 'Loading files...')}
|
||||
</div>
|
||||
) : loadError ? (
|
||||
(() => {
|
||||
const guidance = parseInstallRgGuidance(loadError)
|
||||
@@ -241,7 +259,9 @@ export default function QuickOpen(): React.JSX.Element | null {
|
||||
)
|
||||
})()
|
||||
) : filtered.length === 0 ? (
|
||||
<CommandEmpty>{translate("auto.components.QuickOpen.74e2e1b3e4", "No matching files.")}</CommandEmpty>
|
||||
<CommandEmpty>
|
||||
{translate('auto.components.QuickOpen.74e2e1b3e4', 'No matching files.')}
|
||||
</CommandEmpty>
|
||||
) : (
|
||||
filtered.map((item) => {
|
||||
const lastSlash = item.path.lastIndexOf('/')
|
||||
@@ -266,17 +286,21 @@ export default function QuickOpen(): React.JSX.Element | null {
|
||||
</CommandList>
|
||||
<div className="flex items-center justify-end border-t border-border/60 px-3.5 py-2.5 text-[11px] text-muted-foreground/82">
|
||||
<div className="flex items-center gap-2">
|
||||
<FooterKey>{translate("auto.components.QuickOpen.250e5b2dfb", "Enter")}</FooterKey>
|
||||
<span>{translate("auto.components.QuickOpen.61b1c871a6", "Open")}</span>
|
||||
<FooterKey>{translate("auto.components.QuickOpen.95fccbae88", "Esc")}</FooterKey>
|
||||
<span>{translate("auto.components.QuickOpen.73b2c581f1", "Close")}</span>
|
||||
<FooterKey>{translate('auto.components.QuickOpen.250e5b2dfb', 'Enter')}</FooterKey>
|
||||
<span>{translate('auto.components.QuickOpen.61b1c871a6', 'Open')}</span>
|
||||
<FooterKey>{translate('auto.components.QuickOpen.95fccbae88', 'Esc')}</FooterKey>
|
||||
<span>{translate('auto.components.QuickOpen.73b2c581f1', 'Close')}</span>
|
||||
<FooterKey>↑↓</FooterKey>
|
||||
<span>{translate("auto.components.QuickOpen.1dbd3f59ff", "Move")}</span>
|
||||
<span>{translate('auto.components.QuickOpen.1dbd3f59ff', 'Move')}</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* Accessibility: announce result count changes */}
|
||||
<div aria-live="polite" className="sr-only">
|
||||
{deferredQuery.trim() ? translate("auto.components.QuickOpen.b227d88520", "{{value0}} files found", { value0: filtered.length }) : ''}
|
||||
{deferredQuery.trim()
|
||||
? translate('auto.components.QuickOpen.b227d88520', '{{value0}} files found', {
|
||||
value0: filtered.length
|
||||
})
|
||||
: ''}
|
||||
</div>
|
||||
</CommandDialog>
|
||||
)
|
||||
|
||||
@@ -115,7 +115,8 @@ export function SelectedTextCopyMenu({
|
||||
onClick={handleCopy}
|
||||
>
|
||||
<Copy className="size-3.5 text-muted-foreground" />
|
||||
{translate("auto.components.SelectedTextCopyMenu.9b40d7b018", "Copy")}</button>
|
||||
{translate('auto.components.SelectedTextCopyMenu.9b40d7b018', 'Copy')}
|
||||
</button>
|
||||
</div>,
|
||||
document.body
|
||||
)}
|
||||
|
||||
@@ -101,25 +101,39 @@ export function StarNagCard(): React.JSX.Element | null {
|
||||
<div className="flex items-center gap-2">
|
||||
<Star className="size-4 fill-amber-400/60 text-amber-400/80" />
|
||||
<h3 id="star-nag-heading" className="text-sm font-semibold">
|
||||
{translate("auto.components.StarNagCard.5f6df21046", "Enjoying Orca?")}</h3>
|
||||
{translate('auto.components.StarNagCard.5f6df21046', 'Enjoying Orca?')}
|
||||
</h3>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-7 shrink-0"
|
||||
onClick={handleClose}
|
||||
aria-label={translate("auto.components.StarNagCard.b5e685e4d9", "Dismiss")}
|
||||
aria-label={translate('auto.components.StarNagCard.b5e685e4d9', 'Dismiss')}
|
||||
>
|
||||
<X className="size-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{translate("auto.components.StarNagCard.30c36231c1", "If Orca has saved you time, a GitHub star goes a long way. It helps other developers discover the project and keeps the team motivated to ship improvements.")}</p>
|
||||
{translate(
|
||||
'auto.components.StarNagCard.30c36231c1',
|
||||
'If Orca has saved you time, a GitHub star goes a long way. It helps other developers discover the project and keeps the team motivated to ship improvements.'
|
||||
)}
|
||||
</p>
|
||||
|
||||
{error ? (
|
||||
<p className="text-xs text-destructive">
|
||||
{translate("auto.components.StarNagCard.cf82170065", "Could not star the repo. Make sure")}<code>{translate("auto.components.StarNagCard.cd8c34aac1", "gh")}</code> {translate("auto.components.StarNagCard.92b0f9d921", "is authenticated and try again.")}</p>
|
||||
{translate(
|
||||
'auto.components.StarNagCard.cf82170065',
|
||||
'Could not star the repo. Make sure'
|
||||
)}
|
||||
<code>{translate('auto.components.StarNagCard.cd8c34aac1', 'gh')}</code>{' '}
|
||||
{translate(
|
||||
'auto.components.StarNagCard.92b0f9d921',
|
||||
'is authenticated and try again.'
|
||||
)}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
<Button
|
||||
@@ -130,7 +144,9 @@ export function StarNagCard(): React.JSX.Element | null {
|
||||
className="mt-0.5 w-full gap-1.5"
|
||||
>
|
||||
<Star className="size-3.5" />
|
||||
{busy ? translate("auto.components.StarNagCard.af3c9bbb37", "Starring…") : translate("auto.components.StarNagCard.2d67b6c849", "Star on GitHub")}
|
||||
{busy
|
||||
? translate('auto.components.StarNagCard.af3c9bbb37', 'Starring…')
|
||||
: translate('auto.components.StarNagCard.2d67b6c849', 'Star on GitHub')}
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -488,7 +488,9 @@ function LinearStateCell({
|
||||
result.error ??
|
||||
translate('auto.components.TaskPage.6775c05483', 'Failed to update Linear state')
|
||||
)
|
||||
return
|
||||
}
|
||||
useAppStore.getState().recordFeatureInteraction('linear-tasks')
|
||||
})
|
||||
.catch(() => {
|
||||
if (reqId !== reqRef.current) {
|
||||
@@ -904,7 +906,9 @@ function GHStatusCell({
|
||||
typed.error ??
|
||||
translate('auto.components.TaskPage.1c893195ac', 'Failed to update state')
|
||||
)
|
||||
return
|
||||
}
|
||||
useAppStore.getState().recordFeatureInteraction('github-tasks')
|
||||
})
|
||||
.catch(() => {
|
||||
if (reqId !== reqRef.current) {
|
||||
@@ -1349,6 +1353,7 @@ function GHAssigneesCell({
|
||||
} else {
|
||||
throw new Error('No GitHub repository context available for this issue.')
|
||||
}
|
||||
useAppStore.getState().recordFeatureInteraction('github-tasks')
|
||||
} catch (err) {
|
||||
patchWorkItem(item.id, { assignees: previousAssignees }, item.repoId)
|
||||
toast.error(
|
||||
@@ -1814,6 +1819,7 @@ function PRReviewCell({
|
||||
setLocalReviewRequests(nextReviewRequests)
|
||||
patchWorkItem(item.id, { reviewRequests: nextReviewRequests }, item.repoId)
|
||||
setReviewerInput('')
|
||||
useAppStore.getState().recordFeatureInteraction('github-tasks')
|
||||
} else {
|
||||
toast.error(result.error)
|
||||
}
|
||||
@@ -2200,6 +2206,7 @@ function PRMergeCell({
|
||||
prRepo: item.prRepo ?? null
|
||||
})
|
||||
if (result.ok) {
|
||||
useAppStore.getState().recordFeatureInteraction('github-tasks')
|
||||
toast.success(translate('auto.components.TaskPage.a161925adc', 'Pull request merged'))
|
||||
onRefresh()
|
||||
} else {
|
||||
@@ -2227,6 +2234,7 @@ function PRMergeCell({
|
||||
prRepo: item.prRepo ?? null
|
||||
})
|
||||
if (result.ok) {
|
||||
useAppStore.getState().recordFeatureInteraction('github-tasks')
|
||||
toast.success(
|
||||
enabled
|
||||
? translate('auto.components.TaskPage.fed317634c', 'Auto-merge enabled')
|
||||
@@ -2850,12 +2858,15 @@ export default function TaskPage(): React.JSX.Element {
|
||||
|
||||
const openGitHubDetailPage = useCallback(
|
||||
(item: GitHubWorkItem, initialTab: ItemDialogTab = 'conversation') => {
|
||||
openTaskPage({
|
||||
taskSource: 'github',
|
||||
preselectedRepoId: item.repoId,
|
||||
openGitHubWorkItem: item,
|
||||
openGitHubInitialTab: initialTab
|
||||
})
|
||||
openTaskPage(
|
||||
{
|
||||
taskSource: 'github',
|
||||
preselectedRepoId: item.repoId,
|
||||
openGitHubWorkItem: item,
|
||||
openGitHubInitialTab: initialTab
|
||||
},
|
||||
{ recordTasksInteraction: false }
|
||||
)
|
||||
},
|
||||
[openTaskPage]
|
||||
)
|
||||
@@ -3078,7 +3089,10 @@ export default function TaskPage(): React.JSX.Element {
|
||||
|
||||
const openLinearDetailPage = useCallback(
|
||||
(issue: LinearIssue) => {
|
||||
openTaskPage({ taskSource: 'linear', openLinearIssue: issue })
|
||||
openTaskPage(
|
||||
{ taskSource: 'linear', openLinearIssue: issue },
|
||||
{ recordTasksInteraction: false }
|
||||
)
|
||||
},
|
||||
[openTaskPage]
|
||||
)
|
||||
@@ -4192,7 +4206,9 @@ export default function TaskPage(): React.JSX.Element {
|
||||
result.error ??
|
||||
translate('auto.components.TaskPage.6775c05483', 'Failed to update Linear state')
|
||||
)
|
||||
return
|
||||
}
|
||||
useAppStore.getState().recordFeatureInteraction('linear-tasks')
|
||||
} catch {
|
||||
patchLinearIssue(issue.id, { state: previousState })
|
||||
patchScopedLinearIssue(issue.id, { state: previousState })
|
||||
@@ -5229,6 +5245,7 @@ export default function TaskPage(): React.JSX.Element {
|
||||
// the worktree appeared in the sidebar before the user had a chance
|
||||
// to review it. The composer already owns the prefill flow. Telemetry
|
||||
// attribution flows via `openComposerForItem` (sets telemetrySource).
|
||||
useAppStore.getState().recordFeatureInteraction('github-tasks')
|
||||
openComposerForItem(item)
|
||||
},
|
||||
[openComposerForItem]
|
||||
@@ -5260,7 +5277,9 @@ export default function TaskPage(): React.JSX.Element {
|
||||
'Unable to open the workspace attached to this issue.'
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
useAppStore.getState().recordFeatureInteraction('github-tasks')
|
||||
},
|
||||
[handleUseWorkItem]
|
||||
)
|
||||
@@ -5285,6 +5304,7 @@ export default function TaskPage(): React.JSX.Element {
|
||||
|
||||
const handleUseGitLabItem = useCallback(
|
||||
(item: GitLabWorkItem): void => {
|
||||
useAppStore.getState().recordFeatureInteraction('gitlab-tasks')
|
||||
openComposerForGitLabItem(item)
|
||||
},
|
||||
[openComposerForGitLabItem]
|
||||
@@ -5557,13 +5577,14 @@ export default function TaskPage(): React.JSX.Element {
|
||||
setNewLinearIssueProjectId(null)
|
||||
setNewLinearIssueLabelIds([])
|
||||
setLinearRefreshNonce((n) => n + 1)
|
||||
useAppStore.getState().recordFeatureInteraction('linear-tasks')
|
||||
|
||||
// Why: auto-select the new issue in the inline workspace so the user
|
||||
// sees exactly what was filed, mirroring the GitHub create-issue flow.
|
||||
void linearGetIssue(settings, result.id, newLinearIssueTargetTeam.workspaceId)
|
||||
.then((full) => {
|
||||
if (full) {
|
||||
openLinearDetailPage(full)
|
||||
setSelectedLinearIssue(full, { allowOutsideList: true })
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
@@ -5580,8 +5601,8 @@ export default function TaskPage(): React.JSX.Element {
|
||||
newLinearIssueAssigneeId,
|
||||
newLinearIssueProjectId,
|
||||
newLinearIssueLabelIds,
|
||||
openLinearDetailPage,
|
||||
selectedLinearProject,
|
||||
setSelectedLinearIssue,
|
||||
settings
|
||||
])
|
||||
|
||||
@@ -6314,6 +6335,7 @@ export default function TaskPage(): React.JSX.Element {
|
||||
// dialog pre-filled rather than yolo-creating the worktree, so the
|
||||
// user can confirm name / agent / setup before the worktree lands in
|
||||
// the sidebar. Telemetry attribution flows via openComposerForLinearItem.
|
||||
useAppStore.getState().recordFeatureInteraction('linear-tasks')
|
||||
openComposerForLinearItem(issue, renderedText)
|
||||
},
|
||||
[openComposerForLinearItem]
|
||||
@@ -6503,7 +6525,10 @@ export default function TaskPage(): React.JSX.Element {
|
||||
disabled={source.disabled}
|
||||
onClick={() => {
|
||||
taskSourceManuallyChangedRef.current = true
|
||||
openTaskPage({ taskSource: source.id })
|
||||
openTaskPage(
|
||||
{ taskSource: source.id },
|
||||
{ recordTasksInteraction: false }
|
||||
)
|
||||
void updateSettings({ defaultTaskSource: source.id }).catch(() => {
|
||||
toast.error(
|
||||
translate(
|
||||
@@ -8205,10 +8230,14 @@ export default function TaskPage(): React.JSX.Element {
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
key={item.id}
|
||||
onClick={() => setGitlabDialogItem(item)}
|
||||
onClick={() => {
|
||||
useAppStore.getState().recordFeatureInteraction('gitlab-tasks')
|
||||
setGitlabDialogItem(item)
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault()
|
||||
useAppStore.getState().recordFeatureInteraction('gitlab-tasks')
|
||||
setGitlabDialogItem(item)
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -546,7 +546,12 @@ function Terminal(): React.JSX.Element | null {
|
||||
releaseCloseDialogGuardAfterDebounce()
|
||||
return
|
||||
}
|
||||
toast.error(translate("auto.components.Terminal.a2a279b32a", "Save timed out or failed. Fix errors before closing."))
|
||||
toast.error(
|
||||
translate(
|
||||
'auto.components.Terminal.a2a279b32a',
|
||||
'Save timed out or failed. Fix errors before closing.'
|
||||
)
|
||||
)
|
||||
setSaveDialogFileId(fileId)
|
||||
// Why: a genuine timeout leaves the user back on the same dialog, so
|
||||
// release the guard immediately — a new click here is a deliberate
|
||||
@@ -861,7 +866,7 @@ function Terminal(): React.JSX.Element | null {
|
||||
return
|
||||
}
|
||||
createBrowserTab(activeWorktreeId, defaultUrl, {
|
||||
title: translate("auto.components.Terminal.37da0d736f", "New Browser Tab"),
|
||||
title: translate('auto.components.Terminal.37da0d736f', 'New Browser Tab'),
|
||||
focusAddressBar: true
|
||||
})
|
||||
}, [
|
||||
@@ -1292,7 +1297,12 @@ function Terminal(): React.JSX.Element | null {
|
||||
if (floatingWorkspaceFocused) {
|
||||
void createFloatingWorkspaceMarkdownTab(useAppStore.getState()).catch((err) => {
|
||||
toast.error(
|
||||
err instanceof Error ? err.message : translate("auto.components.Terminal.f0600556b3", "Failed to create untitled markdown file.")
|
||||
err instanceof Error
|
||||
? err.message
|
||||
: translate(
|
||||
'auto.components.Terminal.f0600556b3',
|
||||
'Failed to create untitled markdown file.'
|
||||
)
|
||||
)
|
||||
})
|
||||
return
|
||||
@@ -1806,11 +1816,12 @@ function Terminal(): React.JSX.Element | null {
|
||||
})}
|
||||
</div>
|
||||
|
||||
{renderedActiveWorktreeId && activeTabType === "editor" && worktreeFiles.length > 0 && (
|
||||
{renderedActiveWorktreeId && activeTabType === 'editor' && worktreeFiles.length > 0 && (
|
||||
<Suspense
|
||||
fallback={
|
||||
<div className="flex-1 flex items-center justify-center text-muted-foreground text-sm">
|
||||
{translate("auto.components.Terminal.5c1d2a32bb", "Loading editor...")}</div>
|
||||
{translate('auto.components.Terminal.5c1d2a32bb', 'Loading editor...')}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<EditorPanel />
|
||||
@@ -1830,20 +1841,32 @@ function Terminal(): React.JSX.Element | null {
|
||||
>
|
||||
<DialogContent className="max-w-sm">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-sm">{translate("auto.components.Terminal.21295c6b8c", "Unsaved Changes")}</DialogTitle>
|
||||
<DialogTitle className="text-sm">
|
||||
{translate('auto.components.Terminal.21295c6b8c', 'Unsaved Changes')}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-xs">
|
||||
{saveDialogFile
|
||||
? translate("auto.components.Terminal.61ed600d29", "\"{{value0}}\" has unsaved changes. Do you want to save before closing?", { value0: basename(saveDialogFile.relativePath) })
|
||||
: translate("auto.components.Terminal.46e08bc5c8", "This file has unsaved changes.")}
|
||||
? translate(
|
||||
'auto.components.Terminal.61ed600d29',
|
||||
'"{{value0}}" has unsaved changes. Do you want to save before closing?',
|
||||
{ value0: basename(saveDialogFile.relativePath) }
|
||||
)
|
||||
: translate(
|
||||
'auto.components.Terminal.46e08bc5c8',
|
||||
'This file has unsaved changes.'
|
||||
)}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter className="gap-2">
|
||||
<Button type="button" variant="outline" size="sm" onClick={handleSaveDialogCancel}>
|
||||
{translate("auto.components.Terminal.f82e9f02df", "Cancel")}</Button>
|
||||
{translate('auto.components.Terminal.f82e9f02df', 'Cancel')}
|
||||
</Button>
|
||||
<Button type="button" variant="outline" size="sm" onClick={handleSaveDialogDiscard}>
|
||||
{translate("auto.components.Terminal.0037b21794", "Don't Save")}</Button>
|
||||
{translate('auto.components.Terminal.0037b21794', "Don't Save")}
|
||||
</Button>
|
||||
<Button type="button" size="sm" onClick={handleSaveDialogSave}>
|
||||
{translate("auto.components.Terminal.cd51e28d8b", "Save")}</Button>
|
||||
{translate('auto.components.Terminal.cd51e28d8b', 'Save')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
@@ -1859,9 +1882,15 @@ function Terminal(): React.JSX.Element | null {
|
||||
>
|
||||
<DialogContent className="max-w-sm" showCloseButton={false}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-sm">{translate("auto.components.Terminal.2fa9c69ff3", "Close Window?")}</DialogTitle>
|
||||
<DialogTitle className="text-sm">
|
||||
{translate('auto.components.Terminal.2fa9c69ff3', 'Close Window?')}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-xs">
|
||||
{translate("auto.components.Terminal.7958465754", "There are local terminals with running processes. Close the window anyway?")}</DialogDescription>
|
||||
{translate(
|
||||
'auto.components.Terminal.7958465754',
|
||||
'There are local terminals with running processes. Close the window anyway?'
|
||||
)}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter className="gap-2">
|
||||
<Button
|
||||
@@ -1870,7 +1899,8 @@ function Terminal(): React.JSX.Element | null {
|
||||
size="sm"
|
||||
onClick={() => setWindowCloseDialogOpen(false)}
|
||||
>
|
||||
{translate("auto.components.Terminal.f82e9f02df", "Cancel")}</Button>
|
||||
{translate('auto.components.Terminal.f82e9f02df', 'Cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="destructive"
|
||||
@@ -1881,7 +1911,8 @@ function Terminal(): React.JSX.Element | null {
|
||||
window.api.ui.confirmWindowClose()
|
||||
}}
|
||||
>
|
||||
{translate("auto.components.Terminal.73768427cf", "Close")}</Button>
|
||||
{translate('auto.components.Terminal.73768427cf', 'Close')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@@ -110,7 +110,7 @@ export default function TerminalSearch({
|
||||
type="text"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder={translate("auto.components.TerminalSearch.e07012f26e", "Search...")}
|
||||
placeholder={translate('auto.components.TerminalSearch.e07012f26e', 'Search...')}
|
||||
className="min-w-0 flex-1 border-none bg-transparent text-sm text-white outline-none placeholder:text-zinc-500"
|
||||
/>
|
||||
|
||||
@@ -122,7 +122,7 @@ export default function TerminalSearch({
|
||||
className={`flex size-6 shrink-0 items-center justify-center rounded ${
|
||||
caseSensitive ? 'bg-zinc-700/50 text-blue-400' : 'text-zinc-400 hover:text-zinc-200'
|
||||
}`}
|
||||
title={translate("auto.components.TerminalSearch.90c61387d9", "Case sensitive")}
|
||||
title={translate('auto.components.TerminalSearch.90c61387d9', 'Case sensitive')}
|
||||
>
|
||||
<CaseSensitive size={14} />
|
||||
</Button>
|
||||
@@ -135,7 +135,7 @@ export default function TerminalSearch({
|
||||
className={`flex size-6 shrink-0 items-center justify-center rounded ${
|
||||
regex ? 'bg-zinc-700/50 text-blue-400' : 'text-zinc-400 hover:text-zinc-200'
|
||||
}`}
|
||||
title={translate("auto.components.TerminalSearch.42e466b9f1", "Regex")}
|
||||
title={translate('auto.components.TerminalSearch.42e466b9f1', 'Regex')}
|
||||
>
|
||||
<Regex size={14} />
|
||||
</Button>
|
||||
@@ -148,7 +148,7 @@ export default function TerminalSearch({
|
||||
size="icon-xs"
|
||||
onClick={findPrevious}
|
||||
className="flex size-6 shrink-0 items-center justify-center rounded text-zinc-400 hover:text-zinc-200"
|
||||
title={translate("auto.components.TerminalSearch.0f3066256e", "Previous match")}
|
||||
title={translate('auto.components.TerminalSearch.0f3066256e', 'Previous match')}
|
||||
>
|
||||
<ChevronUp size={14} />
|
||||
</Button>
|
||||
@@ -159,7 +159,7 @@ export default function TerminalSearch({
|
||||
size="icon-xs"
|
||||
onClick={findNext}
|
||||
className="flex size-6 shrink-0 items-center justify-center rounded text-zinc-400 hover:text-zinc-200"
|
||||
title={translate("auto.components.TerminalSearch.7cb40c04eb", "Next match")}
|
||||
title={translate('auto.components.TerminalSearch.7cb40c04eb', 'Next match')}
|
||||
>
|
||||
<ChevronDown size={14} />
|
||||
</Button>
|
||||
@@ -172,7 +172,7 @@ export default function TerminalSearch({
|
||||
size="icon-xs"
|
||||
onClick={onClose}
|
||||
className="flex size-6 shrink-0 items-center justify-center rounded text-zinc-400 hover:text-zinc-200"
|
||||
title={translate("auto.components.TerminalSearch.db234b7519", "Close")}
|
||||
title={translate('auto.components.TerminalSearch.db234b7519', 'Close')}
|
||||
>
|
||||
<X size={14} />
|
||||
</Button>
|
||||
|
||||
@@ -255,6 +255,7 @@ export default function WorktreeJumpPalette(): React.JSX.Element | null {
|
||||
const openModal = useAppStore((s) => s.openModal)
|
||||
const openSettingsPage = useAppStore((s) => s.openSettingsPage)
|
||||
const openSettingsTarget = useAppStore((s) => s.openSettingsTarget)
|
||||
const recordFeatureInteraction = useAppStore((s) => s.recordFeatureInteraction)
|
||||
const worktreesByRepo = useAppStore((s) => s.worktreesByRepo)
|
||||
const allWorktrees = useAllWorktrees()
|
||||
const repos = useAppStore((s) => s.repos)
|
||||
@@ -799,6 +800,7 @@ export default function WorktreeJumpPalette(): React.JSX.Element | null {
|
||||
|
||||
useEffect(() => {
|
||||
if (visible && !wasVisibleRef.current) {
|
||||
recordFeatureInteraction('cmd-j')
|
||||
createLookupGuard.invalidate()
|
||||
activeGroupSnapshotRef.current = captureCmdJActiveGroupSnapshot(
|
||||
useAppStore.getState(),
|
||||
@@ -845,6 +847,7 @@ export default function WorktreeJumpPalette(): React.JSX.Element | null {
|
||||
activeWorktreeId,
|
||||
browserTabsByWorktree,
|
||||
createLookupGuard,
|
||||
recordFeatureInteraction,
|
||||
visible
|
||||
])
|
||||
|
||||
@@ -953,12 +956,13 @@ export default function WorktreeJumpPalette(): React.JSX.Element | null {
|
||||
return
|
||||
}
|
||||
activateAndRevealWorktree(worktreeId)
|
||||
recordFeatureInteraction('cmd-j-workspace-open')
|
||||
skipRestoreFocusRef.current = true
|
||||
closeModal()
|
||||
setSelectedItemId('')
|
||||
focusFallbackSurface()
|
||||
},
|
||||
[closeModal, focusFallbackSurface]
|
||||
[closeModal, focusFallbackSurface, recordFeatureInteraction]
|
||||
)
|
||||
|
||||
const handleSelectBrowserPage = useCallback(
|
||||
@@ -989,6 +993,7 @@ export default function WorktreeJumpPalette(): React.JSX.Element | null {
|
||||
const state = useAppStore.getState()
|
||||
state.setActiveBrowserTab(workspace.id)
|
||||
state.setActiveBrowserPage(workspace.id, pageId)
|
||||
recordFeatureInteraction('cmd-j-browser-page-open')
|
||||
skipRestoreFocusRef.current = true
|
||||
closeModal()
|
||||
setSelectedItemId('')
|
||||
@@ -997,7 +1002,7 @@ export default function WorktreeJumpPalette(): React.JSX.Element | null {
|
||||
target: isBlankBrowserUrl(page.url) ? 'address-bar' : 'webview'
|
||||
})
|
||||
},
|
||||
[closeModal, requestBrowserFocus]
|
||||
[closeModal, recordFeatureInteraction, requestBrowserFocus]
|
||||
)
|
||||
|
||||
const handleSelectSimulatorTab = useCallback(
|
||||
@@ -1046,8 +1051,9 @@ export default function WorktreeJumpPalette(): React.JSX.Element | null {
|
||||
setSelectedItemId('')
|
||||
openSettingsTarget(target)
|
||||
openSettingsPage()
|
||||
recordFeatureInteraction('cmd-j-settings-open')
|
||||
},
|
||||
[closeModal, openSettingsPage, openSettingsTarget]
|
||||
[closeModal, openSettingsPage, openSettingsTarget, recordFeatureInteraction]
|
||||
)
|
||||
|
||||
const handleSelectQuickAction = useCallback(
|
||||
@@ -1059,10 +1065,16 @@ export default function WorktreeJumpPalette(): React.JSX.Element | null {
|
||||
void action.run(ctx).then((result) => {
|
||||
if (result.status === 'unavailable') {
|
||||
toast.error(getUnavailableQuickActionMessage(action.title, result.reason))
|
||||
return
|
||||
}
|
||||
if (action.id === 'create-workspace') {
|
||||
recordFeatureInteraction('cmd-j-create-workspace')
|
||||
return
|
||||
}
|
||||
recordFeatureInteraction('cmd-j-quick-action')
|
||||
})
|
||||
},
|
||||
[buildQuickActionContext, closeModal]
|
||||
[buildQuickActionContext, closeModal, recordFeatureInteraction]
|
||||
)
|
||||
|
||||
const handleSelectItem = useCallback(
|
||||
@@ -1099,6 +1111,7 @@ export default function WorktreeJumpPalette(): React.JSX.Element | null {
|
||||
typeof data.initialRepoId === 'string' ? data.initialRepoId : undefined
|
||||
)
|
||||
closeModal()
|
||||
recordFeatureInteraction('cmd-j-create-workspace')
|
||||
// Why: defer opening so Radix fully unmounts the palette's dialog before
|
||||
// the composer modal mounts, avoiding focus churn between the two.
|
||||
queueMicrotask(() =>
|
||||
@@ -1122,6 +1135,7 @@ export default function WorktreeJumpPalette(): React.JSX.Element | null {
|
||||
if (activeMatch) {
|
||||
closeModal()
|
||||
activateAndRevealWorktree(activeMatch.id)
|
||||
recordFeatureInteraction('cmd-j-workspace-open')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1141,6 +1155,7 @@ export default function WorktreeJumpPalette(): React.JSX.Element | null {
|
||||
// composer once the lookup returns.
|
||||
const lookupToken = createLookupGuard.start()
|
||||
preserveCreateLookupOnCloseRef.current = true
|
||||
recordFeatureInteraction('cmd-j-create-workspace')
|
||||
closeModal()
|
||||
void window.api.gh
|
||||
.workItemByOwnerRepo({
|
||||
@@ -1199,6 +1214,7 @@ export default function WorktreeJumpPalette(): React.JSX.Element | null {
|
||||
if (activeMatch) {
|
||||
closeModal()
|
||||
activateAndRevealWorktree(activeMatch.id)
|
||||
recordFeatureInteraction('cmd-j-workspace-open')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1213,6 +1229,7 @@ export default function WorktreeJumpPalette(): React.JSX.Element | null {
|
||||
prefetchCreateWorkspaceBaseForComposer(repoForLookup.id)
|
||||
const lookupToken = createLookupGuard.start()
|
||||
preserveCreateLookupOnCloseRef.current = true
|
||||
recordFeatureInteraction('cmd-j-create-workspace')
|
||||
closeModal()
|
||||
void window.api.gh
|
||||
.workItem({ repoPath: repoForLookup.path, repoId: repoForLookup.id, number: ghNumber })
|
||||
@@ -1263,6 +1280,7 @@ export default function WorktreeJumpPalette(): React.JSX.Element | null {
|
||||
createWorktreeName,
|
||||
openModal,
|
||||
prefetchCreateWorkspaceBaseForComposer,
|
||||
recordFeatureInteraction,
|
||||
repoMap
|
||||
])
|
||||
|
||||
|
||||
@@ -911,7 +911,13 @@ export function getActivityThreadGroup(
|
||||
if (groupBy === 'project') {
|
||||
return thread.repo
|
||||
? { key: `project:${thread.repo.id}`, label: thread.repo.displayName }
|
||||
: { key: 'project:unknown', label: translate("auto.components.activity.ActivityPrototypePage.5651b216c6", "Unknown project") }
|
||||
: {
|
||||
key: 'project:unknown',
|
||||
label: translate(
|
||||
'auto.components.activity.ActivityPrototypePage.5651b216c6',
|
||||
'Unknown project'
|
||||
)
|
||||
}
|
||||
}
|
||||
if (groupBy === 'worktree') {
|
||||
return { key: `worktree:${thread.worktree.id}`, label: thread.worktree.displayName }
|
||||
@@ -1162,7 +1168,10 @@ function ThreadRow({
|
||||
{thread.unread ? (
|
||||
<FilledBellIcon
|
||||
className="size-[13px] shrink-0 text-amber-500 drop-shadow-sm"
|
||||
aria-label={translate("auto.components.activity.ActivityPrototypePage.beb2c19173", "Unread")}
|
||||
aria-label={translate(
|
||||
'auto.components.activity.ActivityPrototypePage.beb2c19173',
|
||||
'Unread'
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<Tooltip>
|
||||
@@ -1179,12 +1188,20 @@ function ThreadRow({
|
||||
'hover:bg-accent/80 active:scale-95',
|
||||
'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring'
|
||||
)}
|
||||
aria-label={translate("auto.components.activity.ActivityPrototypePage.59b131fbd9", "Mark thread unread")}
|
||||
aria-label={translate(
|
||||
'auto.components.activity.ActivityPrototypePage.59b131fbd9',
|
||||
'Mark thread unread'
|
||||
)}
|
||||
>
|
||||
<Bell className="size-3 text-muted-foreground/40 opacity-0 transition-opacity group-hover:opacity-100 group-hover/unread:opacity-100" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="left">{translate("auto.components.activity.ActivityPrototypePage.59b131fbd9", "Mark thread unread")}</TooltipContent>
|
||||
<TooltipContent side="left">
|
||||
{translate(
|
||||
'auto.components.activity.ActivityPrototypePage.59b131fbd9',
|
||||
'Mark thread unread'
|
||||
)}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</span>
|
||||
@@ -1216,7 +1233,10 @@ function ThreadRow({
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="icon-xs"
|
||||
aria-label={translate("auto.components.activity.ActivityPrototypePage.4616ea39fd", "Jump to workspace")}
|
||||
aria-label={translate(
|
||||
'auto.components.activity.ActivityPrototypePage.4616ea39fd',
|
||||
'Jump to workspace'
|
||||
)}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation()
|
||||
onJump()
|
||||
@@ -1226,7 +1246,12 @@ function ThreadRow({
|
||||
<ExternalLink className="size-3" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="left">{translate("auto.components.activity.ActivityPrototypePage.4616ea39fd", "Jump to workspace")}</TooltipContent>
|
||||
<TooltipContent side="left">
|
||||
{translate(
|
||||
'auto.components.activity.ActivityPrototypePage.4616ea39fd',
|
||||
'Jump to workspace'
|
||||
)}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</span>
|
||||
) : null}
|
||||
@@ -1615,7 +1640,10 @@ export default function ActivityPrototypePage(): React.JSX.Element {
|
||||
<Input
|
||||
value={query}
|
||||
onChange={(event) => setQuery(event.target.value)}
|
||||
placeholder={translate("auto.components.activity.ActivityPrototypePage.795cbf26e2", "Filter...")}
|
||||
placeholder={translate(
|
||||
'auto.components.activity.ActivityPrototypePage.795cbf26e2',
|
||||
'Filter...'
|
||||
)}
|
||||
className="h-8 w-full pl-7 text-xs"
|
||||
/>
|
||||
</div>
|
||||
@@ -1626,15 +1654,38 @@ export default function ActivityPrototypePage(): React.JSX.Element {
|
||||
<SelectTrigger
|
||||
size="sm"
|
||||
className="h-8 w-[128px] shrink-0 px-2 text-xs"
|
||||
aria-label={translate("auto.components.activity.ActivityPrototypePage.770d458144", "Group agent activity by")}
|
||||
aria-label={translate(
|
||||
'auto.components.activity.ActivityPrototypePage.770d458144',
|
||||
'Group agent activity by'
|
||||
)}
|
||||
>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent align="end">
|
||||
<SelectItem value="status">{translate("auto.components.activity.ActivityPrototypePage.4a3986b200", "Status")}</SelectItem>
|
||||
<SelectItem value="project">{translate("auto.components.activity.ActivityPrototypePage.8c3b621ddf", "Project")}</SelectItem>
|
||||
<SelectItem value="worktree">{translate("auto.components.activity.ActivityPrototypePage.b29191b3e0", "Worktree")}</SelectItem>
|
||||
<SelectItem value="agent">{translate("auto.components.activity.ActivityPrototypePage.f6396e1f85", "Agent")}</SelectItem>
|
||||
<SelectItem value="status">
|
||||
{translate(
|
||||
'auto.components.activity.ActivityPrototypePage.4a3986b200',
|
||||
'Status'
|
||||
)}
|
||||
</SelectItem>
|
||||
<SelectItem value="project">
|
||||
{translate(
|
||||
'auto.components.activity.ActivityPrototypePage.8c3b621ddf',
|
||||
'Project'
|
||||
)}
|
||||
</SelectItem>
|
||||
<SelectItem value="worktree">
|
||||
{translate(
|
||||
'auto.components.activity.ActivityPrototypePage.b29191b3e0',
|
||||
'Worktree'
|
||||
)}
|
||||
</SelectItem>
|
||||
<SelectItem value="agent">
|
||||
{translate(
|
||||
'auto.components.activity.ActivityPrototypePage.f6396e1f85',
|
||||
'Agent'
|
||||
)}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Tooltip>
|
||||
@@ -1650,12 +1701,20 @@ export default function ActivityPrototypePage(): React.JSX.Element {
|
||||
? '!border-primary !bg-primary !text-primary-foreground shadow-xs ring-2 ring-primary/35 hover:!bg-primary/90 hover:!text-primary-foreground'
|
||||
: 'text-muted-foreground hover:text-foreground'
|
||||
)}
|
||||
aria-label={translate("auto.components.activity.ActivityPrototypePage.d1a88df9a8", "Show unread threads only")}
|
||||
aria-label={translate(
|
||||
'auto.components.activity.ActivityPrototypePage.d1a88df9a8',
|
||||
'Show unread threads only'
|
||||
)}
|
||||
>
|
||||
<BellDot className="size-3.5" />
|
||||
</Toggle>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">{translate("auto.components.activity.ActivityPrototypePage.d1a88df9a8", "Show unread threads only")}</TooltipContent>
|
||||
<TooltipContent side="bottom">
|
||||
{translate(
|
||||
'auto.components.activity.ActivityPrototypePage.d1a88df9a8',
|
||||
'Show unread threads only'
|
||||
)}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
{/* Why (overflow menu): "Mark all read" is a low-frequency,
|
||||
destructive-feeling action — parking it behind a `…` keeps
|
||||
@@ -1671,13 +1730,21 @@ export default function ActivityPrototypePage(): React.JSX.Element {
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="size-8 shrink-0 border-input bg-transparent p-0 text-muted-foreground shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-transparent dark:hover:bg-accent dark:hover:text-accent-foreground"
|
||||
aria-label={translate("auto.components.activity.ActivityPrototypePage.db8a1878b5", "Thread list options")}
|
||||
aria-label={translate(
|
||||
'auto.components.activity.ActivityPrototypePage.db8a1878b5',
|
||||
'Thread list options'
|
||||
)}
|
||||
>
|
||||
<MoreVertical className="size-3.5" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">{translate("auto.components.activity.ActivityPrototypePage.a472a14700", "More options")}</TooltipContent>
|
||||
<TooltipContent side="bottom">
|
||||
{translate(
|
||||
'auto.components.activity.ActivityPrototypePage.a472a14700',
|
||||
'More options'
|
||||
)}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<DropdownMenuContent align="end" sideOffset={6}>
|
||||
<DropdownMenuCheckboxItem
|
||||
@@ -1685,20 +1752,35 @@ export default function ActivityPrototypePage(): React.JSX.Element {
|
||||
onCheckedChange={(checked) => setCompactMode(checked === true)}
|
||||
onSelect={(event) => event.preventDefault()}
|
||||
>
|
||||
{translate("auto.components.activity.ActivityPrototypePage.f70e4bec47", "Compact mode")}</DropdownMenuCheckboxItem>
|
||||
{translate(
|
||||
'auto.components.activity.ActivityPrototypePage.f70e4bec47',
|
||||
'Compact mode'
|
||||
)}
|
||||
</DropdownMenuCheckboxItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
onSelect={() => markAllThreadsRead()}
|
||||
disabled={!hasUnreadThreads}
|
||||
>
|
||||
{translate("auto.components.activity.ActivityPrototypePage.023ff75afe", "Mark all read")}</DropdownMenuItem>
|
||||
{translate(
|
||||
'auto.components.activity.ActivityPrototypePage.023ff75afe',
|
||||
'Mark all read'
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-h-0 flex-1 overflow-auto scrollbar-sleek">
|
||||
{visibleThreadGroups.map((group) => (
|
||||
<section key={group.key} aria-label={translate("auto.components.activity.ActivityPrototypePage.a2b4437bfb", "{{value0}} activity", { value0: group.label })}>
|
||||
<section
|
||||
key={group.key}
|
||||
aria-label={translate(
|
||||
'auto.components.activity.ActivityPrototypePage.a2b4437bfb',
|
||||
'{{value0}} activity',
|
||||
{ value0: group.label }
|
||||
)}
|
||||
>
|
||||
<ActivityStatusGroupHeader group={group} />
|
||||
{group.threads.map((thread) => (
|
||||
<ThreadRow
|
||||
@@ -1716,12 +1798,22 @@ export default function ActivityPrototypePage(): React.JSX.Element {
|
||||
))}
|
||||
{visibleThreads.length === 0 ? (
|
||||
<div className="px-3 py-8 text-sm text-muted-foreground">
|
||||
{translate("auto.components.activity.ActivityPrototypePage.7cd632006b", "No agent activity matches these filters.")}</div>
|
||||
{translate(
|
||||
'auto.components.activity.ActivityPrototypePage.7cd632006b',
|
||||
'No agent activity matches these filters.'
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div
|
||||
aria-label={translate("auto.components.activity.ActivityPrototypePage.443690186e", "Resize activity thread list")}
|
||||
title={translate("auto.components.activity.ActivityPrototypePage.866083500b", "Drag to resize")}
|
||||
aria-label={translate(
|
||||
'auto.components.activity.ActivityPrototypePage.443690186e',
|
||||
'Resize activity thread list'
|
||||
)}
|
||||
title={translate(
|
||||
'auto.components.activity.ActivityPrototypePage.866083500b',
|
||||
'Drag to resize'
|
||||
)}
|
||||
className={cn(
|
||||
'group absolute -right-1.5 top-0 z-20 flex h-full w-3 cursor-col-resize items-stretch justify-center',
|
||||
isThreadListResizing && 'bg-ring/10'
|
||||
@@ -1778,8 +1870,14 @@ export default function ActivityPrototypePage(): React.JSX.Element {
|
||||
<div className="flex min-h-0 flex-1 flex-col items-center justify-center gap-2 p-4 text-sm text-muted-foreground">
|
||||
<TerminalSquare className="size-7" />
|
||||
{storeData.worktreeMap.has(selectedThread.worktree.id)
|
||||
? translate("auto.components.activity.ActivityPrototypePage.afdc2139a8", "Agent terminal closed. Open a new terminal in this workspace to continue.")
|
||||
: translate("auto.components.activity.ActivityPrototypePage.22b22034bc", "Standalone terminal unavailable in Activity.")}
|
||||
? translate(
|
||||
'auto.components.activity.ActivityPrototypePage.afdc2139a8',
|
||||
'Agent terminal closed. Open a new terminal in this workspace to continue.'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.activity.ActivityPrototypePage.22b22034bc',
|
||||
'Standalone terminal unavailable in Activity.'
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1815,12 +1913,22 @@ export default function ActivityPrototypePage(): React.JSX.Element {
|
||||
{visiblePortalUnavailable ? (
|
||||
<div className="ml-3 mt-3 inline-flex items-center gap-2 rounded-md border border-border bg-background/85 px-2 py-1 text-xs text-muted-foreground shadow-xs">
|
||||
<span className="h-3 w-1.5 rounded-sm bg-muted-foreground/70" />
|
||||
<span>{translate("auto.components.activity.ActivityPrototypePage.8de7c5beaa", "Terminal unavailable")}</span>
|
||||
<span>
|
||||
{translate(
|
||||
'auto.components.activity.ActivityPrototypePage.8de7c5beaa',
|
||||
'Terminal unavailable'
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
) : showTerminalLoadingLabel ? (
|
||||
<div className="ml-3 mt-3 inline-flex items-center gap-2 rounded-md border border-border bg-background/85 px-2 py-1 text-xs text-muted-foreground shadow-xs">
|
||||
<span className="h-3 w-1.5 animate-pulse rounded-sm bg-muted-foreground/70" />
|
||||
<span>{translate("auto.components.activity.ActivityPrototypePage.1b633f5c1e", "Connecting terminal...")}</span>
|
||||
<span>
|
||||
{translate(
|
||||
'auto.components.activity.ActivityPrototypePage.1b633f5c1e',
|
||||
'Connecting terminal...'
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -1834,11 +1942,19 @@ export default function ActivityPrototypePage(): React.JSX.Element {
|
||||
{visibleThreads.length === 0 ? (
|
||||
<>
|
||||
<MessageSquareText className="size-7" />
|
||||
{translate("auto.components.activity.ActivityPrototypePage.e3db9892f6", "No activity yet.")}</>
|
||||
{translate(
|
||||
'auto.components.activity.ActivityPrototypePage.e3db9892f6',
|
||||
'No activity yet.'
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<TerminalSquare className="size-7" />
|
||||
{translate("auto.components.activity.ActivityPrototypePage.cf780197a1", "Select an agent to view its activity")}</>
|
||||
{translate(
|
||||
'auto.components.activity.ActivityPrototypePage.cf780197a1',
|
||||
'Select an agent to view its activity'
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -27,18 +27,29 @@ export function ActivityTitlebarControls(): React.JSX.Element {
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
onClick={closeActivityPage}
|
||||
aria-label={translate("auto.components.activity.ActivityTitlebarControls.dc708f3eff", "Close agents")}
|
||||
aria-label={translate(
|
||||
'auto.components.activity.ActivityTitlebarControls.dc708f3eff',
|
||||
'Close agents'
|
||||
)}
|
||||
>
|
||||
<ArrowLeft className="size-3.5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={6}>
|
||||
{translate("auto.components.activity.ActivityTitlebarControls.dc708f3eff", "Close agents")}</TooltipContent>
|
||||
{translate(
|
||||
'auto.components.activity.ActivityTitlebarControls.dc708f3eff',
|
||||
'Close agents'
|
||||
)}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Bell className="size-3.5 shrink-0 text-muted-foreground" />
|
||||
<span className="truncate text-xs font-medium">{translate("auto.components.activity.ActivityTitlebarControls.d6a8de3934", "agents")}</span>
|
||||
<span className="truncate text-xs font-medium">
|
||||
{translate('auto.components.activity.ActivityTitlebarControls.d6a8de3934', 'agents')}
|
||||
</span>
|
||||
<Badge variant="secondary" className="h-5 px-1.5 text-[11px] font-normal">
|
||||
{unreadCount} {translate("auto.components.activity.ActivityTitlebarControls.f915168c8e", "unread")}</Badge>
|
||||
{unreadCount}{' '}
|
||||
{translate('auto.components.activity.ActivityTitlebarControls.f915168c8e', 'unread')}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -102,7 +102,9 @@ function renderItem({
|
||||
<ContextMenuContent className="z-[70]">
|
||||
<ContextMenuItem onSelect={onSetDefault} disabled={isDefault}>
|
||||
<Star className="size-3.5" />
|
||||
{isDefault ? translate("auto.components.agent.AgentCombobox.1b0d6965fa", "Current default") : translate("auto.components.agent.AgentCombobox.9c6b59fe58", "Set as default")}
|
||||
{isDefault
|
||||
? translate('auto.components.agent.AgentCombobox.1b0d6965fa', 'Current default')
|
||||
: translate('auto.components.agent.AgentCombobox.9c6b59fe58', 'Set as default')}
|
||||
</ContextMenuItem>
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
@@ -286,7 +288,9 @@ export default function AgentCombobox({
|
||||
) : (
|
||||
<span className="inline-flex min-w-0 flex-1 items-center gap-1.5">
|
||||
<Terminal className="size-3.5" />
|
||||
<span className="truncate">{translate("auto.components.agent.AgentCombobox.986f946354", "Blank Terminal")}</span>
|
||||
<span className="truncate">
|
||||
{translate('auto.components.agent.AgentCombobox.986f946354', 'Blank Terminal')}
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
<ChevronsUpDown className="size-3.5 opacity-50" />
|
||||
@@ -307,12 +311,20 @@ export default function AgentCombobox({
|
||||
<Command shouldFilter={false} value={commandValue} onValueChange={setCommandValue}>
|
||||
<CommandInput
|
||||
ref={setInputNode}
|
||||
placeholder={translate("auto.components.agent.AgentCombobox.48c6a5a9b4", "Search agents...")}
|
||||
placeholder={translate(
|
||||
'auto.components.agent.AgentCombobox.48c6a5a9b4',
|
||||
'Search agents...'
|
||||
)}
|
||||
value={query}
|
||||
onValueChange={setQuery}
|
||||
/>
|
||||
<CommandList>
|
||||
<CommandEmpty>{translate("auto.components.agent.AgentCombobox.579c768bde", "No agents match your search.")}</CommandEmpty>
|
||||
<CommandEmpty>
|
||||
{translate(
|
||||
'auto.components.agent.AgentCombobox.579c768bde',
|
||||
'No agents match your search.'
|
||||
)}
|
||||
</CommandEmpty>
|
||||
{blankMatchesQuery
|
||||
? renderItem({
|
||||
key: BLANK_VALUE,
|
||||
@@ -322,7 +334,10 @@ export default function AgentCombobox({
|
||||
onSelect: () => handleSelect(null),
|
||||
onSetDefault: onSetDefault ? () => onSetDefault('blank') : undefined,
|
||||
icon: <Terminal className="size-3.5" />,
|
||||
label: translate("auto.components.agent.AgentCombobox.986f946354", "Blank Terminal")
|
||||
label: translate(
|
||||
'auto.components.agent.AgentCombobox.986f946354',
|
||||
'Blank Terminal'
|
||||
)
|
||||
})
|
||||
: null}
|
||||
{filteredAgents.map((agent) =>
|
||||
@@ -348,7 +363,8 @@ export default function AgentCombobox({
|
||||
onMouseEnter={() => setCommandValue('')}
|
||||
className="h-9 w-full justify-start rounded-none px-3 text-xs font-normal text-muted-foreground"
|
||||
>
|
||||
{translate("auto.components.agent.AgentCombobox.19522e25ee", "Manage agents")}<ArrowRight className="ml-auto size-3" />
|
||||
{translate('auto.components.agent.AgentCombobox.19522e25ee', 'Manage agents')}
|
||||
<ArrowRight className="ml-auto size-3" />
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
@@ -34,9 +34,15 @@ export default function AgentSettingsDialog({
|
||||
agents are detected. */}
|
||||
<DialogContent className="sm:max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-sm">{translate("auto.components.agent.AgentSettingsDialog.fc0268e4ed", "Agents")}</DialogTitle>
|
||||
<DialogTitle className="text-sm">
|
||||
{translate('auto.components.agent.AgentSettingsDialog.fc0268e4ed', 'Agents')}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-xs">
|
||||
{translate("auto.components.agent.AgentSettingsDialog.50cdb57c03", "Manage AI agents, set a default, and customize commands.")}</DialogDescription>
|
||||
{translate(
|
||||
'auto.components.agent.AgentSettingsDialog.50cdb57c03',
|
||||
'Manage AI agents, set a default, and customize commands.'
|
||||
)}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="scrollbar-sleek -mr-2 max-h-[70vh] overflow-y-auto pr-2">
|
||||
<AgentsPane settings={settings} updateSettings={updateSettings} />
|
||||
|
||||
@@ -17,10 +17,22 @@ export function getCronScheduleStatusLabel(
|
||||
): { kind: 'empty' | 'invalid' | 'valid'; label: string } {
|
||||
const trimmed = schedule.trim()
|
||||
if (!trimmed) {
|
||||
return { kind: 'empty', label: translate("auto.components.automations.AutomationCustomCronPanel.968e66d686", "Enter a five-field cron.") }
|
||||
return {
|
||||
kind: 'empty',
|
||||
label: translate(
|
||||
'auto.components.automations.AutomationCustomCronPanel.968e66d686',
|
||||
'Enter a five-field cron.'
|
||||
)
|
||||
}
|
||||
}
|
||||
if (!validateSchedule(trimmed)) {
|
||||
return { kind: 'invalid', label: translate("auto.components.automations.AutomationCustomCronPanel.e81a02d61b", "Enter a valid five-field cron before saving.") }
|
||||
return {
|
||||
kind: 'invalid',
|
||||
label: translate(
|
||||
'auto.components.automations.AutomationCustomCronPanel.e81a02d61b',
|
||||
'Enter a valid five-field cron before saving.'
|
||||
)
|
||||
}
|
||||
}
|
||||
const formatted = formatAutomationSchedule(trimmed)
|
||||
return { kind: 'valid', label: formatted === 'Custom schedule' ? 'Valid custom cron' : formatted }
|
||||
@@ -50,7 +62,12 @@ export function AutomationCustomCronPanel({
|
||||
|
||||
return (
|
||||
<div className="grid gap-3">
|
||||
<Field label={translate("auto.components.automations.AutomationCustomCronPanel.3e3b2c369f", "Cron expression")}>
|
||||
<Field
|
||||
label={translate(
|
||||
'auto.components.automations.AutomationCustomCronPanel.3e3b2c369f',
|
||||
'Cron expression'
|
||||
)}
|
||||
>
|
||||
<Input
|
||||
value={draft.customSchedule}
|
||||
placeholder="0 9 * * 1-5"
|
||||
@@ -88,7 +105,7 @@ export function AutomationCustomCronPanel({
|
||||
: 'border-border/70 bg-muted/30 text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
{customScheduleStatus.kind === "invalid" ? (
|
||||
{customScheduleStatus.kind === 'invalid' ? (
|
||||
<CircleAlert className="size-3.5 shrink-0" />
|
||||
) : (
|
||||
<CheckCircle2 className="size-3.5 shrink-0" />
|
||||
|
||||
@@ -69,17 +69,35 @@ export function AutomationEditorDialogHeader({
|
||||
<div className="min-w-0 flex-1 space-y-2">
|
||||
<DialogTitle className="text-sm font-medium">
|
||||
{isEditing
|
||||
? translate("auto.components.automations.AutomationEditorDialogHeader.17086b48ee", "Edit automation")
|
||||
? translate(
|
||||
'auto.components.automations.AutomationEditorDialogHeader.17086b48ee',
|
||||
'Edit automation'
|
||||
)
|
||||
: isEditingExternal
|
||||
? translate("auto.components.automations.AutomationEditorDialogHeader.03142e7721", "Edit Hermes automation")
|
||||
? translate(
|
||||
'auto.components.automations.AutomationEditorDialogHeader.03142e7721',
|
||||
'Edit Hermes automation'
|
||||
)
|
||||
: isHermesCreate
|
||||
? translate("auto.components.automations.AutomationEditorDialogHeader.0a75e5e2fa", "Create Hermes automation")
|
||||
: translate("auto.components.automations.AutomationEditorDialogHeader.4133d33862", "Create automation")}
|
||||
? translate(
|
||||
'auto.components.automations.AutomationEditorDialogHeader.0a75e5e2fa',
|
||||
'Create Hermes automation'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.automations.AutomationEditorDialogHeader.4133d33862',
|
||||
'Create automation'
|
||||
)}
|
||||
</DialogTitle>
|
||||
<Input
|
||||
value={draftName}
|
||||
placeholder={translate("auto.components.automations.AutomationEditorDialogHeader.1d9826933e", "Weekday repo audit")}
|
||||
aria-label={translate("auto.components.automations.AutomationEditorDialogHeader.58f56b73d9", "Automation name")}
|
||||
placeholder={translate(
|
||||
'auto.components.automations.AutomationEditorDialogHeader.1d9826933e',
|
||||
'Weekday repo audit'
|
||||
)}
|
||||
aria-label={translate(
|
||||
'auto.components.automations.AutomationEditorDialogHeader.58f56b73d9',
|
||||
'Automation name'
|
||||
)}
|
||||
className="h-10 max-w-md border-input bg-input/30 px-3 text-lg font-semibold text-foreground shadow-xs placeholder:text-muted-foreground dark:bg-input/30"
|
||||
onChange={(event) => onDraftNameChange(event.target.value)}
|
||||
/>
|
||||
@@ -97,9 +115,17 @@ export function AutomationEditorDialogHeader({
|
||||
className="grid grid-cols-2"
|
||||
>
|
||||
<ToggleGroupItem value="orca" className={modeToggleItemClassName}>
|
||||
{translate("auto.components.automations.AutomationEditorDialogHeader.6f309eef8d", "Orca")}</ToggleGroupItem>
|
||||
{translate(
|
||||
'auto.components.automations.AutomationEditorDialogHeader.6f309eef8d',
|
||||
'Orca'
|
||||
)}
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem value="hermes" className={modeToggleItemClassName}>
|
||||
{translate("auto.components.automations.AutomationEditorDialogHeader.7e35393632", "Hermes")}</ToggleGroupItem>
|
||||
{translate(
|
||||
'auto.components.automations.AutomationEditorDialogHeader.7e35393632',
|
||||
'Hermes'
|
||||
)}
|
||||
</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
<Popover open={templateOpen} onOpenChange={onTemplateOpenChange}>
|
||||
<PopoverTrigger asChild>
|
||||
@@ -110,7 +136,11 @@ export function AutomationEditorDialogHeader({
|
||||
className={pickerTriggerClassName}
|
||||
>
|
||||
<Sparkles className="size-4" />
|
||||
{translate("auto.components.automations.AutomationEditorDialogHeader.31f9253920", "Use template")}</Button>
|
||||
{translate(
|
||||
'auto.components.automations.AutomationEditorDialogHeader.31f9253920',
|
||||
'Use template'
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="end" className="w-96 p-3">
|
||||
<div className="grid gap-2">
|
||||
|
||||
@@ -28,18 +28,29 @@ export function AutomationMissedRunGraceField({
|
||||
<Field
|
||||
label={
|
||||
<span className="inline-flex items-center gap-1">
|
||||
{translate("auto.components.automations.AutomationMissedRunGraceField.fc089e5fde", "Grace")}<Tooltip>
|
||||
{translate(
|
||||
'auto.components.automations.AutomationMissedRunGraceField.fc089e5fde',
|
||||
'Grace'
|
||||
)}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
aria-label={translate("auto.components.automations.AutomationMissedRunGraceField.3df53d554a", "Missed-run grace help")}
|
||||
aria-label={translate(
|
||||
'auto.components.automations.AutomationMissedRunGraceField.3df53d554a',
|
||||
'Missed-run grace help'
|
||||
)}
|
||||
className="rounded-sm text-muted-foreground outline-none hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50"
|
||||
>
|
||||
<Info className="size-3.5" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={6} className="max-w-72">
|
||||
{translate("auto.components.automations.AutomationMissedRunGraceField.3d70c185c8", "If Orca or the execution host was unavailable at the scheduled time, Orca runs one missed occurrence when it becomes available within this window. Older missed runs are skipped.")}</TooltipContent>
|
||||
{translate(
|
||||
'auto.components.automations.AutomationMissedRunGraceField.3d70c185c8',
|
||||
'If Orca or the execution host was unavailable at the scheduled time, Orca runs one missed occurrence when it becomes available within this window. Older missed runs are skipped.'
|
||||
)}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</span>
|
||||
}
|
||||
@@ -55,13 +66,48 @@ export function AutomationMissedRunGraceField({
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent position="popper" side="bottom" align="start" sideOffset={4}>
|
||||
<SelectItem value="0">{translate("auto.components.automations.AutomationMissedRunGraceField.529dc6c0b7", "No grace")}</SelectItem>
|
||||
<SelectItem value="30">{translate("auto.components.automations.AutomationMissedRunGraceField.e5ad263ae5", "30 minutes")}</SelectItem>
|
||||
<SelectItem value="60">{translate("auto.components.automations.AutomationMissedRunGraceField.521f77cd58", "1 hour")}</SelectItem>
|
||||
<SelectItem value="180">{translate("auto.components.automations.AutomationMissedRunGraceField.2dc9ee84d0", "3 hours")}</SelectItem>
|
||||
<SelectItem value="720">{translate("auto.components.automations.AutomationMissedRunGraceField.ba50e2a230", "12 hours")}</SelectItem>
|
||||
<SelectItem value="1440">{translate("auto.components.automations.AutomationMissedRunGraceField.adbab51feb", "24 hours")}</SelectItem>
|
||||
<SelectItem value="2880">{translate("auto.components.automations.AutomationMissedRunGraceField.0f4459e91d", "48 hours")}</SelectItem>
|
||||
<SelectItem value="0">
|
||||
{translate(
|
||||
'auto.components.automations.AutomationMissedRunGraceField.529dc6c0b7',
|
||||
'No grace'
|
||||
)}
|
||||
</SelectItem>
|
||||
<SelectItem value="30">
|
||||
{translate(
|
||||
'auto.components.automations.AutomationMissedRunGraceField.e5ad263ae5',
|
||||
'30 minutes'
|
||||
)}
|
||||
</SelectItem>
|
||||
<SelectItem value="60">
|
||||
{translate(
|
||||
'auto.components.automations.AutomationMissedRunGraceField.521f77cd58',
|
||||
'1 hour'
|
||||
)}
|
||||
</SelectItem>
|
||||
<SelectItem value="180">
|
||||
{translate(
|
||||
'auto.components.automations.AutomationMissedRunGraceField.2dc9ee84d0',
|
||||
'3 hours'
|
||||
)}
|
||||
</SelectItem>
|
||||
<SelectItem value="720">
|
||||
{translate(
|
||||
'auto.components.automations.AutomationMissedRunGraceField.ba50e2a230',
|
||||
'12 hours'
|
||||
)}
|
||||
</SelectItem>
|
||||
<SelectItem value="1440">
|
||||
{translate(
|
||||
'auto.components.automations.AutomationMissedRunGraceField.adbab51feb',
|
||||
'24 hours'
|
||||
)}
|
||||
</SelectItem>
|
||||
<SelectItem value="2880">
|
||||
{translate(
|
||||
'auto.components.automations.AutomationMissedRunGraceField.0f4459e91d',
|
||||
'48 hours'
|
||||
)}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</Field>
|
||||
|
||||
@@ -24,11 +24,19 @@ export function AutomationPrecheckFields({
|
||||
}: AutomationPrecheckFieldsProps): React.JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<Field label={translate("auto.components.automations.AutomationPrecheckFields.c2a762a180", "Precheck")}>
|
||||
<Field
|
||||
label={translate(
|
||||
'auto.components.automations.AutomationPrecheckFields.c2a762a180',
|
||||
'Precheck'
|
||||
)}
|
||||
>
|
||||
<textarea
|
||||
value={draft.precheckCommand}
|
||||
disabled={disabled}
|
||||
placeholder={translate("auto.components.automations.AutomationPrecheckFields.99a577306c", "gh pr list --json number -q '.[0].number'")}
|
||||
placeholder={translate(
|
||||
'auto.components.automations.AutomationPrecheckFields.99a577306c',
|
||||
"gh pr list --json number -q '.[0].number'"
|
||||
)}
|
||||
onChange={(event) =>
|
||||
onDraftChange((current) => ({
|
||||
...current,
|
||||
@@ -38,7 +46,12 @@ export function AutomationPrecheckFields({
|
||||
className="min-h-[68px] w-full resize-none rounded-md border border-input bg-transparent px-3 py-2 font-mono text-sm shadow-xs outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-input/30"
|
||||
/>
|
||||
</Field>
|
||||
<Field label={translate("auto.components.automations.AutomationPrecheckFields.bb2dfb3629", "Timeout")}>
|
||||
<Field
|
||||
label={translate(
|
||||
'auto.components.automations.AutomationPrecheckFields.bb2dfb3629',
|
||||
'Timeout'
|
||||
)}
|
||||
>
|
||||
<Select
|
||||
value={draft.precheckTimeoutSeconds}
|
||||
disabled={disabled}
|
||||
@@ -50,11 +63,36 @@ export function AutomationPrecheckFields({
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent position="popper" side="bottom" align="start" sideOffset={4}>
|
||||
<SelectItem value="30">{translate("auto.components.automations.AutomationPrecheckFields.51e28cdad9", "30 sec")}</SelectItem>
|
||||
<SelectItem value="60">{translate("auto.components.automations.AutomationPrecheckFields.c820119736", "1 min")}</SelectItem>
|
||||
<SelectItem value="120">{translate("auto.components.automations.AutomationPrecheckFields.d84d3765fd", "2 min")}</SelectItem>
|
||||
<SelectItem value="300">{translate("auto.components.automations.AutomationPrecheckFields.bf49585b3c", "5 min")}</SelectItem>
|
||||
<SelectItem value="600">{translate("auto.components.automations.AutomationPrecheckFields.d2a2ac89ac", "10 min")}</SelectItem>
|
||||
<SelectItem value="30">
|
||||
{translate(
|
||||
'auto.components.automations.AutomationPrecheckFields.51e28cdad9',
|
||||
'30 sec'
|
||||
)}
|
||||
</SelectItem>
|
||||
<SelectItem value="60">
|
||||
{translate(
|
||||
'auto.components.automations.AutomationPrecheckFields.c820119736',
|
||||
'1 min'
|
||||
)}
|
||||
</SelectItem>
|
||||
<SelectItem value="120">
|
||||
{translate(
|
||||
'auto.components.automations.AutomationPrecheckFields.d84d3765fd',
|
||||
'2 min'
|
||||
)}
|
||||
</SelectItem>
|
||||
<SelectItem value="300">
|
||||
{translate(
|
||||
'auto.components.automations.AutomationPrecheckFields.bf49585b3c',
|
||||
'5 min'
|
||||
)}
|
||||
</SelectItem>
|
||||
<SelectItem value="600">
|
||||
{translate(
|
||||
'auto.components.automations.AutomationPrecheckFields.d2a2ac89ac',
|
||||
'10 min'
|
||||
)}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</Field>
|
||||
|
||||
@@ -48,16 +48,28 @@ export function AutomationRunHistory({
|
||||
return (
|
||||
<div className="rounded-md border border-border/50 bg-muted/20 shadow-sm">
|
||||
<div className="flex items-center justify-between border-b border-border/50 px-3 py-2">
|
||||
<div className="text-sm font-medium">{translate("auto.components.automations.AutomationRunHistory.53fc5f07ab", "Run history")}</div>
|
||||
<div className="text-sm font-medium">
|
||||
{translate('auto.components.automations.AutomationRunHistory.53fc5f07ab', 'Run history')}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">{runCountLabel}</div>
|
||||
</div>
|
||||
<div className="min-h-[18rem] min-w-0">
|
||||
<div className="grid grid-cols-[minmax(9rem,1fr)_minmax(10rem,1.1fr)_minmax(5rem,.55fr)_minmax(5rem,.55fr)_minmax(6rem,auto)] gap-3 border-b border-border/50 px-3 py-1.5 text-[11px] font-medium uppercase text-muted-foreground">
|
||||
<div>{translate("auto.components.automations.AutomationRunHistory.8faaa00726", "Run")}</div>
|
||||
<div>{translate("auto.components.automations.AutomationRunHistory.149c0b49c7", "Workspace")}</div>
|
||||
<div>{translate("auto.components.automations.AutomationRunHistory.86a248187e", "Spend")}</div>
|
||||
<div>{translate("auto.components.automations.AutomationRunHistory.13988187b3", "Tokens")}</div>
|
||||
<div>{translate("auto.components.automations.AutomationRunHistory.9974a2b429", "Status")}</div>
|
||||
<div>
|
||||
{translate('auto.components.automations.AutomationRunHistory.8faaa00726', 'Run')}
|
||||
</div>
|
||||
<div>
|
||||
{translate('auto.components.automations.AutomationRunHistory.149c0b49c7', 'Workspace')}
|
||||
</div>
|
||||
<div>
|
||||
{translate('auto.components.automations.AutomationRunHistory.86a248187e', 'Spend')}
|
||||
</div>
|
||||
<div>
|
||||
{translate('auto.components.automations.AutomationRunHistory.13988187b3', 'Tokens')}
|
||||
</div>
|
||||
<div>
|
||||
{translate('auto.components.automations.AutomationRunHistory.9974a2b429', 'Status')}
|
||||
</div>
|
||||
</div>
|
||||
<div className="divide-y divide-border/50">
|
||||
{runs.map((run) => {
|
||||
@@ -115,9 +127,12 @@ export function AutomationRunHistory({
|
||||
}
|
||||
title={usageLabel}
|
||||
>
|
||||
{run.usage?.status === "known"
|
||||
{run.usage?.status === 'known'
|
||||
? formatAutomationTokens(run.usage.totalTokens)
|
||||
: translate("auto.components.automations.AutomationRunHistory.a00e38d1a3", "n/a")}
|
||||
: translate(
|
||||
'auto.components.automations.AutomationRunHistory.a00e38d1a3',
|
||||
'n/a'
|
||||
)}
|
||||
</div>
|
||||
<div className="flex justify-start">
|
||||
<Badge variant={getAutomationRunStatusVariant(run.status)}>
|
||||
@@ -128,7 +143,12 @@ export function AutomationRunHistory({
|
||||
)
|
||||
})}
|
||||
{runs.length === 0 ? (
|
||||
<div className="px-3 py-6 text-center text-sm text-muted-foreground">{translate("auto.components.automations.AutomationRunHistory.402651bfb6", "No runs yet.")}</div>
|
||||
<div className="px-3 py-6 text-center text-sm text-muted-foreground">
|
||||
{translate(
|
||||
'auto.components.automations.AutomationRunHistory.402651bfb6',
|
||||
'No runs yet.'
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -34,7 +34,10 @@ export function AutomationRunPageFrame({
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
aria-label={translate("auto.components.automations.AutomationRunPageFrame.33741dd973", "Back to runs")}
|
||||
aria-label={translate(
|
||||
'auto.components.automations.AutomationRunPageFrame.33741dd973',
|
||||
'Back to runs'
|
||||
)}
|
||||
onClick={onBack}
|
||||
>
|
||||
<ArrowLeft className="size-3.5" />
|
||||
@@ -46,7 +49,10 @@ export function AutomationRunPageFrame({
|
||||
</div>
|
||||
{breadcrumbs.length > 0 ? (
|
||||
<ol
|
||||
aria-label={translate("auto.components.automations.AutomationRunPageFrame.40a511bed4", "Run context")}
|
||||
aria-label={translate(
|
||||
'auto.components.automations.AutomationRunPageFrame.40a511bed4',
|
||||
'Run context'
|
||||
)}
|
||||
className="mt-0.5 flex min-w-0 flex-wrap items-center gap-x-1.5 gap-y-0.5 text-xs text-muted-foreground"
|
||||
>
|
||||
{breadcrumbs.map((breadcrumb, index) => (
|
||||
|
||||
@@ -172,7 +172,12 @@ export function AutomationSchedulePicker({
|
||||
className="popover-scroll-content scrollbar-sleek max-h-[var(--radix-popover-content-available-height)] w-[min(var(--radix-popover-trigger-width),calc(100vw-2rem))] min-w-[min(22rem,calc(100vw-2rem))] max-w-[calc(100vw-2rem)] overflow-y-auto p-3"
|
||||
>
|
||||
<div className="grid gap-3">
|
||||
<Field label={translate("auto.components.automations.AutomationSchedulePicker.233b8c94b6", "Cadence")}>
|
||||
<Field
|
||||
label={translate(
|
||||
'auto.components.automations.AutomationSchedulePicker.233b8c94b6',
|
||||
'Cadence'
|
||||
)}
|
||||
>
|
||||
<Select
|
||||
value={draft.preset}
|
||||
onValueChange={(preset) =>
|
||||
@@ -194,7 +199,7 @@ export function AutomationSchedulePicker({
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</Field>
|
||||
{draft.preset === "custom" ? (
|
||||
{draft.preset === 'custom' ? (
|
||||
<AutomationCustomCronPanel
|
||||
draft={draft}
|
||||
customScheduleInvalid={customScheduleInvalid}
|
||||
@@ -203,8 +208,13 @@ export function AutomationSchedulePicker({
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{draft.preset === "weekly" ? (
|
||||
<Field label={translate("auto.components.automations.AutomationSchedulePicker.6b914c5fbb", "Day")}>
|
||||
{draft.preset === 'weekly' ? (
|
||||
<Field
|
||||
label={translate(
|
||||
'auto.components.automations.AutomationSchedulePicker.6b914c5fbb',
|
||||
'Day'
|
||||
)}
|
||||
>
|
||||
<Select
|
||||
value={draft.dayOfWeek}
|
||||
onValueChange={(dayOfWeek) =>
|
||||
@@ -224,8 +234,13 @@ export function AutomationSchedulePicker({
|
||||
</Select>
|
||||
</Field>
|
||||
) : null}
|
||||
{draft.preset === "hourly" ? (
|
||||
<Field label={translate("auto.components.automations.AutomationSchedulePicker.9e677335b0", "Minute")}>
|
||||
{draft.preset === 'hourly' ? (
|
||||
<Field
|
||||
label={translate(
|
||||
'auto.components.automations.AutomationSchedulePicker.9e677335b0',
|
||||
'Minute'
|
||||
)}
|
||||
>
|
||||
<Select
|
||||
value={String(clockParts.minute)}
|
||||
onValueChange={(minute) =>
|
||||
@@ -249,7 +264,12 @@ export function AutomationSchedulePicker({
|
||||
</Select>
|
||||
</Field>
|
||||
) : (
|
||||
<Field label={translate("auto.components.automations.AutomationSchedulePicker.d90981f766", "Time")}>
|
||||
<Field
|
||||
label={translate(
|
||||
'auto.components.automations.AutomationSchedulePicker.d90981f766',
|
||||
'Time'
|
||||
)}
|
||||
>
|
||||
<div className="grid grid-cols-[minmax(0,1fr)_minmax(0,1fr)_minmax(0,0.8fr)] gap-2">
|
||||
<Select
|
||||
value={String(clockParts.hour12)}
|
||||
@@ -262,7 +282,10 @@ export function AutomationSchedulePicker({
|
||||
}
|
||||
>
|
||||
<SelectTrigger
|
||||
aria-label={translate("auto.components.automations.AutomationSchedulePicker.6b802ecc99", "Hour")}
|
||||
aria-label={translate(
|
||||
'auto.components.automations.AutomationSchedulePicker.6b802ecc99',
|
||||
'Hour'
|
||||
)}
|
||||
className={cn('w-full min-w-0', FIELD_CONTROL_CLASS)}
|
||||
>
|
||||
<SelectValue />
|
||||
@@ -286,7 +309,10 @@ export function AutomationSchedulePicker({
|
||||
}
|
||||
>
|
||||
<SelectTrigger
|
||||
aria-label={translate("auto.components.automations.AutomationSchedulePicker.9e677335b0", "Minute")}
|
||||
aria-label={translate(
|
||||
'auto.components.automations.AutomationSchedulePicker.9e677335b0',
|
||||
'Minute'
|
||||
)}
|
||||
className={cn('w-full min-w-0', FIELD_CONTROL_CLASS)}
|
||||
>
|
||||
<SelectValue />
|
||||
@@ -310,7 +336,10 @@ export function AutomationSchedulePicker({
|
||||
}
|
||||
>
|
||||
<SelectTrigger
|
||||
aria-label={translate("auto.components.automations.AutomationSchedulePicker.22359b186a", "AM or PM")}
|
||||
aria-label={translate(
|
||||
'auto.components.automations.AutomationSchedulePicker.22359b186a',
|
||||
'AM or PM'
|
||||
)}
|
||||
className={cn('w-full min-w-0', FIELD_CONTROL_CLASS)}
|
||||
>
|
||||
<SelectValue />
|
||||
|
||||
@@ -21,18 +21,26 @@ export function AutomationSessionField({
|
||||
<Field
|
||||
label={
|
||||
<span className="inline-flex items-center gap-1">
|
||||
{translate("auto.components.automations.AutomationSessionField.5ad314118e", "Session")}<Tooltip>
|
||||
{translate('auto.components.automations.AutomationSessionField.5ad314118e', 'Session')}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
aria-label={translate("auto.components.automations.AutomationSessionField.4bdce31f37", "Session reuse help")}
|
||||
aria-label={translate(
|
||||
'auto.components.automations.AutomationSessionField.4bdce31f37',
|
||||
'Session reuse help'
|
||||
)}
|
||||
className="rounded-sm text-muted-foreground outline-none hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50"
|
||||
>
|
||||
<Info className="size-3.5" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={6} className="max-w-72">
|
||||
{translate("auto.components.automations.AutomationSessionField.b675112193", "Reuse sends future runs to the previous live automation session. If that session is gone, Orca starts a fresh one.")}</TooltipContent>
|
||||
{translate(
|
||||
'auto.components.automations.AutomationSessionField.b675112193',
|
||||
'Reuse sends future runs to the previous live automation session. If that session is gone, Orca starts a fresh one.'
|
||||
)}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</span>
|
||||
}
|
||||
@@ -55,9 +63,11 @@ export function AutomationSessionField({
|
||||
className="grid w-full grid-cols-2"
|
||||
>
|
||||
<ToggleGroupItem value="fresh" className={toggleItemClassName}>
|
||||
{translate("auto.components.automations.AutomationSessionField.c90888ee94", "Fresh")}</ToggleGroupItem>
|
||||
{translate('auto.components.automations.AutomationSessionField.c90888ee94', 'Fresh')}
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem value="reuse" className={toggleItemClassName}>
|
||||
{translate("auto.components.automations.AutomationSessionField.f3c76dce51", "Reuse")}</ToggleGroupItem>
|
||||
{translate('auto.components.automations.AutomationSessionField.f3c76dce51', 'Reuse')}
|
||||
</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
</Field>
|
||||
)
|
||||
|
||||
@@ -176,7 +176,12 @@ export function CreateFromPicker({
|
||||
className={cn('h-9 w-full justify-between px-3 text-sm font-normal', triggerClassName)}
|
||||
>
|
||||
<span className="flex min-w-0 items-center gap-1.5">
|
||||
<span className="shrink-0 text-muted-foreground">{translate("auto.components.automations.CreateFromPicker.dd3841b442", "Branch from")}</span>
|
||||
<span className="shrink-0 text-muted-foreground">
|
||||
{translate(
|
||||
'auto.components.automations.CreateFromPicker.dd3841b442',
|
||||
'Branch from'
|
||||
)}
|
||||
</span>
|
||||
<span className="truncate">{selectedLabel}</span>
|
||||
</span>
|
||||
<ChevronsUpDown className="size-4 opacity-50" />
|
||||
@@ -195,11 +200,22 @@ export function CreateFromPicker({
|
||||
ref={setInputNode}
|
||||
value={query}
|
||||
onValueChange={setQuery}
|
||||
placeholder={translate("auto.components.automations.CreateFromPicker.f061f49e3f", "Search repo branches...")}
|
||||
placeholder={translate(
|
||||
'auto.components.automations.CreateFromPicker.f061f49e3f',
|
||||
'Search repo branches...'
|
||||
)}
|
||||
/>
|
||||
<CommandList className="max-h-72">
|
||||
<CommandEmpty>
|
||||
{isSearching ? translate("auto.components.automations.CreateFromPicker.9ce96621f4", "Searching branches...") : translate("auto.components.automations.CreateFromPicker.79512f22a7", "No branches found.")}
|
||||
{isSearching
|
||||
? translate(
|
||||
'auto.components.automations.CreateFromPicker.9ce96621f4',
|
||||
'Searching branches...'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.automations.CreateFromPicker.79512f22a7',
|
||||
'No branches found.'
|
||||
)}
|
||||
</CommandEmpty>
|
||||
<CommandItem
|
||||
value={effectiveDefault ? `${effectiveDefault} default` : 'project default'}
|
||||
@@ -215,7 +231,16 @@ export function CreateFromPicker({
|
||||
)}
|
||||
/>
|
||||
<span className="truncate">
|
||||
{effectiveDefault ? translate("auto.components.automations.CreateFromPicker.e53d306056", "{{value0}} (default)", { value0: effectiveDefault }) : translate("auto.components.automations.CreateFromPicker.ef6d762538", "Project default")}
|
||||
{effectiveDefault
|
||||
? translate(
|
||||
'auto.components.automations.CreateFromPicker.e53d306056',
|
||||
'{{value0}} (default)',
|
||||
{ value0: effectiveDefault }
|
||||
)
|
||||
: translate(
|
||||
'auto.components.automations.CreateFromPicker.ef6d762538',
|
||||
'Project default'
|
||||
)}
|
||||
</span>
|
||||
</CommandItem>
|
||||
{branchOptions
|
||||
|
||||
@@ -111,10 +111,24 @@ export function ExternalAutomationManagers({
|
||||
<div className="rounded-md border border-border/50 bg-muted/20 shadow-sm">
|
||||
<div className="flex items-center justify-between border-b border-border/50 px-3 py-2">
|
||||
<div>
|
||||
<div className="text-sm font-medium">{translate("auto.components.automations.ExternalAutomationManagers.c6695e6fbd", "External automations")}</div>
|
||||
<div className="text-sm font-medium">
|
||||
{translate(
|
||||
'auto.components.automations.ExternalAutomationManagers.c6695e6fbd',
|
||||
'External automations'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Badge variant="outline">
|
||||
{automationCount} {automationCount === 1 ? translate("auto.components.automations.ExternalAutomationManagers.701515f010", "automation") : translate("auto.components.automations.ExternalAutomationManagers.e2532150ed", "automations")}
|
||||
{automationCount}{' '}
|
||||
{automationCount === 1
|
||||
? translate(
|
||||
'auto.components.automations.ExternalAutomationManagers.701515f010',
|
||||
'automation'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.automations.ExternalAutomationManagers.e2532150ed',
|
||||
'automations'
|
||||
)}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="divide-y divide-border/50">
|
||||
@@ -127,9 +141,18 @@ export function ExternalAutomationManagers({
|
||||
{getProviderLabel(manager)} / {getTargetKindLabel(manager)} ·{' '}
|
||||
{manager.status === 'available'
|
||||
? manager.canManage
|
||||
? translate("auto.components.automations.ExternalAutomationManagers.0a2d4359a8", "Manageable")
|
||||
: translate("auto.components.automations.ExternalAutomationManagers.dbdcec22bd", "Read-only")
|
||||
: translate("auto.components.automations.ExternalAutomationManagers.92405f1431", "Unavailable")}
|
||||
? translate(
|
||||
'auto.components.automations.ExternalAutomationManagers.0a2d4359a8',
|
||||
'Manageable'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.automations.ExternalAutomationManagers.dbdcec22bd',
|
||||
'Read-only'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.automations.ExternalAutomationManagers.92405f1431',
|
||||
'Unavailable'
|
||||
)}
|
||||
{manager.error ? ` - ${manager.error}` : null}
|
||||
</div>
|
||||
</div>
|
||||
@@ -149,19 +172,45 @@ export function ExternalAutomationManagers({
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<span className="truncate font-medium">{job.name}</span>
|
||||
<Badge variant={job.enabled ? 'secondary' : 'outline'}>
|
||||
{job.enabled ? translate("auto.components.automations.ExternalAutomationManagers.b3feba84c7", "Active") : translate("auto.components.automations.ExternalAutomationManagers.2b0adbce21", "Paused")}
|
||||
{job.enabled
|
||||
? translate(
|
||||
'auto.components.automations.ExternalAutomationManagers.b3feba84c7',
|
||||
'Active'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.automations.ExternalAutomationManagers.2b0adbce21',
|
||||
'Paused'
|
||||
)}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="mt-1 truncate text-xs font-medium text-foreground/80">
|
||||
{scheduleDisplay.label}
|
||||
</div>
|
||||
<div className="mt-1 truncate text-xs text-muted-foreground">
|
||||
{translate("auto.components.automations.ExternalAutomationManagers.20fd7a3a15", "next")} {formatExternalDate(job.nextRunAt, now)} · {getProviderLabel(manager)}{' '}
|
||||
/ {manager.targetLabel}
|
||||
{translate(
|
||||
'auto.components.automations.ExternalAutomationManagers.20fd7a3a15',
|
||||
'next'
|
||||
)}{' '}
|
||||
{formatExternalDate(job.nextRunAt, now)} · {getProviderLabel(manager)} /{' '}
|
||||
{manager.targetLabel}
|
||||
</div>
|
||||
{manager.provider === "hermes" ? (
|
||||
{manager.provider === 'hermes' ? (
|
||||
<div className="mt-1 truncate text-xs text-muted-foreground">
|
||||
{job.runCount} {job.runCount === 1 ? translate("auto.components.automations.ExternalAutomationManagers.8e9165af08", "run") : translate("auto.components.automations.ExternalAutomationManagers.e66091daf4", "runs")} {translate("auto.components.automations.ExternalAutomationManagers.844f1acb72", "found")}</div>
|
||||
{job.runCount}{' '}
|
||||
{job.runCount === 1
|
||||
? translate(
|
||||
'auto.components.automations.ExternalAutomationManagers.8e9165af08',
|
||||
'run'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.automations.ExternalAutomationManagers.e66091daf4',
|
||||
'runs'
|
||||
)}{' '}
|
||||
{translate(
|
||||
'auto.components.automations.ExternalAutomationManagers.844f1acb72',
|
||||
'found'
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
{job.promptPreview || job.lastError ? (
|
||||
<div className="mt-1 truncate text-xs text-muted-foreground">
|
||||
@@ -170,12 +219,19 @@ export function ExternalAutomationManagers({
|
||||
) : null}
|
||||
</div>
|
||||
<div className="hidden min-w-0 text-xs text-muted-foreground md:block">
|
||||
{translate("auto.components.automations.ExternalAutomationManagers.5820648765", "Last")}{formatExternalDate(job.lastRunAt, now)}
|
||||
{translate(
|
||||
'auto.components.automations.ExternalAutomationManagers.5820648765',
|
||||
'Last'
|
||||
)}
|
||||
{formatExternalDate(job.lastRunAt, now)}
|
||||
{job.lastStatus ? ` · ${job.lastStatus}` : null}
|
||||
</div>
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
<ExternalActionButton
|
||||
label={translate("auto.components.automations.ExternalAutomationManagers.cc77ba88ff", "Run external automation")}
|
||||
label={translate(
|
||||
'auto.components.automations.ExternalAutomationManagers.cc77ba88ff',
|
||||
'Run external automation'
|
||||
)}
|
||||
disabled={!manager.canManage || runningActionKey !== null}
|
||||
onClick={() => onAction(manager, job, 'run')}
|
||||
>
|
||||
@@ -185,9 +241,12 @@ export function ExternalAutomationManagers({
|
||||
<Play className="size-3.5" />
|
||||
)}
|
||||
</ExternalActionButton>
|
||||
{manager.provider === "hermes" ? (
|
||||
{manager.provider === 'hermes' ? (
|
||||
<ExternalActionButton
|
||||
label={translate("auto.components.automations.ExternalAutomationManagers.1df491fd00", "Edit external automation")}
|
||||
label={translate(
|
||||
'auto.components.automations.ExternalAutomationManagers.1df491fd00',
|
||||
'Edit external automation'
|
||||
)}
|
||||
disabled={!manager.canManage || runningActionKey !== null}
|
||||
onClick={() => onEdit?.(manager, job)}
|
||||
>
|
||||
@@ -196,7 +255,15 @@ export function ExternalAutomationManagers({
|
||||
) : null}
|
||||
<ExternalActionButton
|
||||
label={
|
||||
job.enabled ? translate("auto.components.automations.ExternalAutomationManagers.0def1693bb", "Pause external automation") : translate("auto.components.automations.ExternalAutomationManagers.1c3bfd38fe", "Resume external automation")
|
||||
job.enabled
|
||||
? translate(
|
||||
'auto.components.automations.ExternalAutomationManagers.0def1693bb',
|
||||
'Pause external automation'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.automations.ExternalAutomationManagers.1c3bfd38fe',
|
||||
'Resume external automation'
|
||||
)
|
||||
}
|
||||
disabled={!manager.canManage || runningActionKey !== null}
|
||||
onClick={() => onAction(manager, job, job.enabled ? 'pause' : 'resume')}
|
||||
@@ -211,7 +278,10 @@ export function ExternalAutomationManagers({
|
||||
)}
|
||||
</ExternalActionButton>
|
||||
<ExternalActionButton
|
||||
label={translate("auto.components.automations.ExternalAutomationManagers.a42bf2b27e", "Delete external automation")}
|
||||
label={translate(
|
||||
'auto.components.automations.ExternalAutomationManagers.a42bf2b27e',
|
||||
'Delete external automation'
|
||||
)}
|
||||
className="text-destructive hover:text-destructive"
|
||||
disabled={!manager.canManage || runningActionKey !== null}
|
||||
onClick={() => onAction(manager, job, 'delete')}
|
||||
@@ -223,7 +293,7 @@ export function ExternalAutomationManagers({
|
||||
)}
|
||||
</ExternalActionButton>
|
||||
</div>
|
||||
{manager.provider === "hermes" ? (
|
||||
{manager.provider === 'hermes' ? (
|
||||
<div className="col-span-3">
|
||||
<ExternalAutomationRunTable
|
||||
manager={manager}
|
||||
@@ -239,15 +309,35 @@ export function ExternalAutomationManagers({
|
||||
})}
|
||||
{manager.jobs.length === 0 ? (
|
||||
<div className="px-3 py-4 text-sm text-muted-foreground">
|
||||
{translate("auto.components.automations.ExternalAutomationManagers.3d58d5b67d", "No")}{' '}
|
||||
{manager.provider === 'hermes' ? translate("auto.components.automations.ExternalAutomationManagers.766abf833c", "Hermes") : translate("auto.components.automations.ExternalAutomationManagers.5524365227", "OpenClaw")} {translate("auto.components.automations.ExternalAutomationManagers.6da3bfba4b", "automations found.")}</div>
|
||||
{translate(
|
||||
'auto.components.automations.ExternalAutomationManagers.3d58d5b67d',
|
||||
'No'
|
||||
)}{' '}
|
||||
{manager.provider === 'hermes'
|
||||
? translate(
|
||||
'auto.components.automations.ExternalAutomationManagers.766abf833c',
|
||||
'Hermes'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.automations.ExternalAutomationManagers.5524365227',
|
||||
'OpenClaw'
|
||||
)}{' '}
|
||||
{translate(
|
||||
'auto.components.automations.ExternalAutomationManagers.6da3bfba4b',
|
||||
'automations found.'
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{managers.length === 0 ? (
|
||||
<div className="px-3 py-6 text-center text-sm text-muted-foreground">
|
||||
{translate("auto.components.automations.ExternalAutomationManagers.e02f970595", "No external automation managers found.")}</div>
|
||||
{translate(
|
||||
'auto.components.automations.ExternalAutomationManagers.e02f970595',
|
||||
'No external automation managers found.'
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -183,7 +183,9 @@ export function ExternalAutomationRunTable({
|
||||
<div className="mt-2 rounded-md border border-border/50 bg-background/50">
|
||||
<div className="flex items-center justify-between border-b border-border/50 px-3 py-2">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<div className="text-xs font-medium">{translate("auto.components.automations.ExternalAutomationRunTable.2d4388a908", "Runs")}</div>
|
||||
<div className="text-xs font-medium">
|
||||
{translate('auto.components.automations.ExternalAutomationRunTable.2d4388a908', 'Runs')}
|
||||
</div>
|
||||
{isLoading ? <Loader2 className="size-3.5 animate-spin text-muted-foreground" /> : null}
|
||||
{fetchError ? (
|
||||
<Tooltip>
|
||||
@@ -197,7 +199,13 @@ export function ExternalAutomationRunTable({
|
||||
) : null}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{totalCount} {totalCount === 1 ? translate("auto.components.automations.ExternalAutomationRunTable.872d032d05", "run") : translate("auto.components.automations.ExternalAutomationRunTable.d5527d8fe7", "runs")}
|
||||
{totalCount}{' '}
|
||||
{totalCount === 1
|
||||
? translate('auto.components.automations.ExternalAutomationRunTable.872d032d05', 'run')
|
||||
: translate(
|
||||
'auto.components.automations.ExternalAutomationRunTable.d5527d8fe7',
|
||||
'runs'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -205,9 +213,24 @@ export function ExternalAutomationRunTable({
|
||||
<div>
|
||||
<div className="min-w-0 border-b border-border/50">
|
||||
<div className="grid grid-cols-[minmax(7.5rem,.45fr)_minmax(0,1fr)_auto] gap-3 border-b border-border/50 px-3 py-1.5 text-[11px] font-medium uppercase text-muted-foreground">
|
||||
<span>{translate("auto.components.automations.ExternalAutomationRunTable.d4b34feb66", "Run time")}</span>
|
||||
<span>{translate("auto.components.automations.ExternalAutomationRunTable.a813df9808", "Preview")}</span>
|
||||
<span>{translate("auto.components.automations.ExternalAutomationRunTable.be551397ca", "Status")}</span>
|
||||
<span>
|
||||
{translate(
|
||||
'auto.components.automations.ExternalAutomationRunTable.d4b34feb66',
|
||||
'Run time'
|
||||
)}
|
||||
</span>
|
||||
<span>
|
||||
{translate(
|
||||
'auto.components.automations.ExternalAutomationRunTable.a813df9808',
|
||||
'Preview'
|
||||
)}
|
||||
</span>
|
||||
<span>
|
||||
{translate(
|
||||
'auto.components.automations.ExternalAutomationRunTable.be551397ca',
|
||||
'Status'
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="divide-y divide-border/50">
|
||||
{visibleRuns.map((run) => (
|
||||
@@ -248,7 +271,15 @@ export function ExternalAutomationRunTable({
|
||||
</div>
|
||||
) : (
|
||||
<div className="px-3 py-4 text-sm text-muted-foreground">
|
||||
{isLoading ? translate("auto.components.automations.ExternalAutomationRunTable.8ea934cacf", "Loading runs...") : translate("auto.components.automations.ExternalAutomationRunTable.9c080765ff", "No Hermes runs found yet.")}
|
||||
{isLoading
|
||||
? translate(
|
||||
'auto.components.automations.ExternalAutomationRunTable.8ea934cacf',
|
||||
'Loading runs...'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.automations.ExternalAutomationRunTable.9c080765ff',
|
||||
'No Hermes runs found yet.'
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -256,7 +287,9 @@ export function ExternalAutomationRunTable({
|
||||
<div className="flex min-w-0 items-center gap-2 text-xs text-muted-foreground">
|
||||
<FileText className="size-3.5" />
|
||||
<span>
|
||||
{pageStart}-{pageEnd} {translate("auto.components.automations.ExternalAutomationRunTable.7475c0ce96", "of")}{totalCount}
|
||||
{pageStart}-{pageEnd}{' '}
|
||||
{translate('auto.components.automations.ExternalAutomationRunTable.7475c0ce96', 'of')}
|
||||
{totalCount}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
@@ -264,7 +297,10 @@ export function ExternalAutomationRunTable({
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
aria-label={translate("auto.components.automations.ExternalAutomationRunTable.52d468a0b8", "Previous run page")}
|
||||
aria-label={translate(
|
||||
'auto.components.automations.ExternalAutomationRunTable.52d468a0b8',
|
||||
'Previous run page'
|
||||
)}
|
||||
disabled={page === 0 || isLoading}
|
||||
onClick={() => handlePageChange(Math.max(0, page - 1))}
|
||||
>
|
||||
@@ -277,7 +313,10 @@ export function ExternalAutomationRunTable({
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
aria-label={translate("auto.components.automations.ExternalAutomationRunTable.0ba9c0a95c", "Next run page")}
|
||||
aria-label={translate(
|
||||
'auto.components.automations.ExternalAutomationRunTable.0ba9c0a95c',
|
||||
'Next run page'
|
||||
)}
|
||||
disabled={page >= totalPages - 1 || isLoading}
|
||||
onClick={() => handlePageChange(Math.min(totalPages - 1, page + 1))}
|
||||
>
|
||||
|
||||
@@ -303,7 +303,10 @@ export function HermesCronOutputView({ content }: { content: string }): React.JS
|
||||
) : null}
|
||||
|
||||
{errorSection ? (
|
||||
<SectionCard title={translate("auto.components.automations.HermesCronOutputView.05affc68e3", "Error")} accent="error">
|
||||
<SectionCard
|
||||
title={translate('auto.components.automations.HermesCronOutputView.05affc68e3', 'Error')}
|
||||
accent="error"
|
||||
>
|
||||
<CommentMarkdown
|
||||
variant="document"
|
||||
content={errorSection.body}
|
||||
@@ -313,7 +316,13 @@ export function HermesCronOutputView({ content }: { content: string }): React.JS
|
||||
) : null}
|
||||
|
||||
{responseSection ? (
|
||||
<SectionCard title={translate("auto.components.automations.HermesCronOutputView.4557213074", "Response")} accent="response">
|
||||
<SectionCard
|
||||
title={translate(
|
||||
'auto.components.automations.HermesCronOutputView.4557213074',
|
||||
'Response'
|
||||
)}
|
||||
accent="response"
|
||||
>
|
||||
<CommentMarkdown
|
||||
variant="document"
|
||||
content={responseSection.body}
|
||||
@@ -324,7 +333,7 @@ export function HermesCronOutputView({ content }: { content: string }): React.JS
|
||||
|
||||
{promptSection ? (
|
||||
<CollapsibleSection
|
||||
title={translate("auto.components.automations.HermesCronOutputView.e27c716b43", "Prompt")}
|
||||
title={translate('auto.components.automations.HermesCronOutputView.e27c716b43', 'Prompt')}
|
||||
tone="muted"
|
||||
icon={MessageSquare}
|
||||
iconClass="text-indigo-700 dark:text-indigo-400"
|
||||
|
||||
@@ -75,7 +75,11 @@ export function WorkspaceCombobox({
|
||||
className={cn('h-9 w-full justify-between px-3 text-sm font-normal', triggerClassName)}
|
||||
>
|
||||
<span className={cn('truncate', !selected && 'text-muted-foreground')}>
|
||||
{selected?.displayName ?? translate("auto.components.automations.WorkspaceCombobox.66a0cd9628", "Select workspace")}
|
||||
{selected?.displayName ??
|
||||
translate(
|
||||
'auto.components.automations.WorkspaceCombobox.66a0cd9628',
|
||||
'Select workspace'
|
||||
)}
|
||||
</span>
|
||||
<ChevronsUpDown className="size-4 opacity-50" />
|
||||
</Button>
|
||||
@@ -89,9 +93,20 @@ export function WorkspaceCombobox({
|
||||
}}
|
||||
>
|
||||
<Command>
|
||||
<CommandInput ref={setInputNode} placeholder={translate("auto.components.automations.WorkspaceCombobox.8e9c8cc6b5", "Search workspaces...")} />
|
||||
<CommandInput
|
||||
ref={setInputNode}
|
||||
placeholder={translate(
|
||||
'auto.components.automations.WorkspaceCombobox.8e9c8cc6b5',
|
||||
'Search workspaces...'
|
||||
)}
|
||||
/>
|
||||
<CommandList className="max-h-72">
|
||||
<CommandEmpty>{translate("auto.components.automations.WorkspaceCombobox.ee5b280eba", "No workspaces found.")}</CommandEmpty>
|
||||
<CommandEmpty>
|
||||
{translate(
|
||||
'auto.components.automations.WorkspaceCombobox.ee5b280eba',
|
||||
'No workspaces found.'
|
||||
)}
|
||||
</CommandEmpty>
|
||||
{worktrees.map((worktree) => (
|
||||
<CommandItem
|
||||
key={worktree.id}
|
||||
|
||||
@@ -41,6 +41,9 @@ export function getExternalAutomationScheduleDisplay(
|
||||
}
|
||||
|
||||
return {
|
||||
label: translate("auto.components.automations.external.automation.schedule.display.a8e92b815a", "Schedule unavailable")
|
||||
label: translate(
|
||||
'auto.components.automations.external.automation.schedule.display.a8e92b815a',
|
||||
'Schedule unavailable'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,13 +152,22 @@ export default function BrowserFind({
|
||||
type="text"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder={translate("auto.components.browser.pane.BrowserFind.636a69cd66", "Find in page...")}
|
||||
placeholder={translate(
|
||||
'auto.components.browser.pane.BrowserFind.636a69cd66',
|
||||
'Find in page...'
|
||||
)}
|
||||
className="min-w-0 flex-1 border-none bg-transparent text-sm text-white outline-none placeholder:text-zinc-500"
|
||||
/>
|
||||
|
||||
{query ? (
|
||||
<span className="shrink-0 text-xs text-zinc-400">
|
||||
{totalMatches > 0 ? translate("auto.components.browser.pane.BrowserFind.fc63f336aa", "{{value0}} of {{value1}}", { value0: activeMatch, value1: totalMatches }) : translate("auto.components.browser.pane.BrowserFind.7baca7b1b8", "No matches")}
|
||||
{totalMatches > 0
|
||||
? translate(
|
||||
'auto.components.browser.pane.BrowserFind.fc63f336aa',
|
||||
'{{value0}} of {{value1}}',
|
||||
{ value0: activeMatch, value1: totalMatches }
|
||||
)
|
||||
: translate('auto.components.browser.pane.BrowserFind.7baca7b1b8', 'No matches')}
|
||||
</span>
|
||||
) : null}
|
||||
|
||||
@@ -170,7 +179,7 @@ export default function BrowserFind({
|
||||
size="icon-xs"
|
||||
onClick={findPrevious}
|
||||
className="flex size-6 shrink-0 items-center justify-center rounded text-zinc-400 hover:text-zinc-200"
|
||||
title={translate("auto.components.browser.pane.BrowserFind.ca7aebbd7f", "Previous match")}
|
||||
title={translate('auto.components.browser.pane.BrowserFind.ca7aebbd7f', 'Previous match')}
|
||||
>
|
||||
<ChevronUp size={14} />
|
||||
</Button>
|
||||
@@ -181,7 +190,7 @@ export default function BrowserFind({
|
||||
size="icon-xs"
|
||||
onClick={findNext}
|
||||
className="flex size-6 shrink-0 items-center justify-center rounded text-zinc-400 hover:text-zinc-200"
|
||||
title={translate("auto.components.browser.pane.BrowserFind.5c0c02ae76", "Next match")}
|
||||
title={translate('auto.components.browser.pane.BrowserFind.5c0c02ae76', 'Next match')}
|
||||
>
|
||||
<ChevronDown size={14} />
|
||||
</Button>
|
||||
@@ -194,7 +203,7 @@ export default function BrowserFind({
|
||||
size="icon-xs"
|
||||
onClick={onClose}
|
||||
className="flex size-6 shrink-0 items-center justify-center rounded text-zinc-400 hover:text-zinc-200"
|
||||
title={translate("auto.components.browser.pane.BrowserFind.c9d5f63fdc", "Close")}
|
||||
title={translate('auto.components.browser.pane.BrowserFind.c9d5f63fdc', 'Close')}
|
||||
>
|
||||
<X size={14} />
|
||||
</Button>
|
||||
|
||||
@@ -53,15 +53,33 @@ export function BrowserMobileDriverOverlay({ driver, onTakeBack }: Props): React
|
||||
<div className="pointer-events-auto flex w-full max-w-[30rem] flex-col gap-3 rounded-lg border border-border bg-card p-6 pb-5 text-card-foreground shadow-xs">
|
||||
<div className="flex items-center gap-1.5 text-xs font-medium text-foreground">
|
||||
<span aria-hidden="true">●</span>
|
||||
<span>{translate("auto.components.browser.pane.BrowserMobileDriverOverlay.20539eca03", "Mobile is driving this browser")}</span>
|
||||
<span>
|
||||
{translate(
|
||||
'auto.components.browser.pane.BrowserMobileDriverOverlay.20539eca03',
|
||||
'Mobile is driving this browser'
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-base font-semibold leading-tight">
|
||||
{translate(
|
||||
'auto.components.browser.pane.BrowserMobileDriverOverlay.d9768ec642',
|
||||
'Browser input is paused'
|
||||
)}
|
||||
</div>
|
||||
<div className="text-base font-semibold leading-tight">{translate("auto.components.browser.pane.BrowserMobileDriverOverlay.d9768ec642", "Browser input is paused")}</div>
|
||||
<div className="text-sm leading-relaxed text-muted-foreground">
|
||||
{translate("auto.components.browser.pane.BrowserMobileDriverOverlay.f4ecd61552", "This tab is being controlled from your phone. Take back to use it on desktop.")}</div>
|
||||
{translate(
|
||||
'auto.components.browser.pane.BrowserMobileDriverOverlay.f4ecd61552',
|
||||
'This tab is being controlled from your phone. Take back to use it on desktop.'
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-1 flex justify-end">
|
||||
{/* autoFocus puts keyboard users on the recovery action when the lock appears. */}
|
||||
<Button type="button" size="sm" onClick={handleTakeBack} disabled={pending} autoFocus>
|
||||
{translate("auto.components.browser.pane.BrowserMobileDriverOverlay.a6914ee43f", "Take back")}</Button>
|
||||
{translate(
|
||||
'auto.components.browser.pane.BrowserMobileDriverOverlay.a6914ee43f',
|
||||
'Take back'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4173,6 +4173,10 @@ function BrowserPagePane({
|
||||
[annotationBannerSendModeId, annotationTraySendModeId, closeAgentSendPopoverTargetMode]
|
||||
)
|
||||
|
||||
const handleBrowserAnnotationsSentToAgent = useCallback((): void => {
|
||||
recordFeatureInteraction('browser-annotations-sent-to-agent')
|
||||
}, [recordFeatureInteraction])
|
||||
|
||||
const handleClearBrowserAnnotations = useCallback((): void => {
|
||||
if (browserAnnotationsRef.current.length === 0) {
|
||||
return
|
||||
@@ -4877,6 +4881,7 @@ function BrowserPagePane({
|
||||
prompt={browserAnnotationsPrompt}
|
||||
promptDelivery="submit-after-ready"
|
||||
launchSource="notes_send"
|
||||
onPromptDelivered={handleBrowserAnnotationsSentToAgent}
|
||||
/>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
@@ -5136,6 +5141,7 @@ function BrowserPagePane({
|
||||
prompt={browserAnnotationsPrompt}
|
||||
promptDelivery="submit-after-ready"
|
||||
launchSource="notes_send"
|
||||
onPromptDelivered={handleBrowserAnnotationsSentToAgent}
|
||||
/>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
@@ -128,9 +128,14 @@ export default function GrabConfirmationSheet({
|
||||
<div className="flex items-center justify-between border-b border-border/70 px-4 py-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="rounded-md bg-indigo-500/10 px-2 py-0.5 text-xs font-medium text-indigo-400">
|
||||
{translate("auto.components.browser.pane.GrabConfirmationSheet.f3575229df", "Grab")}</div>
|
||||
{translate('auto.components.browser.pane.GrabConfirmationSheet.f3575229df', 'Grab')}
|
||||
</div>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{translate("auto.components.browser.pane.GrabConfirmationSheet.50f7114f99", "Review before attaching. Captured page context may include visible site content.")}</span>
|
||||
{translate(
|
||||
'auto.components.browser.pane.GrabConfirmationSheet.50f7114f99',
|
||||
'Review before attaching. Captured page context may include visible site content.'
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<Button size="icon" variant="ghost" className="h-7 w-7" onClick={onCancel}>
|
||||
<X className="size-4" />
|
||||
@@ -147,7 +152,10 @@ export default function GrabConfirmationSheet({
|
||||
<div className="overflow-hidden rounded-lg border border-border/60">
|
||||
<img
|
||||
src={payload.screenshot.dataUrl}
|
||||
alt={translate("auto.components.browser.pane.GrabConfirmationSheet.9c6ce0632a", "Selected element screenshot")}
|
||||
alt={translate(
|
||||
'auto.components.browser.pane.GrabConfirmationSheet.9c6ce0632a',
|
||||
'Selected element screenshot'
|
||||
)}
|
||||
className="max-h-48 w-full object-contain bg-black/5"
|
||||
/>
|
||||
</div>
|
||||
@@ -156,7 +164,11 @@ export default function GrabConfirmationSheet({
|
||||
{/* Element summary */}
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
|
||||
{translate("auto.components.browser.pane.GrabConfirmationSheet.a759d8f866", "Selected Element")}</h3>
|
||||
{translate(
|
||||
'auto.components.browser.pane.GrabConfirmationSheet.a759d8f866',
|
||||
'Selected Element'
|
||||
)}
|
||||
</h3>
|
||||
<div className="rounded-lg border border-border/60 bg-muted/20 p-3 text-sm">
|
||||
<div className="flex items-baseline gap-2">
|
||||
<span className="font-mono font-semibold text-foreground">
|
||||
@@ -164,14 +176,20 @@ export default function GrabConfirmationSheet({
|
||||
</span>
|
||||
{target.accessibility.role ? (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{translate("auto.components.browser.pane.GrabConfirmationSheet.d053db279d", "role=")}<EscapedText text={target.accessibility.role} />
|
||||
{translate(
|
||||
'auto.components.browser.pane.GrabConfirmationSheet.d053db279d',
|
||||
'role='
|
||||
)}
|
||||
<EscapedText text={target.accessibility.role} />
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
{target.accessibility.accessibleName ? (
|
||||
<div className="mt-1 text-muted-foreground">
|
||||
{translate("auto.components.browser.pane.GrabConfirmationSheet.eb98a0971a", "\"")}<EscapedText text={target.accessibility.accessibleName} />
|
||||
{translate("auto.components.browser.pane.GrabConfirmationSheet.eb98a0971a", "\"")}</div>
|
||||
{translate('auto.components.browser.pane.GrabConfirmationSheet.eb98a0971a', '"')}
|
||||
<EscapedText text={target.accessibility.accessibleName} />
|
||||
{translate('auto.components.browser.pane.GrabConfirmationSheet.eb98a0971a', '"')}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="mt-1 font-mono text-xs text-muted-foreground/70">
|
||||
<EscapedText text={target.selector} />
|
||||
@@ -185,10 +203,19 @@ export default function GrabConfirmationSheet({
|
||||
{/* Page info */}
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
|
||||
{translate("auto.components.browser.pane.GrabConfirmationSheet.9098b118ab", "Page")}</h3>
|
||||
{translate('auto.components.browser.pane.GrabConfirmationSheet.9098b118ab', 'Page')}
|
||||
</h3>
|
||||
<div className="rounded-lg border border-border/60 bg-muted/20 p-3 text-sm">
|
||||
<div className="font-medium text-foreground">
|
||||
<EscapedText text={page.title || translate("auto.components.browser.pane.GrabConfirmationSheet.405bb315da", "Untitled")} />
|
||||
<EscapedText
|
||||
text={
|
||||
page.title ||
|
||||
translate(
|
||||
'auto.components.browser.pane.GrabConfirmationSheet.405bb315da',
|
||||
'Untitled'
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-0.5 text-xs text-muted-foreground/70">
|
||||
<EscapedText text={page.sanitizedUrl} />
|
||||
@@ -200,7 +227,8 @@ export default function GrabConfirmationSheet({
|
||||
{target.htmlSnippet ? (
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
|
||||
{translate("auto.components.browser.pane.GrabConfirmationSheet.7d1480fbf1", "HTML")}</h3>
|
||||
{translate('auto.components.browser.pane.GrabConfirmationSheet.7d1480fbf1', 'HTML')}
|
||||
</h3>
|
||||
<pre className="max-h-32 overflow-auto rounded-lg border border-border/60 bg-muted/20 p-3 font-mono text-xs text-foreground/80 scrollbar-sleek">
|
||||
<EscapedText text={target.htmlSnippet} />
|
||||
</pre>
|
||||
@@ -211,7 +239,11 @@ export default function GrabConfirmationSheet({
|
||||
{nearbyText.length > 0 ? (
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
|
||||
{translate("auto.components.browser.pane.GrabConfirmationSheet.effd75e330", "Nearby Context")}</h3>
|
||||
{translate(
|
||||
'auto.components.browser.pane.GrabConfirmationSheet.effd75e330',
|
||||
'Nearby Context'
|
||||
)}
|
||||
</h3>
|
||||
<div className="rounded-lg border border-border/60 bg-muted/20 p-3">
|
||||
<ul className="list-inside list-disc space-y-0.5 text-sm text-muted-foreground">
|
||||
{nearbyText.map((text, i) => (
|
||||
@@ -229,18 +261,28 @@ export default function GrabConfirmationSheet({
|
||||
{/* Actions */}
|
||||
<div className="flex items-center justify-end gap-2 border-t border-border/70 px-4 py-3">
|
||||
<Button variant="ghost" size="sm" onClick={onCancel}>
|
||||
{translate("auto.components.browser.pane.GrabConfirmationSheet.87d97bdd6d", "Cancel")}</Button>
|
||||
{translate('auto.components.browser.pane.GrabConfirmationSheet.87d97bdd6d', 'Cancel')}
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" className="gap-1.5" onClick={onCopy}>
|
||||
<Copy className="size-3.5" />
|
||||
{translate("auto.components.browser.pane.GrabConfirmationSheet.26fd87f4df", "Copy")}</Button>
|
||||
{translate('auto.components.browser.pane.GrabConfirmationSheet.26fd87f4df', 'Copy')}
|
||||
</Button>
|
||||
{onCopyScreenshot ? (
|
||||
<Button variant="outline" size="sm" className="gap-1.5" onClick={onCopyScreenshot}>
|
||||
<Image className="size-3.5" />
|
||||
{translate("auto.components.browser.pane.GrabConfirmationSheet.7095e98362", "Copy Screenshot")}</Button>
|
||||
{translate(
|
||||
'auto.components.browser.pane.GrabConfirmationSheet.7095e98362',
|
||||
'Copy Screenshot'
|
||||
)}
|
||||
</Button>
|
||||
) : null}
|
||||
<Button size="sm" className="gap-1.5" onClick={onAttach}>
|
||||
<MessageSquarePlus className="size-3.5" />
|
||||
{translate("auto.components.browser.pane.GrabConfirmationSheet.314a0aaa5b", "Attach to AI")}</Button>
|
||||
{translate(
|
||||
'auto.components.browser.pane.GrabConfirmationSheet.314a0aaa5b',
|
||||
'Attach to AI'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -83,7 +83,11 @@ export function buildBrowserAddressBarSuggestions({
|
||||
topAction = {
|
||||
url: buildSearchUrl(trimmed, searchEngine, { kagiSessionLink }),
|
||||
title: trimmed,
|
||||
subtitle: translate("auto.components.browser.pane.browser.address.bar.suggestions.87fcdd0da9", "{{value0}} Search", { value0: SEARCH_ENGINE_LABELS[searchEngine] }),
|
||||
subtitle: translate(
|
||||
'auto.components.browser.pane.browser.address.bar.suggestions.87fcdd0da9',
|
||||
'{{value0}} Search',
|
||||
{ value0: SEARCH_ENGINE_LABELS[searchEngine] }
|
||||
),
|
||||
lastVisitedAt: 0,
|
||||
visitCount: 0,
|
||||
isSearch: true
|
||||
|
||||
@@ -99,14 +99,16 @@ export function ConfirmationDialogProvider({
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="outline" onClick={() => settleActiveRequest(false)}>
|
||||
{displayedRequest?.options.cancelLabel ?? translate("auto.components.confirmation.dialog.56f5c60e0c", "Cancel")}
|
||||
{displayedRequest?.options.cancelLabel ??
|
||||
translate('auto.components.confirmation.dialog.56f5c60e0c', 'Cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant={displayedRequest?.options.confirmVariant ?? 'default'}
|
||||
onClick={() => settleActiveRequest(true)}
|
||||
>
|
||||
{displayedRequest?.options.confirmLabel ?? translate("auto.components.confirmation.dialog.8490e5d36a", "Confirm")}
|
||||
{displayedRequest?.options.confirmLabel ??
|
||||
translate('auto.components.confirmation.dialog.8490e5d36a', 'Confirm')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@@ -36,15 +36,27 @@ function AutoRenameBranchFromWorkControl(): JSX.Element {
|
||||
<div className="mt-3 rounded-md border border-border/70 bg-muted/35 px-3 py-2.5">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<div className="text-xs font-medium text-foreground">{translate("auto.components.contextual.tours.ContextualTourControl.731c5573df", "Auto-name from first message")}</div>
|
||||
<div className="text-xs font-medium text-foreground">
|
||||
{translate(
|
||||
'auto.components.contextual.tours.ContextualTourControl.731c5573df',
|
||||
'Auto-name from first message'
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-0.5 text-[11px] leading-4 text-muted-foreground">
|
||||
{translate("auto.components.contextual.tours.ContextualTourControl.02e8373219", "Auto-generates a new name when you leave this text box empty.")}</div>
|
||||
{translate(
|
||||
'auto.components.contextual.tours.ContextualTourControl.02e8373219',
|
||||
'Auto-generates a new name when you leave this text box empty.'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-checked={enabled}
|
||||
aria-label={translate("auto.components.contextual.tours.ContextualTourControl.186eecc34f", "Auto-name workspace from first agent message")}
|
||||
aria-label={translate(
|
||||
'auto.components.contextual.tours.ContextualTourControl.186eecc34f',
|
||||
'Auto-name workspace from first agent message'
|
||||
)}
|
||||
onClick={() => {
|
||||
toggleAutoRenameBranchFromWork({
|
||||
enabled,
|
||||
|
||||
@@ -132,7 +132,17 @@ export function ContextualTourOverlaySurface({
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
aria-label={renderState.isLastStep ? translate("auto.components.contextual.tours.ContextualTourOverlaySurface.d974f32a83", "Dismiss tour") : translate("auto.components.contextual.tours.ContextualTourOverlaySurface.4f86e2a10b", "Skip tour")}
|
||||
aria-label={
|
||||
renderState.isLastStep
|
||||
? translate(
|
||||
'auto.components.contextual.tours.ContextualTourOverlaySurface.d974f32a83',
|
||||
'Dismiss tour'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.contextual.tours.ContextualTourOverlaySurface.4f86e2a10b',
|
||||
'Skip tour'
|
||||
)
|
||||
}
|
||||
onClick={() => onSkip(activeTourId)}
|
||||
className="absolute right-2 top-2 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
@@ -150,9 +160,22 @@ export function ContextualTourOverlaySurface({
|
||||
/>
|
||||
<div className="flex items-center gap-1.5">
|
||||
{!renderState.isFirstStep ? (
|
||||
<Button type="button" variant="ghost" size="xs" aria-label={translate("auto.components.contextual.tours.ContextualTourOverlaySurface.4a9568f773", "Back")} onClick={onBack}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
aria-label={translate(
|
||||
'auto.components.contextual.tours.ContextualTourOverlaySurface.4a9568f773',
|
||||
'Back'
|
||||
)}
|
||||
onClick={onBack}
|
||||
>
|
||||
<ArrowLeft />
|
||||
{translate("auto.components.contextual.tours.ContextualTourOverlaySurface.4a9568f773", "Back")}</Button>
|
||||
{translate(
|
||||
'auto.components.contextual.tours.ContextualTourOverlaySurface.4a9568f773',
|
||||
'Back'
|
||||
)}
|
||||
</Button>
|
||||
) : null}
|
||||
{renderState.secondaryAction ? (
|
||||
<Button
|
||||
@@ -176,7 +199,7 @@ export function ContextualTourOverlaySurface({
|
||||
}
|
||||
>
|
||||
{primaryAction.label}
|
||||
{primaryAction.kind === "next" && !renderState.isLastStep ? <ArrowRight /> : null}
|
||||
{primaryAction.kind === 'next' && !renderState.isLastStep ? <ArrowRight /> : null}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -19,7 +19,11 @@ export function ContextualTourProgressDots({
|
||||
aria-valuemin={1}
|
||||
aria-valuemax={total}
|
||||
aria-valuenow={current}
|
||||
aria-label={translate("auto.components.contextual.tours.ContextualTourProgressDots.dcd6e6b03e", "Step {{value0}} of {{value1}}", { value0: current, value1: total })}
|
||||
aria-label={translate(
|
||||
'auto.components.contextual.tours.ContextualTourProgressDots.dcd6e6b03e',
|
||||
'Step {{value0}} of {{value1}}',
|
||||
{ value0: current, value1: total }
|
||||
)}
|
||||
>
|
||||
<span className="flex items-center gap-1.5" aria-hidden="true">
|
||||
{Array.from({ length: total }).map((_, index) => {
|
||||
@@ -41,7 +45,9 @@ export function ContextualTourProgressDots({
|
||||
})}
|
||||
</span>
|
||||
<span className="whitespace-nowrap text-[11px] font-medium leading-none text-muted-foreground">
|
||||
{current} {translate("auto.components.contextual.tours.ContextualTourProgressDots.7734cb8ad3", "of")}{total}
|
||||
{current}{' '}
|
||||
{translate('auto.components.contextual.tours.ContextualTourProgressDots.7734cb8ad3', 'of')}
|
||||
{total}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -118,7 +118,13 @@ export function measureContextualTourOverlayRenderState(args: {
|
||||
const sidebarAlreadyVisible =
|
||||
activeStep.primaryAction?.kind === 'show-worktrees' && args.sidebarOpen
|
||||
const primaryAction = sidebarAlreadyVisible
|
||||
? ({ kind: 'next', label: translate("auto.components.contextual.tours.contextual.tour.overlay.measurement.38b3155418", "Next") } as const)
|
||||
? ({
|
||||
kind: 'next',
|
||||
label: translate(
|
||||
'auto.components.contextual.tours.contextual.tour.overlay.measurement.38b3155418',
|
||||
'Next'
|
||||
)
|
||||
} as const)
|
||||
: activeStep.primaryAction
|
||||
const secondaryAction = sidebarAlreadyVisible ? undefined : activeStep.secondaryAction
|
||||
|
||||
|
||||
@@ -52,7 +52,15 @@ export function DashboardAgentChildDisclosure({
|
||||
onMouseDown={stopMouseDown}
|
||||
onKeyDown={stopKeyDown}
|
||||
className="-ml-0.5 inline-flex size-4 shrink-0 items-center justify-center rounded-sm border border-sidebar-border/80 bg-sidebar text-foreground/80 shadow-xs hover:bg-sidebar-accent hover:text-foreground"
|
||||
aria-label={translate("auto.components.dashboard.DashboardAgentChildDisclosure.1b57ce9fa4", "{{value0}} {{value1}} child {{value2}}", { value0: childAgentsExpanded ? 'Hide' : 'Show', value1: childAgentCount, value2: childAgentCount === 1 ? 'agent' : 'agents' })}
|
||||
aria-label={translate(
|
||||
'auto.components.dashboard.DashboardAgentChildDisclosure.1b57ce9fa4',
|
||||
'{{value0}} {{value1}} child {{value2}}',
|
||||
{
|
||||
value0: childAgentsExpanded ? 'Hide' : 'Show',
|
||||
value1: childAgentCount,
|
||||
value2: childAgentCount === 1 ? 'agent' : 'agents'
|
||||
}
|
||||
)}
|
||||
aria-expanded={childAgentsExpanded}
|
||||
>
|
||||
<ChevronRight
|
||||
|
||||
@@ -26,9 +26,16 @@ export function DashboardAgentRowMessage({
|
||||
{isInterrupted ? (
|
||||
<span
|
||||
className="shrink-0 text-[10px] leading-snug text-muted-foreground/80"
|
||||
aria-label={translate("auto.components.dashboard.DashboardAgentRowMessage.1ec01cef03", "Interrupted by user")}
|
||||
aria-label={translate(
|
||||
'auto.components.dashboard.DashboardAgentRowMessage.1ec01cef03',
|
||||
'Interrupted by user'
|
||||
)}
|
||||
>
|
||||
{translate("auto.components.dashboard.DashboardAgentRowMessage.0a01046763", "interrupted")}</span>
|
||||
{translate(
|
||||
'auto.components.dashboard.DashboardAgentRowMessage.0a01046763',
|
||||
'interrupted'
|
||||
)}
|
||||
</span>
|
||||
) : null}
|
||||
{lastAssistantMessage ? (
|
||||
<CommentMarkdown
|
||||
|
||||
@@ -159,15 +159,22 @@ export function DiffCommentCard({
|
||||
<button
|
||||
type="button"
|
||||
className="orca-diff-comment-pill-btn"
|
||||
title={translate("auto.components.diff.comments.DiffCommentCard.508ee678a5", "Open in browser")}
|
||||
aria-label={translate("auto.components.diff.comments.DiffCommentCard.508ee678a5", "Open in browser")}
|
||||
title={translate(
|
||||
'auto.components.diff.comments.DiffCommentCard.508ee678a5',
|
||||
'Open in browser'
|
||||
)}
|
||||
aria-label={translate(
|
||||
'auto.components.diff.comments.DiffCommentCard.508ee678a5',
|
||||
'Open in browser'
|
||||
)}
|
||||
onClick={(ev) => {
|
||||
ev.preventDefault()
|
||||
ev.stopPropagation()
|
||||
void window.api.shell.openUrl(url)
|
||||
}}
|
||||
>
|
||||
{translate("auto.components.diff.comments.DiffCommentCard.6978871a3d", "Open")}</button>
|
||||
{translate('auto.components.diff.comments.DiffCommentCard.6978871a3d', 'Open')}
|
||||
</button>
|
||||
{(onSubmitEdit || onDelete) && (
|
||||
<span className="orca-diff-comment-pill-divider" />
|
||||
)}
|
||||
@@ -178,8 +185,14 @@ export function DiffCommentCard({
|
||||
<button
|
||||
type="button"
|
||||
className="orca-diff-comment-pill-btn"
|
||||
title={translate("auto.components.diff.comments.DiffCommentCard.cad3384faa", "Edit note")}
|
||||
aria-label={translate("auto.components.diff.comments.DiffCommentCard.cad3384faa", "Edit note")}
|
||||
title={translate(
|
||||
'auto.components.diff.comments.DiffCommentCard.cad3384faa',
|
||||
'Edit note'
|
||||
)}
|
||||
aria-label={translate(
|
||||
'auto.components.diff.comments.DiffCommentCard.cad3384faa',
|
||||
'Edit note'
|
||||
)}
|
||||
onClick={(ev) => {
|
||||
ev.preventDefault()
|
||||
ev.stopPropagation()
|
||||
@@ -195,8 +208,14 @@ export function DiffCommentCard({
|
||||
<button
|
||||
type="button"
|
||||
className="orca-diff-comment-pill-btn orca-diff-comment-pill-btn-danger"
|
||||
title={translate("auto.components.diff.comments.DiffCommentCard.cce596969e", "Delete note")}
|
||||
aria-label={translate("auto.components.diff.comments.DiffCommentCard.cce596969e", "Delete note")}
|
||||
title={translate(
|
||||
'auto.components.diff.comments.DiffCommentCard.cce596969e',
|
||||
'Delete note'
|
||||
)}
|
||||
aria-label={translate(
|
||||
'auto.components.diff.comments.DiffCommentCard.cce596969e',
|
||||
'Delete note'
|
||||
)}
|
||||
onClick={(ev) => {
|
||||
ev.preventDefault()
|
||||
ev.stopPropagation()
|
||||
@@ -249,14 +268,23 @@ export function DiffCommentCard({
|
||||
/>
|
||||
<div className="orca-diff-comment-popover-footer">
|
||||
<Button variant="ghost" size="sm" onClick={handleCancel} disabled={submitting}>
|
||||
{translate("auto.components.diff.comments.DiffCommentCard.0203bed775", "Cancel")}</Button>
|
||||
{translate('auto.components.diff.comments.DiffCommentCard.0203bed775', 'Cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => void handleSubmit()}
|
||||
disabled={!canSubmit}
|
||||
title={submitting ? translate("auto.components.diff.comments.DiffCommentCard.bb0a55f856", "Saving…") : undefined}
|
||||
title={
|
||||
submitting
|
||||
? translate(
|
||||
'auto.components.diff.comments.DiffCommentCard.bb0a55f856',
|
||||
'Saving…'
|
||||
)
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{translate("auto.components.diff.comments.DiffCommentCard.109a791e7b", "Save")}<CornerDownLeft className="ml-1 size-3 opacity-70" />
|
||||
{translate('auto.components.diff.comments.DiffCommentCard.109a791e7b', 'Save')}
|
||||
<CornerDownLeft className="ml-1 size-3 opacity-70" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -130,8 +130,16 @@ export function DiffCommentPopover({
|
||||
<div id={labelId} className="orca-diff-comment-popover-label">
|
||||
{title ??
|
||||
(startLine && startLine !== lineNumber
|
||||
? translate("auto.components.diff.comments.DiffCommentPopover.c845170b3b", "Lines {{value0}}-{{value1}}", { value0: startLine, value1: lineNumber })
|
||||
: translate("auto.components.diff.comments.DiffCommentPopover.e05063cfc1", "Line {{value0}}", { value0: lineNumber }))}
|
||||
? translate(
|
||||
'auto.components.diff.comments.DiffCommentPopover.c845170b3b',
|
||||
'Lines {{value0}}-{{value1}}',
|
||||
{ value0: startLine, value1: lineNumber }
|
||||
)
|
||||
: translate(
|
||||
'auto.components.diff.comments.DiffCommentPopover.e05063cfc1',
|
||||
'Line {{value0}}',
|
||||
{ value0: lineNumber }
|
||||
))}
|
||||
</div>
|
||||
<textarea
|
||||
ref={focusTextareaRef}
|
||||
@@ -170,7 +178,8 @@ export function DiffCommentPopover({
|
||||
/>
|
||||
<div className="orca-diff-comment-popover-footer">
|
||||
<Button variant="ghost" size="sm" onClick={onCancel}>
|
||||
{translate("auto.components.diff.comments.DiffCommentPopover.2b3ce6d394", "Cancel")}</Button>
|
||||
{translate('auto.components.diff.comments.DiffCommentPopover.2b3ce6d394', 'Cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={handleSubmit}
|
||||
|
||||
@@ -106,7 +106,10 @@ function getSingleCommentSendScopes(
|
||||
return [
|
||||
{
|
||||
id: 'note',
|
||||
label: translate("auto.components.diff.comments.useDiffCommentDecorator.995fa28b50", "This note"),
|
||||
label: translate(
|
||||
'auto.components.diff.comments.useDiffCommentDecorator.995fa28b50',
|
||||
'This note'
|
||||
),
|
||||
notes: comment.sentAt ? [] : [comment],
|
||||
prompt: formatCommentPrompt ? formatCommentPrompt(comment) : formatDiffComments([comment])
|
||||
}
|
||||
|
||||
@@ -38,16 +38,23 @@ export function ChangesModeView({
|
||||
if (!dc) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full text-muted-foreground text-sm">
|
||||
{translate("auto.components.editor.ChangesModeView.54e0035b15", "Loading diff...")}</div>
|
||||
{translate('auto.components.editor.ChangesModeView.54e0035b15', 'Loading diff...')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
if (dc.kind === 'binary') {
|
||||
return (
|
||||
<div className="flex h-full items-center justify-center px-6 text-center">
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm font-medium text-foreground">{translate("auto.components.editor.ChangesModeView.7dffb0f563", "Binary file")}</div>
|
||||
<div className="text-sm font-medium text-foreground">
|
||||
{translate('auto.components.editor.ChangesModeView.7dffb0f563', 'Binary file')}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{translate("auto.components.editor.ChangesModeView.052c184f24", "Text diff is unavailable for this file.")}</div>
|
||||
{translate(
|
||||
'auto.components.editor.ChangesModeView.052c184f24',
|
||||
'Text diff is unavailable for this file.'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -68,7 +75,11 @@ export function ChangesModeView({
|
||||
{activeFile.conflict && <ConflictBanner file={activeFile} entry={activeConflictEntry} />}
|
||||
{isIdentical && (
|
||||
<div className="border-b border-border/60 bg-muted/40 px-3 py-2 text-xs text-muted-foreground">
|
||||
{translate("auto.components.editor.ChangesModeView.ef25ae2d09", "No uncommitted changes.")}</div>
|
||||
{translate(
|
||||
'auto.components.editor.ChangesModeView.ef25ae2d09',
|
||||
'No uncommitted changes.'
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex min-h-0 flex-1 flex-col">
|
||||
<DiffViewer
|
||||
|
||||
@@ -73,13 +73,15 @@ export default function CodeBlockCopyButton({
|
||||
type="button"
|
||||
className="code-block-copy-btn"
|
||||
onClick={handleCopy}
|
||||
aria-label={translate("auto.components.editor.CodeBlockCopyButton.1f9f4def45", "Copy code")}
|
||||
title={translate("auto.components.editor.CodeBlockCopyButton.1f9f4def45", "Copy code")}
|
||||
aria-label={translate('auto.components.editor.CodeBlockCopyButton.1f9f4def45', 'Copy code')}
|
||||
title={translate('auto.components.editor.CodeBlockCopyButton.1f9f4def45', 'Copy code')}
|
||||
>
|
||||
{copied ? (
|
||||
<>
|
||||
<Check size={14} />
|
||||
<span className="code-block-copy-label">{translate("auto.components.editor.CodeBlockCopyButton.28921f5bf9", "Copied")}</span>
|
||||
<span className="code-block-copy-label">
|
||||
{translate('auto.components.editor.CodeBlockCopyButton.28921f5bf9', 'Copied')}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<Copy size={14} />
|
||||
|
||||
@@ -171,12 +171,16 @@ export function CombinedDiffFileTree({
|
||||
<div className="sticky top-0 z-20 shrink-0 bg-background">
|
||||
<div className="flex items-center justify-between gap-2 border-b border-border px-3 py-1.5">
|
||||
<div className="text-[11px] font-semibold uppercase tracking-[0.05em] text-muted-foreground">
|
||||
{translate("auto.components.editor.CombinedDiffFileTree.481e63ca52", "Files")}</div>
|
||||
{translate('auto.components.editor.CombinedDiffFileTree.481e63ca52', 'Files')}
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
aria-label={translate("auto.components.editor.CombinedDiffFileTree.21783df79f", "Collapse file tree")}
|
||||
aria-label={translate(
|
||||
'auto.components.editor.CombinedDiffFileTree.21783df79f',
|
||||
'Collapse file tree'
|
||||
)}
|
||||
onClick={() => onCollapsedChange(true)}
|
||||
>
|
||||
<PanelLeftClose className="size-3.5" />
|
||||
@@ -188,7 +192,10 @@ export function CombinedDiffFileTree({
|
||||
<Input
|
||||
value={query}
|
||||
onChange={(event) => setQuery(event.target.value)}
|
||||
placeholder={translate("auto.components.editor.CombinedDiffFileTree.4cc7b83ffe", "Filter files...")}
|
||||
placeholder={translate(
|
||||
'auto.components.editor.CombinedDiffFileTree.4cc7b83ffe',
|
||||
'Filter files...'
|
||||
)}
|
||||
className="h-8 pl-7 text-xs"
|
||||
/>
|
||||
</div>
|
||||
@@ -198,7 +205,10 @@ export function CombinedDiffFileTree({
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="icon-sm"
|
||||
aria-label={translate("auto.components.editor.CombinedDiffFileTree.cd0e0ed79e", "Filter diff files")}
|
||||
aria-label={translate(
|
||||
'auto.components.editor.CombinedDiffFileTree.cd0e0ed79e',
|
||||
'Filter diff files'
|
||||
)}
|
||||
className={cn(activeFilterCount > 0 && 'border-foreground/30 text-foreground')}
|
||||
>
|
||||
<Filter className="size-3.5" />
|
||||
@@ -206,7 +216,11 @@ export function CombinedDiffFileTree({
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="end" side="bottom" sideOffset={6} className="w-56 p-0">
|
||||
<div className="border-b border-border px-3 py-2 text-xs font-semibold text-foreground">
|
||||
{translate("auto.components.editor.CombinedDiffFileTree.c00020f081", "File extensions")}</div>
|
||||
{translate(
|
||||
'auto.components.editor.CombinedDiffFileTree.c00020f081',
|
||||
'File extensions'
|
||||
)}
|
||||
</div>
|
||||
<div className="max-h-60 overflow-auto py-1 scrollbar-sleek">
|
||||
{availableExtensions.map((extension) => {
|
||||
const checked = !excludedExtensions.has(extension)
|
||||
@@ -234,7 +248,12 @@ export function CombinedDiffFileTree({
|
||||
<Check
|
||||
className={cn('size-3.5 shrink-0', includeViewed ? 'opacity-100' : 'opacity-0')}
|
||||
/>
|
||||
<span className="min-w-0 flex-1 truncate">{translate("auto.components.editor.CombinedDiffFileTree.be119cb9d1", "Viewed files")}</span>
|
||||
<span className="min-w-0 flex-1 truncate">
|
||||
{translate(
|
||||
'auto.components.editor.CombinedDiffFileTree.be119cb9d1',
|
||||
'Viewed files'
|
||||
)}
|
||||
</span>
|
||||
</button>
|
||||
{activeFilterCount > 0 && (
|
||||
<button
|
||||
@@ -242,7 +261,11 @@ export function CombinedDiffFileTree({
|
||||
className="w-full px-3 py-1.5 text-left text-xs text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
|
||||
onClick={resetFilters}
|
||||
>
|
||||
{translate("auto.components.editor.CombinedDiffFileTree.eafe1aeb53", "Reset filters")}</button>
|
||||
{translate(
|
||||
'auto.components.editor.CombinedDiffFileTree.eafe1aeb53',
|
||||
'Reset filters'
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
@@ -252,8 +275,12 @@ export function CombinedDiffFileTree({
|
||||
<div className="min-h-0 flex-1 overflow-auto py-1 scrollbar-sleek">
|
||||
{filteredEntries.length === 0 ? (
|
||||
<div className="px-3 py-6 text-center text-xs text-muted-foreground">
|
||||
{translate("auto.components.editor.CombinedDiffFileTree.f984289373", "No files match the current filters.")}</div>
|
||||
) : mode === "uncommitted" ? (
|
||||
{translate(
|
||||
'auto.components.editor.CombinedDiffFileTree.f984289373',
|
||||
'No files match the current filters.'
|
||||
)}
|
||||
</div>
|
||||
) : mode === 'uncommitted' ? (
|
||||
uncommittedGroups.map((group) => (
|
||||
<div key={group.area} className="py-1">
|
||||
<div className="px-3 pb-1 text-[11px] font-semibold uppercase tracking-[0.05em] text-muted-foreground">
|
||||
|
||||
@@ -1219,7 +1219,12 @@ export default function CombinedDiffViewer({
|
||||
if (ok) {
|
||||
setClearNotesDialogOpen(false)
|
||||
} else {
|
||||
toast.error(translate("auto.components.editor.CombinedDiffViewer.45cf23b418", "Failed to clear notes."))
|
||||
toast.error(
|
||||
translate(
|
||||
'auto.components.editor.CombinedDiffViewer.45cf23b418',
|
||||
'Failed to clear notes.'
|
||||
)
|
||||
)
|
||||
}
|
||||
} finally {
|
||||
if (mountedRef.current) {
|
||||
@@ -1269,9 +1274,17 @@ export default function CombinedDiffViewer({
|
||||
<div className="flex flex-1 items-center justify-center px-6 text-center">
|
||||
<div className="max-w-md space-y-3">
|
||||
<div className="text-sm font-medium text-foreground">
|
||||
{translate("auto.components.editor.CombinedDiffViewer.820ec01f24", "Conflicted files are reviewed separately")}</div>
|
||||
{translate(
|
||||
'auto.components.editor.CombinedDiffViewer.820ec01f24',
|
||||
'Conflicted files are reviewed separately'
|
||||
)}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{translate("auto.components.editor.CombinedDiffViewer.eb5f40e49c", "This diff view excludes unresolved conflicts because the normal two-way diff pipeline is not conflict-safe.")}</div>
|
||||
{translate(
|
||||
'auto.components.editor.CombinedDiffViewer.eb5f40e49c',
|
||||
'This diff view excludes unresolved conflicts because the normal two-way diff pipeline is not conflict-safe.'
|
||||
)}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{file.skippedConflicts!.map((entry) => entry.path).join(', ')}
|
||||
</div>
|
||||
@@ -1292,7 +1305,11 @@ export default function CombinedDiffViewer({
|
||||
)
|
||||
}
|
||||
>
|
||||
{translate("auto.components.editor.CombinedDiffViewer.39f8007549", "Review conflicts")}</Button>
|
||||
{translate(
|
||||
'auto.components.editor.CombinedDiffViewer.39f8007549',
|
||||
'Review conflicts'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1305,7 +1322,11 @@ export default function CombinedDiffViewer({
|
||||
<div className="flex h-full min-h-0 flex-col">
|
||||
{commitHeader}
|
||||
<div className="flex flex-1 items-center justify-center text-sm text-muted-foreground">
|
||||
{translate("auto.components.editor.CombinedDiffViewer.fd8892b120", "No changes to display")}</div>
|
||||
{translate(
|
||||
'auto.components.editor.CombinedDiffViewer.fd8892b120',
|
||||
'No changes to display'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1313,9 +1334,21 @@ export default function CombinedDiffViewer({
|
||||
const skippedConflictNotice =
|
||||
(file.skippedConflicts?.length ?? 0) > 0 ? (
|
||||
<div className="mx-4 mt-3 rounded-md border border-border/60 bg-muted/20 px-3 py-2 text-xs">
|
||||
<div className="font-medium text-foreground">{translate("auto.components.editor.CombinedDiffViewer.820ec01f24", "Conflicted files are reviewed separately")}</div>
|
||||
<div className="font-medium text-foreground">
|
||||
{translate(
|
||||
'auto.components.editor.CombinedDiffViewer.820ec01f24',
|
||||
'Conflicted files are reviewed separately'
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-1 text-muted-foreground">
|
||||
{file.skippedConflicts!.length} {translate("auto.components.editor.CombinedDiffViewer.689b99f8ad", "unresolved conflict")}{file.skippedConflicts!.length === 1 ? '' : 's'} {translate("auto.components.editor.CombinedDiffViewer.39e73e7181", "were excluded from this diff view.")}</div>
|
||||
{file.skippedConflicts!.length}{' '}
|
||||
{translate('auto.components.editor.CombinedDiffViewer.689b99f8ad', 'unresolved conflict')}
|
||||
{file.skippedConflicts!.length === 1 ? '' : 's'}{' '}
|
||||
{translate(
|
||||
'auto.components.editor.CombinedDiffViewer.39e73e7181',
|
||||
'were excluded from this diff view.'
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-2 flex items-center gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
@@ -1334,7 +1367,8 @@ export default function CombinedDiffViewer({
|
||||
)
|
||||
}
|
||||
>
|
||||
{translate("auto.components.editor.CombinedDiffViewer.39f8007549", "Review conflicts")}</Button>
|
||||
{translate('auto.components.editor.CombinedDiffViewer.39f8007549', 'Review conflicts')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : null
|
||||
@@ -1352,19 +1386,40 @@ export default function CombinedDiffViewer({
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
aria-label={translate("auto.components.editor.CombinedDiffViewer.b6c3b84476", "Show file tree")}
|
||||
aria-label={translate(
|
||||
'auto.components.editor.CombinedDiffViewer.b6c3b84476',
|
||||
'Show file tree'
|
||||
)}
|
||||
onClick={() => setFileTreeCollapsed(false)}
|
||||
>
|
||||
<PanelLeftOpen className="size-3.5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={6}>
|
||||
{translate("auto.components.editor.CombinedDiffViewer.b6c3b84476", "Show file tree")}</TooltipContent>
|
||||
{translate(
|
||||
'auto.components.editor.CombinedDiffViewer.b6c3b84476',
|
||||
'Show file tree'
|
||||
)}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
<span className="truncate text-xs text-muted-foreground">
|
||||
{sections.length} {translate("auto.components.editor.CombinedDiffViewer.7e7ca60816", "changed files")}{isBranchMode && branchCompare ? translate("auto.components.editor.CombinedDiffViewer.6094135eec", " vs {{value0}}", { value0: branchCompare.baseRef }) : ''}
|
||||
{isCommitMode && commitCompare ? translate("auto.components.editor.CombinedDiffViewer.724a13568d", " in {{value0}}", { value0: commitCompare.compareRef }) : ''}
|
||||
{sections.length}{' '}
|
||||
{translate('auto.components.editor.CombinedDiffViewer.7e7ca60816', 'changed files')}
|
||||
{isBranchMode && branchCompare
|
||||
? translate(
|
||||
'auto.components.editor.CombinedDiffViewer.6094135eec',
|
||||
' vs {{value0}}',
|
||||
{ value0: branchCompare.baseRef }
|
||||
)
|
||||
: ''}
|
||||
{isCommitMode && commitCompare
|
||||
? translate(
|
||||
'auto.components.editor.CombinedDiffViewer.724a13568d',
|
||||
' in {{value0}}',
|
||||
{ value0: commitCompare.compareRef }
|
||||
)
|
||||
: ''}
|
||||
</span>
|
||||
{diffCommentCount > 0 && (
|
||||
<div className="ml-1 flex shrink-0 items-center overflow-hidden rounded-full border border-border/70 bg-muted/40">
|
||||
@@ -1373,10 +1428,22 @@ export default function CombinedDiffViewer({
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex h-6 items-center gap-1 pl-2 pr-1.5 text-[11px] font-medium leading-none text-foreground/80 transition-colors hover:bg-accent hover:text-foreground"
|
||||
aria-label={translate("auto.components.editor.CombinedDiffViewer.8f68ad9ca9", "Show {{value0}} AI {{value1}}", { value0: diffCommentCount, value1: diffCommentCount === 1 ? 'note' : 'notes' })}
|
||||
aria-label={translate(
|
||||
'auto.components.editor.CombinedDiffViewer.8f68ad9ca9',
|
||||
'Show {{value0}} AI {{value1}}',
|
||||
{
|
||||
value0: diffCommentCount,
|
||||
value1: diffCommentCount === 1 ? 'note' : 'notes'
|
||||
}
|
||||
)}
|
||||
>
|
||||
<Sparkles className="size-3 text-violet-500 dark:text-violet-400" />
|
||||
<span>{translate("auto.components.editor.CombinedDiffViewer.bb84b4c374", "AI notes")}</span>
|
||||
<span>
|
||||
{translate(
|
||||
'auto.components.editor.CombinedDiffViewer.bb84b4c374',
|
||||
'AI notes'
|
||||
)}
|
||||
</span>
|
||||
<span className="rounded-full bg-background/80 px-1 text-[10px] tabular-nums text-muted-foreground">
|
||||
{diffCommentCount}
|
||||
</span>
|
||||
@@ -1409,22 +1476,32 @@ export default function CombinedDiffViewer({
|
||||
className="text-xs text-muted-foreground hover:text-foreground transition-colors"
|
||||
onClick={openAlternateDiff}
|
||||
>
|
||||
{file.combinedAlternate.source === "combined-branch"
|
||||
? translate("auto.components.editor.CombinedDiffViewer.3d909843bb", "Open Branch Diff")
|
||||
: translate("auto.components.editor.CombinedDiffViewer.982d14bfa5", "Open Uncommitted Diff")}
|
||||
{file.combinedAlternate.source === 'combined-branch'
|
||||
? translate(
|
||||
'auto.components.editor.CombinedDiffViewer.3d909843bb',
|
||||
'Open Branch Diff'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.editor.CombinedDiffViewer.982d14bfa5',
|
||||
'Open Uncommitted Diff'
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className="w-20 text-left text-xs text-muted-foreground hover:text-foreground transition-colors"
|
||||
onClick={() => setAllSectionsCollapsed(!allSectionsCollapsed)}
|
||||
>
|
||||
{allSectionsCollapsed ? translate("auto.components.editor.CombinedDiffViewer.19c45cfdc0", "Expand All") : translate("auto.components.editor.CombinedDiffViewer.ea08dae15b", "Collapse All")}
|
||||
{allSectionsCollapsed
|
||||
? translate('auto.components.editor.CombinedDiffViewer.19c45cfdc0', 'Expand All')
|
||||
: translate('auto.components.editor.CombinedDiffViewer.ea08dae15b', 'Collapse All')}
|
||||
</button>
|
||||
<button
|
||||
className="w-24 px-2 py-0.5 text-center text-xs rounded border border-border text-muted-foreground hover:text-foreground transition-colors"
|
||||
onClick={toggleSideBySide}
|
||||
>
|
||||
{sideBySide ? translate("auto.components.editor.CombinedDiffViewer.f786fd54e1", "Inline") : translate("auto.components.editor.CombinedDiffViewer.ec5053c7f5", "Side by Side")}
|
||||
{sideBySide
|
||||
? translate('auto.components.editor.CombinedDiffViewer.f786fd54e1', 'Inline')
|
||||
: translate('auto.components.editor.CombinedDiffViewer.ec5053c7f5', 'Side by Side')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1538,9 +1615,20 @@ export default function CombinedDiffViewer({
|
||||
>
|
||||
<DialogContent className="max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-sm">{translate("auto.components.editor.CombinedDiffViewer.948a5fd6c8", "Clear Notes")}</DialogTitle>
|
||||
<DialogTitle className="text-sm">
|
||||
{translate('auto.components.editor.CombinedDiffViewer.948a5fd6c8', 'Clear Notes')}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-xs">
|
||||
{translate("auto.components.editor.CombinedDiffViewer.84898c548d", "Clear")}{diffCommentCount} {diffCommentCount === 1 ? translate("auto.components.editor.CombinedDiffViewer.8ab3248fd8", "note") : translate("auto.components.editor.CombinedDiffViewer.0fb870a0fe", "notes")} {translate("auto.components.editor.CombinedDiffViewer.80a286d8f5", "from this worktree?")}</DialogDescription>
|
||||
{translate('auto.components.editor.CombinedDiffViewer.84898c548d', 'Clear')}
|
||||
{diffCommentCount}{' '}
|
||||
{diffCommentCount === 1
|
||||
? translate('auto.components.editor.CombinedDiffViewer.8ab3248fd8', 'note')
|
||||
: translate('auto.components.editor.CombinedDiffViewer.0fb870a0fe', 'notes')}{' '}
|
||||
{translate(
|
||||
'auto.components.editor.CombinedDiffViewer.80a286d8f5',
|
||||
'from this worktree?'
|
||||
)}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
@@ -1549,7 +1637,8 @@ export default function CombinedDiffViewer({
|
||||
onClick={() => setClearNotesDialogOpen(false)}
|
||||
disabled={isClearingNotes}
|
||||
>
|
||||
{translate("auto.components.editor.CombinedDiffViewer.0f806a2ab1", "Cancel")}</Button>
|
||||
{translate('auto.components.editor.CombinedDiffViewer.0f806a2ab1', 'Cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="destructive"
|
||||
@@ -1557,7 +1646,8 @@ export default function CombinedDiffViewer({
|
||||
disabled={isClearingNotes || diffCommentCount === 0}
|
||||
>
|
||||
<Trash2 className="size-4" />
|
||||
{translate("auto.components.editor.CombinedDiffViewer.948a5fd6c8", "Clear Notes")}</Button>
|
||||
{translate('auto.components.editor.CombinedDiffViewer.948a5fd6c8', 'Clear Notes')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
@@ -1585,7 +1675,9 @@ function DiffNotesPreviewPopover({
|
||||
<div className="flex items-center justify-between gap-2 border-b border-border/60 px-3 py-2">
|
||||
<div className="flex min-w-0 items-center gap-1.5 font-medium text-foreground">
|
||||
<MessageSquare className="size-3.5 shrink-0 text-muted-foreground" />
|
||||
<span>{translate("auto.components.editor.CombinedDiffViewer.bb84b4c374", "AI notes")}</span>
|
||||
<span>
|
||||
{translate('auto.components.editor.CombinedDiffViewer.bb84b4c374', 'AI notes')}
|
||||
</span>
|
||||
<span className="text-[11px] font-normal tabular-nums text-muted-foreground">
|
||||
{totalCount}
|
||||
</span>
|
||||
@@ -1600,7 +1692,8 @@ function DiffNotesPreviewPopover({
|
||||
disabled={totalCount === 0}
|
||||
>
|
||||
{copied ? <Check className="size-3" /> : <Copy className="size-3" />}
|
||||
{translate("auto.components.editor.CombinedDiffViewer.88b70d0ef5", "Copy")}</Button>
|
||||
{translate('auto.components.editor.CombinedDiffViewer.88b70d0ef5', 'Copy')}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
@@ -1610,7 +1703,8 @@ function DiffNotesPreviewPopover({
|
||||
disabled={totalCount === 0}
|
||||
>
|
||||
<Trash2 className="size-3" />
|
||||
{translate("auto.components.editor.CombinedDiffViewer.84898c548d", "Clear")}</Button>
|
||||
{translate('auto.components.editor.CombinedDiffViewer.84898c548d', 'Clear')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="max-h-72 overflow-y-auto p-2 scrollbar-sleek">
|
||||
@@ -1620,7 +1714,8 @@ function DiffNotesPreviewPopover({
|
||||
<span className="min-w-0 flex-1 truncate font-mono">{comment.filePath}</span>
|
||||
{comment.sentAt ? (
|
||||
<span className="shrink-0 rounded bg-muted px-1 py-0.5 text-[10px] leading-none">
|
||||
{translate("auto.components.editor.CombinedDiffViewer.1da745c551", "Sent")}</span>
|
||||
{translate('auto.components.editor.CombinedDiffViewer.1da745c551', 'Sent')}
|
||||
</span>
|
||||
) : null}
|
||||
<span className="shrink-0 tabular-nums">
|
||||
{getDiffCommentLineLabel(comment, true)}
|
||||
@@ -1633,7 +1728,13 @@ function DiffNotesPreviewPopover({
|
||||
))}
|
||||
{remainingCount > 0 && (
|
||||
<div className="px-2 py-1 text-[11px] text-muted-foreground">
|
||||
{remainingCount} {translate("auto.components.editor.CombinedDiffViewer.e3b9a6ce02", "more")}{remainingCount === 1 ? translate("auto.components.editor.CombinedDiffViewer.8ab3248fd8", "note") : translate("auto.components.editor.CombinedDiffViewer.0fb870a0fe", "notes")} {translate("auto.components.editor.CombinedDiffViewer.35cc27aeb2", "in Source Control")}</div>
|
||||
{remainingCount}{' '}
|
||||
{translate('auto.components.editor.CombinedDiffViewer.e3b9a6ce02', 'more')}
|
||||
{remainingCount === 1
|
||||
? translate('auto.components.editor.CombinedDiffViewer.8ab3248fd8', 'note')
|
||||
: translate('auto.components.editor.CombinedDiffViewer.0fb870a0fe', 'notes')}{' '}
|
||||
{translate('auto.components.editor.CombinedDiffViewer.35cc27aeb2', 'in Source Control')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -100,7 +100,9 @@ export function ConflictBanner({
|
||||
<CircleCheck className="size-3.5 shrink-0 text-emerald-600 dark:text-emerald-400" />
|
||||
)}
|
||||
<span className="min-w-0 truncate font-medium text-foreground">
|
||||
{label} {translate("auto.components.editor.ConflictComponents.55d61a0ccd", "conflict ·")}{CONFLICT_KIND_LABELS[conflict.conflictKind]}
|
||||
{label}{' '}
|
||||
{translate('auto.components.editor.ConflictComponents.55d61a0ccd', 'conflict ·')}
|
||||
{CONFLICT_KIND_LABELS[conflict.conflictKind]}
|
||||
</span>
|
||||
{conflictNavigation && conflictNavigation.total > 0 && (
|
||||
<span className="shrink-0 px-1 text-[11px] tabular-nums text-muted-foreground">
|
||||
@@ -116,14 +118,21 @@ export function ConflictBanner({
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
aria-label={translate("auto.components.editor.ConflictComponents.41d9af2e7a", "Previous conflict")}
|
||||
aria-label={translate(
|
||||
'auto.components.editor.ConflictComponents.41d9af2e7a',
|
||||
'Previous conflict'
|
||||
)}
|
||||
onClick={() => conflictNavigation.onJump('previous')}
|
||||
>
|
||||
<ChevronUp className="size-3.5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={6}>
|
||||
{translate("auto.components.editor.ConflictComponents.41d9af2e7a", "Previous conflict")}</TooltipContent>
|
||||
{translate(
|
||||
'auto.components.editor.ConflictComponents.41d9af2e7a',
|
||||
'Previous conflict'
|
||||
)}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
@@ -131,14 +140,18 @@ export function ConflictBanner({
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
aria-label={translate("auto.components.editor.ConflictComponents.9c2901ef8a", "Next conflict")}
|
||||
aria-label={translate(
|
||||
'auto.components.editor.ConflictComponents.9c2901ef8a',
|
||||
'Next conflict'
|
||||
)}
|
||||
onClick={() => conflictNavigation.onJump('next')}
|
||||
>
|
||||
<ChevronDown className="size-3.5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={6}>
|
||||
{translate("auto.components.editor.ConflictComponents.9c2901ef8a", "Next conflict")}</TooltipContent>
|
||||
{translate('auto.components.editor.ConflictComponents.9c2901ef8a', 'Next conflict')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
@@ -150,10 +163,17 @@ export function ConflictBanner({
|
||||
list where it provides actionable guidance. */}
|
||||
{!isUnresolved && (
|
||||
<div className="mt-1 text-muted-foreground">
|
||||
{translate("auto.components.editor.ConflictComponents.6e459867ad", "Session-local continuity state. Git is no longer reporting this file as unmerged.")}</div>
|
||||
{translate(
|
||||
'auto.components.editor.ConflictComponents.6e459867ad',
|
||||
'Session-local continuity state. Git is no longer reporting this file as unmerged.'
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{entry?.oldPath && (
|
||||
<div className="mt-1 text-muted-foreground">{translate("auto.components.editor.ConflictComponents.d5edd81755", "Renamed from")}{entry.oldPath}</div>
|
||||
<div className="mt-1 text-muted-foreground">
|
||||
{translate('auto.components.editor.ConflictComponents.d5edd81755', 'Renamed from')}
|
||||
{entry.oldPath}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
@@ -172,7 +192,11 @@ export function ConflictPlaceholderView({ file }: { file: OpenFile }): React.JSX
|
||||
{CONFLICT_KIND_LABELS[conflict.conflictKind]}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{conflict.message ?? translate("auto.components.editor.ConflictComponents.da539359b6", "No working-tree file is available to edit for this conflict.")}
|
||||
{conflict.message ??
|
||||
translate(
|
||||
'auto.components.editor.ConflictComponents.da539359b6',
|
||||
'No working-tree file is available to edit for this conflict.'
|
||||
)}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{conflict.guidance ?? CONFLICT_HINT_MAP[conflict.conflictKind]}
|
||||
@@ -233,16 +257,27 @@ export function ConflictReviewPanel({
|
||||
return (
|
||||
<div className="flex h-full items-center justify-center px-6 text-center">
|
||||
<div className="max-w-md space-y-3">
|
||||
<div className="text-sm font-medium text-foreground">{translate("auto.components.editor.ConflictComponents.992145ff5a", "All conflicts resolved")}</div>
|
||||
<div className="text-sm font-medium text-foreground">
|
||||
{translate(
|
||||
'auto.components.editor.ConflictComponents.992145ff5a',
|
||||
'All conflicts resolved'
|
||||
)}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{translate("auto.components.editor.ConflictComponents.31931dec46", "This review snapshot no longer has any live unresolved conflicts.")}</div>
|
||||
{translate(
|
||||
'auto.components.editor.ConflictComponents.31931dec46',
|
||||
'This review snapshot no longer has any live unresolved conflicts.'
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<Button type="button" size="sm" variant="outline" onClick={onReturnToSourceControl}>
|
||||
<GitMerge className="size-3.5" />
|
||||
{translate("auto.components.editor.ConflictComponents.28e7db4a90", "Source Control")}</Button>
|
||||
{translate('auto.components.editor.ConflictComponents.28e7db4a90', 'Source Control')}
|
||||
</Button>
|
||||
<Button type="button" size="sm" variant="ghost" onClick={onDismiss}>
|
||||
<X className="size-3.5" />
|
||||
{translate("auto.components.editor.ConflictComponents.58ad5ad431", "Dismiss")}</Button>
|
||||
{translate('auto.components.editor.ConflictComponents.58ad5ad431', 'Dismiss')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -268,34 +303,55 @@ export function ConflictReviewPanel({
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
aria-label={translate("auto.components.editor.ConflictComponents.c8ca989aea", "Show file tree")}
|
||||
aria-label={translate(
|
||||
'auto.components.editor.ConflictComponents.c8ca989aea',
|
||||
'Show file tree'
|
||||
)}
|
||||
onClick={() => setFileTreeCollapsed(false)}
|
||||
>
|
||||
<PanelLeftOpen className="size-3.5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={6}>
|
||||
{translate("auto.components.editor.ConflictComponents.c8ca989aea", "Show file tree")}</TooltipContent>
|
||||
{translate(
|
||||
'auto.components.editor.ConflictComponents.c8ca989aea',
|
||||
'Show file tree'
|
||||
)}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
<div className="flex min-w-0 flex-wrap items-baseline gap-x-1.5">
|
||||
<span className="text-sm font-medium text-foreground">
|
||||
{unresolvedCount} {translate("auto.components.editor.ConflictComponents.4be41eaafc", "unresolved conflict")}{unresolvedCount === 1 ? '' : 's'}
|
||||
{unresolvedCount}{' '}
|
||||
{translate(
|
||||
'auto.components.editor.ConflictComponents.4be41eaafc',
|
||||
'unresolved conflict'
|
||||
)}
|
||||
{unresolvedCount === 1 ? '' : 's'}
|
||||
</span>
|
||||
<span className="text-muted-foreground/50">·</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{translate("auto.components.editor.ConflictComponents.a1ce36f77d", "Snapshot captured at")}{snapshotTime}.
|
||||
{translate(
|
||||
'auto.components.editor.ConflictComponents.a1ce36f77d',
|
||||
'Snapshot captured at'
|
||||
)}
|
||||
{snapshotTime}.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<Button type="button" size="sm" variant="outline" onClick={onRefreshSnapshot}>
|
||||
<RefreshCw className="size-3.5" />
|
||||
{translate("auto.components.editor.ConflictComponents.90d576adb2", "Refresh")}</Button>
|
||||
{translate('auto.components.editor.ConflictComponents.90d576adb2', 'Refresh')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex min-h-0 flex-1 flex-col">
|
||||
{selectedContent ?? (
|
||||
<div className="flex h-full min-h-0 items-center justify-center px-6 text-center text-sm text-muted-foreground">
|
||||
{translate("auto.components.editor.ConflictComponents.f338288514", "Loading conflict contents...")}</div>
|
||||
{translate(
|
||||
'auto.components.editor.ConflictComponents.f338288514',
|
||||
'Loading conflict contents...'
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -73,14 +73,18 @@ export function ConflictReviewFileTree({
|
||||
<aside className="flex w-72 shrink-0 flex-col border-r border-border bg-background">
|
||||
<div className="flex items-center justify-between gap-2 border-b border-border px-3 py-1.5">
|
||||
<div className="text-[11px] font-semibold uppercase tracking-[0.05em] text-muted-foreground">
|
||||
{translate("auto.components.editor.ConflictReviewFileTree.99496bab6e", "Files")}</div>
|
||||
{translate('auto.components.editor.ConflictReviewFileTree.99496bab6e', 'Files')}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="text-[11px] text-muted-foreground tabular-nums">{entries.length}</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
aria-label={translate("auto.components.editor.ConflictReviewFileTree.a54551c5a6", "Collapse file tree")}
|
||||
aria-label={translate(
|
||||
'auto.components.editor.ConflictReviewFileTree.a54551c5a6',
|
||||
'Collapse file tree'
|
||||
)}
|
||||
onClick={() => onCollapsedChange(true)}
|
||||
>
|
||||
<PanelLeftClose className="size-3.5" />
|
||||
@@ -90,7 +94,11 @@ export function ConflictReviewFileTree({
|
||||
<div className="min-h-0 flex-1 overflow-auto py-1 scrollbar-sleek">
|
||||
{rows.length === 0 ? (
|
||||
<div className="px-3 py-6 text-center text-xs text-muted-foreground">
|
||||
{translate("auto.components.editor.ConflictReviewFileTree.3449521a8c", "No conflicts in this snapshot.")}</div>
|
||||
{translate(
|
||||
'auto.components.editor.ConflictReviewFileTree.3449521a8c',
|
||||
'No conflicts in this snapshot.'
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
rows.map((node) => (
|
||||
<ConflictReviewFileTreeRow
|
||||
@@ -182,7 +190,11 @@ function ConflictReviewFileTreeRow({
|
||||
: 'bg-muted text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
{isStillUnresolved ? translate("auto.components.editor.ConflictReviewFileTree.69d4e210bb", "Unresolved") : liveEntry ? translate("auto.components.editor.ConflictReviewFileTree.8528a5eaf5", "Resolved") : translate("auto.components.editor.ConflictReviewFileTree.496e28a932", "Gone")}
|
||||
{isStillUnresolved
|
||||
? translate('auto.components.editor.ConflictReviewFileTree.69d4e210bb', 'Unresolved')
|
||||
: liveEntry
|
||||
? translate('auto.components.editor.ConflictReviewFileTree.8528a5eaf5', 'Resolved')
|
||||
: translate('auto.components.editor.ConflictReviewFileTree.496e28a932', 'Gone')}
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
|
||||
@@ -92,7 +92,8 @@ export default function CsvViewer({ content, filePath }: CsvViewerProps): React.
|
||||
if (parsed.rows.length === 0) {
|
||||
return (
|
||||
<div className="flex h-full items-center justify-center text-sm text-muted-foreground">
|
||||
{translate("auto.components.editor.CsvViewer.a233d55b77", "Empty file")}</div>
|
||||
{translate('auto.components.editor.CsvViewer.a233d55b77', 'Empty file')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -178,8 +179,13 @@ export default function CsvViewer({ content, filePath }: CsvViewerProps): React.
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-4 border-t border-border/60 px-3 py-1 text-xs text-muted-foreground">
|
||||
<span>{bodyRows.length.toLocaleString()} {translate("auto.components.editor.CsvViewer.ac31d2cd60", "rows")}</span>
|
||||
<span>{columnCount} {translate("auto.components.editor.CsvViewer.eedd0d37a7", "columns")}</span>
|
||||
<span>
|
||||
{bodyRows.length.toLocaleString()}{' '}
|
||||
{translate('auto.components.editor.CsvViewer.ac31d2cd60', 'rows')}
|
||||
</span>
|
||||
<span>
|
||||
{columnCount} {translate('auto.components.editor.CsvViewer.eedd0d37a7', 'columns')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -43,7 +43,7 @@ export function DiffNotesSendMenu({
|
||||
const scopes = useMemo<NotesSendMenuScope<DiffComment>[]>(() => {
|
||||
const allNotesScope = {
|
||||
id: 'all',
|
||||
label: translate("auto.components.editor.DiffNotesSendMenu.8b87612461", "All unsent notes"),
|
||||
label: translate('auto.components.editor.DiffNotesSendMenu.8b87612461', 'All unsent notes'),
|
||||
notes: unsentNotes,
|
||||
prompt: unsentPrompt
|
||||
}
|
||||
@@ -53,7 +53,7 @@ export function DiffNotesSendMenu({
|
||||
return [
|
||||
{
|
||||
id: 'file',
|
||||
label: translate("auto.components.editor.DiffNotesSendMenu.f1aa04b5cf", "This file"),
|
||||
label: translate('auto.components.editor.DiffNotesSendMenu.f1aa04b5cf', 'This file'),
|
||||
notes: unsentFileNotes,
|
||||
prompt: unsentFilePrompt
|
||||
},
|
||||
|
||||
@@ -85,7 +85,9 @@ export function DiffSectionBody({
|
||||
{section.loading ? (
|
||||
<div className="flex h-full items-center gap-2 bg-muted/10 px-3 text-[11px] text-muted-foreground">
|
||||
<span className="h-1.5 w-1.5 rounded-full bg-muted-foreground/50" />
|
||||
<span>{translate("auto.components.editor.DiffSectionBody.f5cf81cec2", "Loading diff...")}</span>
|
||||
<span>
|
||||
{translate('auto.components.editor.DiffSectionBody.f5cf81cec2', 'Loading diff...')}
|
||||
</span>
|
||||
</div>
|
||||
) : section.error ? (
|
||||
<div className="flex h-full items-center justify-between gap-3 bg-muted/10 px-3 text-[11px] text-muted-foreground">
|
||||
@@ -104,7 +106,8 @@ export function DiffSectionBody({
|
||||
}}
|
||||
>
|
||||
<RefreshCw className="size-3" />
|
||||
{translate("auto.components.editor.DiffSectionBody.cef4cf0ff5", "Retry")}</Button>
|
||||
{translate('auto.components.editor.DiffSectionBody.cef4cf0ff5', 'Retry')}
|
||||
</Button>
|
||||
</div>
|
||||
) : section.diffResult?.kind === 'binary' ? (
|
||||
section.diffResult.isImage ? (
|
||||
@@ -119,11 +122,22 @@ export function DiffSectionBody({
|
||||
) : (
|
||||
<div className="flex h-full items-center justify-center px-6 text-center">
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm font-medium text-foreground">{translate("auto.components.editor.DiffSectionBody.35d6afb5be", "Binary file changed")}</div>
|
||||
<div className="text-sm font-medium text-foreground">
|
||||
{translate(
|
||||
'auto.components.editor.DiffSectionBody.35d6afb5be',
|
||||
'Binary file changed'
|
||||
)}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{isBranchMode
|
||||
? translate("auto.components.editor.DiffSectionBody.7ce8436458", "Text diff is unavailable for this file in branch compare.")
|
||||
: translate("auto.components.editor.DiffSectionBody.72f71f52eb", "Text diff is unavailable for this file.")}
|
||||
? translate(
|
||||
'auto.components.editor.DiffSectionBody.7ce8436458',
|
||||
'Text diff is unavailable for this file in branch compare.'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.editor.DiffSectionBody.72f71f52eb',
|
||||
'Text diff is unavailable for this file.'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -56,7 +56,7 @@ export function DiffSectionHeader({
|
||||
console.error('Failed to copy diff path:', error)
|
||||
})
|
||||
}}
|
||||
title={translate("auto.components.editor.DiffSectionHeader.8915726e93", "Copy path")}
|
||||
title={translate('auto.components.editor.DiffSectionHeader.8915726e93', 'Copy path')}
|
||||
>
|
||||
{path}
|
||||
</span>
|
||||
|
||||
@@ -81,11 +81,14 @@ function FileLoadErrorView({
|
||||
<div className="flex max-w-xl items-start gap-3 rounded-md border border-border bg-background p-4">
|
||||
<AlertCircle className="mt-0.5 size-4 flex-shrink-0 text-destructive" />
|
||||
<div className="min-w-0">
|
||||
<div className="font-medium text-foreground">{translate("auto.components.editor.EditorContent.39f018b052", "Unable to load file")}</div>
|
||||
<div className="font-medium text-foreground">
|
||||
{translate('auto.components.editor.EditorContent.39f018b052', 'Unable to load file')}
|
||||
</div>
|
||||
<div className="mt-1 break-words">{message}</div>
|
||||
<Button type="button" variant="outline" size="sm" className="mt-3" onClick={onRetry}>
|
||||
<RefreshCw className="size-3.5" />
|
||||
{translate("auto.components.editor.EditorContent.2a512bb46a", "Retry")}</Button>
|
||||
{translate('auto.components.editor.EditorContent.2a512bb46a', 'Retry')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -253,8 +256,10 @@ export function EditorContent({
|
||||
conflictKind: entry.conflictKind,
|
||||
conflictStatus: entry.conflictStatus,
|
||||
conflictStatusSource: entry.conflictStatusSource,
|
||||
message:
|
||||
translate("auto.components.editor.EditorContent.8b1a605bae", "This file is in a conflict state, but no working-tree file is available to edit."),
|
||||
message: translate(
|
||||
'auto.components.editor.EditorContent.8b1a605bae',
|
||||
'This file is in a conflict state, but no working-tree file is available to edit.'
|
||||
),
|
||||
guidance: 'Resolve the conflict in Git or restore one side before reopening it.'
|
||||
}
|
||||
: {
|
||||
@@ -477,7 +482,8 @@ export function EditorContent({
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="flex h-full items-center justify-center text-sm text-muted-foreground">
|
||||
{translate("auto.components.editor.EditorContent.b2735221f5", "Loading...")}</div>
|
||||
{translate('auto.components.editor.EditorContent.b2735221f5', 'Loading...')}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -506,7 +512,11 @@ export function EditorContent({
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="flex h-full items-center justify-center text-sm text-muted-foreground">
|
||||
{translate("auto.components.editor.EditorContent.b9de81ba52", "Binary file — cannot display")}</div>
|
||||
{translate(
|
||||
'auto.components.editor.EditorContent.b9de81ba52',
|
||||
'Binary file — cannot display'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -650,7 +660,8 @@ export function EditorContent({
|
||||
if (!fc) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full text-muted-foreground text-sm">
|
||||
{translate("auto.components.editor.EditorContent.37a0e81fa6", "Loading preview...")}</div>
|
||||
{translate('auto.components.editor.EditorContent.37a0e81fa6', 'Loading preview...')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
if (fc.loadError) {
|
||||
@@ -661,7 +672,11 @@ export function EditorContent({
|
||||
if (fc.isBinary) {
|
||||
return (
|
||||
<div className="flex h-full items-center justify-center px-6 text-center text-sm text-muted-foreground">
|
||||
{translate("auto.components.editor.EditorContent.8608ce4cb1", "Markdown preview is unavailable for binary files.")}</div>
|
||||
{translate(
|
||||
'auto.components.editor.EditorContent.8608ce4cb1',
|
||||
'Markdown preview is unavailable for binary files.'
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
const previewSourceFileId = activeFile.markdownPreviewSourceFileId ?? activeFile.filePath
|
||||
@@ -694,7 +709,8 @@ export function EditorContent({
|
||||
if (!fc) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full text-muted-foreground text-sm">
|
||||
{translate("auto.components.editor.EditorContent.b2735221f5", "Loading...")}</div>
|
||||
{translate('auto.components.editor.EditorContent.b2735221f5', 'Loading...')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
if (fc.loadError) {
|
||||
@@ -710,7 +726,11 @@ export function EditorContent({
|
||||
}
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full text-muted-foreground text-sm">
|
||||
{translate("auto.components.editor.EditorContent.b9de81ba52", "Binary file — cannot display")}</div>
|
||||
{translate(
|
||||
'auto.components.editor.EditorContent.b9de81ba52',
|
||||
'Binary file — cannot display'
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
if (isChangesMode) {
|
||||
@@ -744,19 +764,19 @@ export function EditorContent({
|
||||
<div className="min-h-0 flex-1 relative">
|
||||
{isMarkdown ? (
|
||||
renderMarkdownContent(fc)
|
||||
) : isMermaid && mdViewMode === "rich" ? (
|
||||
) : isMermaid && mdViewMode === 'rich' ? (
|
||||
<MermaidViewer
|
||||
key={activeFile.id}
|
||||
content={editBuffers[activeFile.id] ?? fc.content}
|
||||
filePath={activeFile.filePath}
|
||||
/>
|
||||
) : isCsv && mdViewMode === "rich" ? (
|
||||
) : isCsv && mdViewMode === 'rich' ? (
|
||||
<CsvViewer
|
||||
key={activeFile.id}
|
||||
content={editBuffers[activeFile.id] ?? fc.content}
|
||||
filePath={activeFile.filePath}
|
||||
/>
|
||||
) : isNotebook && mdViewMode === "rich" ? (
|
||||
) : isNotebook && mdViewMode === 'rich' ? (
|
||||
<IpynbViewer
|
||||
key={activeFile.id}
|
||||
content={editBuffers[activeFile.id] ?? fc.content}
|
||||
@@ -781,7 +801,8 @@ export function EditorContent({
|
||||
if (!dc) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full text-muted-foreground text-sm">
|
||||
{translate("auto.components.editor.EditorContent.c88c73a0d3", "Loading diff...")}</div>
|
||||
{translate('auto.components.editor.EditorContent.c88c73a0d3', 'Loading diff...')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
const isEditable = activeFile.diffSource === 'unstaged'
|
||||
@@ -800,11 +821,19 @@ export function EditorContent({
|
||||
return (
|
||||
<div className="flex h-full items-center justify-center px-6 text-center">
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm font-medium text-foreground">{translate("auto.components.editor.EditorContent.78541e254e", "Binary file changed")}</div>
|
||||
<div className="text-sm font-medium text-foreground">
|
||||
{translate('auto.components.editor.EditorContent.78541e254e', 'Binary file changed')}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{activeFile.diffSource === "branch"
|
||||
? translate("auto.components.editor.EditorContent.3c6e71df22", "Text diff is unavailable for this file in branch compare.")
|
||||
: translate("auto.components.editor.EditorContent.8a0898ae4c", "Text diff is unavailable for this file.")}
|
||||
{activeFile.diffSource === 'branch'
|
||||
? translate(
|
||||
'auto.components.editor.EditorContent.3c6e71df22',
|
||||
'Text diff is unavailable for this file in branch compare.'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.editor.EditorContent.8a0898ae4c',
|
||||
'Text diff is unavailable for this file.'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -819,7 +848,11 @@ export function EditorContent({
|
||||
deletions simultaneously, so preview mode intentionally shows the
|
||||
modified side of the diff. Source mode remains available for the
|
||||
actual line-by-line comparison. */}
|
||||
{translate("auto.components.editor.EditorContent.9640d1d3db", "Previewing the modified version of this diff. Switch to source mode to inspect changes.")}</div>
|
||||
{translate(
|
||||
'auto.components.editor.EditorContent.9640d1d3db',
|
||||
'Previewing the modified version of this diff. Switch to source mode to inspect changes.'
|
||||
)}
|
||||
</div>
|
||||
<div className="min-h-0 flex-1">
|
||||
<MarkdownPreview
|
||||
key={viewStateScopeId}
|
||||
@@ -878,8 +911,10 @@ function FrontMatterBanner({ raw }: { raw: string }): React.JSX.Element {
|
||||
return (
|
||||
<div className="border-b border-border/60 bg-muted/40 px-3 py-2">
|
||||
<div className="mb-1 text-[10px] font-medium uppercase tracking-wider text-muted-foreground">
|
||||
{translate("auto.components.editor.EditorContent.e4b074749d", "Front Matter")}<span className="ml-2 font-normal normal-case tracking-normal opacity-70">
|
||||
{translate("auto.components.editor.EditorContent.56dba34e1a", "(edit in source mode)")}</span>
|
||||
{translate('auto.components.editor.EditorContent.e4b074749d', 'Front Matter')}
|
||||
<span className="ml-2 font-normal normal-case tracking-normal opacity-70">
|
||||
{translate('auto.components.editor.EditorContent.56dba34e1a', '(edit in source mode)')}
|
||||
</span>
|
||||
</div>
|
||||
<pre className="max-h-32 overflow-auto whitespace-pre-wrap text-xs text-muted-foreground font-mono scrollbar-editor">
|
||||
{inner}
|
||||
|
||||
@@ -39,8 +39,14 @@ export function EditorPanelMarkdownActionsMenu({
|
||||
<button
|
||||
type="button"
|
||||
className="p-1 rounded hover:bg-accent text-muted-foreground hover:text-foreground transition-colors flex-shrink-0"
|
||||
aria-label={translate("auto.components.editor.EditorPanelMarkdownActionsMenu.561251019a", "More actions")}
|
||||
title={translate("auto.components.editor.EditorPanelMarkdownActionsMenu.561251019a", "More actions")}
|
||||
aria-label={translate(
|
||||
'auto.components.editor.EditorPanelMarkdownActionsMenu.561251019a',
|
||||
'More actions'
|
||||
)}
|
||||
title={translate(
|
||||
'auto.components.editor.EditorPanelMarkdownActionsMenu.561251019a',
|
||||
'More actions'
|
||||
)}
|
||||
>
|
||||
<MoreHorizontal size={14} />
|
||||
</button>
|
||||
@@ -54,7 +60,15 @@ export function EditorPanelMarkdownActionsMenu({
|
||||
onToggleMarkdownFrontmatter()
|
||||
}}
|
||||
>
|
||||
{markdownFrontmatterVisible ? translate("auto.components.editor.EditorPanelMarkdownActionsMenu.10c39d58c1", "Hide front matter") : translate("auto.components.editor.EditorPanelMarkdownActionsMenu.8c8b7f5ff5", "Show front matter")}
|
||||
{markdownFrontmatterVisible
|
||||
? translate(
|
||||
'auto.components.editor.EditorPanelMarkdownActionsMenu.10c39d58c1',
|
||||
'Hide front matter'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.editor.EditorPanelMarkdownActionsMenu.8c8b7f5ff5',
|
||||
'Show front matter'
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
{hasViewModeToggle ? <DropdownMenuSeparator /> : null}
|
||||
</>
|
||||
@@ -67,7 +81,11 @@ export function EditorPanelMarkdownActionsMenu({
|
||||
disabled={mdViewMode === 'source'}
|
||||
onSelect={onExportMarkdownToPdf}
|
||||
>
|
||||
{translate("auto.components.editor.EditorPanelMarkdownActionsMenu.3e0ce48c24", "Export as PDF")}</DropdownMenuItem>
|
||||
{translate(
|
||||
'auto.components.editor.EditorPanelMarkdownActionsMenu.3e0ce48c24',
|
||||
'Export as PDF'
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
) : null}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
@@ -185,6 +185,7 @@ export function EditorPanelShell({
|
||||
function EditorLoadingFallback(): JSX.Element {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full text-muted-foreground text-sm">
|
||||
{translate("auto.components.editor.EditorPanelShell.e2c4dec350", "Loading editor...")}</div>
|
||||
{translate('auto.components.editor.EditorPanelShell.e2c4dec350', 'Loading editor...')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -27,29 +27,29 @@ type ViewModeMetadata = { label: string; icon: LucideIcon; title?: string }
|
||||
|
||||
const DEFAULT_VIEW_MODE_METADATA: Record<EditorToggleValue, ViewModeMetadata> = {
|
||||
source: {
|
||||
label: translate("auto.components.editor.EditorViewToggle.4d6ccb7ba6", "Source"),
|
||||
label: translate('auto.components.editor.EditorViewToggle.4d6ccb7ba6', 'Source'),
|
||||
icon: Code
|
||||
},
|
||||
rich: {
|
||||
label: translate("auto.components.editor.EditorViewToggle.aff15f94f5", "Rich Editor"),
|
||||
label: translate('auto.components.editor.EditorViewToggle.aff15f94f5', 'Rich Editor'),
|
||||
icon: Pencil
|
||||
},
|
||||
preview: {
|
||||
label: translate("auto.components.editor.EditorViewToggle.0d193dc03c", "Preview"),
|
||||
label: translate('auto.components.editor.EditorViewToggle.0d193dc03c', 'Preview'),
|
||||
icon: Eye
|
||||
},
|
||||
edit: {
|
||||
label: translate("auto.components.editor.EditorViewToggle.ac3bb87913", "Edit"),
|
||||
label: translate('auto.components.editor.EditorViewToggle.ac3bb87913', 'Edit'),
|
||||
icon: FileText
|
||||
},
|
||||
changes: {
|
||||
label: translate("auto.components.editor.EditorViewToggle.4837f3f578", "Changes"),
|
||||
label: translate('auto.components.editor.EditorViewToggle.4837f3f578', 'Changes'),
|
||||
icon: GitCompareArrows,
|
||||
// Why: "Changes" collides with the Source Control sidebar's "Branch
|
||||
// Changes" section, which diffs against the base ref. This toggle shows
|
||||
// uncommitted changes (working tree vs HEAD), so disambiguate in the
|
||||
// hover title without repeating the button label.
|
||||
title: translate("auto.components.editor.EditorViewToggle.167f45888c", "Uncommitted changes")
|
||||
title: translate('auto.components.editor.EditorViewToggle.167f45888c', 'Uncommitted changes')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,14 +58,14 @@ const DEFAULT_VIEW_MODE_METADATA: Record<EditorToggleValue, ViewModeMetadata> =
|
||||
// which we don't offer, so callers can override the per-mode presentation.
|
||||
export const CSV_VIEW_MODE_METADATA: Partial<Record<MarkdownViewMode, ViewModeMetadata>> = {
|
||||
rich: {
|
||||
label: translate("auto.components.editor.EditorViewToggle.e408aa9cd5", "Table"),
|
||||
label: translate('auto.components.editor.EditorViewToggle.e408aa9cd5', 'Table'),
|
||||
icon: TableIcon
|
||||
}
|
||||
}
|
||||
|
||||
export const NOTEBOOK_VIEW_MODE_METADATA: Partial<Record<MarkdownViewMode, ViewModeMetadata>> = {
|
||||
rich: {
|
||||
label: translate("auto.components.editor.EditorViewToggle.b3410cd5e0", "Notebook"),
|
||||
label: translate('auto.components.editor.EditorViewToggle.b3410cd5e0', 'Notebook'),
|
||||
icon: NotebookText
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,8 @@ function ImageDiffPane({
|
||||
isIntrinsicLayout ? 'min-h-32' : 'flex-1'
|
||||
)}
|
||||
>
|
||||
{translate("auto.components.editor.ImageDiffViewer.fb0ae4f3c0", "No preview")}</div>
|
||||
{translate('auto.components.editor.ImageDiffViewer.fb0ae4f3c0', 'No preview')}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -94,14 +95,14 @@ export default function ImageDiffViewer({
|
||||
style={gridRowStyle}
|
||||
>
|
||||
<ImageDiffPane
|
||||
label={translate("auto.components.editor.ImageDiffViewer.57aac3979a", "Original")}
|
||||
label={translate('auto.components.editor.ImageDiffViewer.57aac3979a', 'Original')}
|
||||
content={originalContent}
|
||||
filePath={filePath}
|
||||
mimeType={mimeType}
|
||||
layout={layout}
|
||||
/>
|
||||
<ImageDiffPane
|
||||
label={translate("auto.components.editor.ImageDiffViewer.a651be62b0", "Modified")}
|
||||
label={translate('auto.components.editor.ImageDiffViewer.a651be62b0', 'Modified')}
|
||||
content={modifiedContent}
|
||||
filePath={filePath}
|
||||
mimeType={mimeType}
|
||||
|
||||
@@ -219,7 +219,12 @@ export default function ImageViewer({
|
||||
)}
|
||||
>
|
||||
<ImageIcon size={40} />
|
||||
<div>{translate("auto.components.editor.ImageViewer.d9d2944855", "Failed to load file preview")}</div>
|
||||
<div>
|
||||
{translate(
|
||||
'auto.components.editor.ImageViewer.d9d2944855',
|
||||
'Failed to load file preview'
|
||||
)}
|
||||
</div>
|
||||
<div className="max-w-md break-all text-center text-xs">{filename}</div>
|
||||
</div>
|
||||
)
|
||||
@@ -233,7 +238,8 @@ export default function ImageViewer({
|
||||
isIntrinsicLayout ? 'min-h-64' : 'h-full'
|
||||
)}
|
||||
>
|
||||
{translate("auto.components.editor.ImageViewer.3ef9551ba2", "Loading preview...")}</div>
|
||||
{translate('auto.components.editor.ImageViewer.3ef9551ba2', 'Loading preview...')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -249,7 +255,7 @@ export default function ImageViewer({
|
||||
: 'flex-1 overflow-auto scrollbar-editor'
|
||||
)}
|
||||
onClick={openPopup}
|
||||
title={translate("auto.components.editor.ImageViewer.77bfc9b35a", "Open image in popup")}
|
||||
title={translate('auto.components.editor.ImageViewer.77bfc9b35a', 'Open image in popup')}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
@@ -299,7 +305,7 @@ export default function ImageViewer({
|
||||
applyInlineZoomChange((currentZoom) => currentZoom / IMAGE_VIEWER_ZOOM_STEP)
|
||||
}
|
||||
disabled={inlineZoom <= MIN_IMAGE_VIEWER_ZOOM}
|
||||
title={translate("auto.components.editor.ImageViewer.be27304574", "Zoom out")}
|
||||
title={translate('auto.components.editor.ImageViewer.be27304574', 'Zoom out')}
|
||||
>
|
||||
<ZoomOut size={14} />
|
||||
</button>
|
||||
@@ -308,7 +314,7 @@ export default function ImageViewer({
|
||||
className="rounded p-1 hover:bg-accent hover:text-foreground disabled:opacity-50"
|
||||
onClick={() => applyInlineZoomChange(() => 1)}
|
||||
disabled={inlineZoom === 1}
|
||||
title={translate("auto.components.editor.ImageViewer.6c89c73d9f", "Reset zoom")}
|
||||
title={translate('auto.components.editor.ImageViewer.6c89c73d9f', 'Reset zoom')}
|
||||
>
|
||||
<RotateCcw size={14} />
|
||||
</button>
|
||||
@@ -319,7 +325,7 @@ export default function ImageViewer({
|
||||
applyInlineZoomChange((currentZoom) => currentZoom * IMAGE_VIEWER_ZOOM_STEP)
|
||||
}
|
||||
disabled={inlineZoom >= MAX_IMAGE_VIEWER_ZOOM}
|
||||
title={translate("auto.components.editor.ImageViewer.3c9217f5a6", "Zoom in")}
|
||||
title={translate('auto.components.editor.ImageViewer.3c9217f5a6', 'Zoom in')}
|
||||
>
|
||||
<ZoomIn size={14} />
|
||||
</button>
|
||||
|
||||
@@ -33,7 +33,12 @@ export default function ImageViewerPopup({
|
||||
className="top-1/2 left-1/2 flex h-[80vh] w-[70vw] max-w-[70vw] -translate-x-1/2 -translate-y-1/2 flex-col gap-0 overflow-hidden border border-border/60 bg-background p-0 shadow-2xl sm:max-w-[70vw]"
|
||||
>
|
||||
<DialogTitle className="sr-only">{filename}</DialogTitle>
|
||||
<DialogDescription className="sr-only">{translate("auto.components.editor.ImageViewerPopup.9e27b2ecaf", "Full-size image preview")}</DialogDescription>
|
||||
<DialogDescription className="sr-only">
|
||||
{translate(
|
||||
'auto.components.editor.ImageViewerPopup.9e27b2ecaf',
|
||||
'Full-size image preview'
|
||||
)}
|
||||
</DialogDescription>
|
||||
<div className="flex shrink-0 items-center justify-between border-b border-border/60 bg-background/95 px-3 py-2">
|
||||
<div className="min-w-0 truncate text-sm font-medium text-foreground">{filename}</div>
|
||||
<button
|
||||
@@ -42,7 +47,7 @@ export default function ImageViewerPopup({
|
||||
onClick={() => onOpenChange(false)}
|
||||
>
|
||||
<X size={14} />
|
||||
<span>{translate("auto.components.editor.ImageViewerPopup.535f4e2b56", "Close")}</span>
|
||||
<span>{translate('auto.components.editor.ImageViewerPopup.535f4e2b56', 'Close')}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
@@ -63,7 +68,9 @@ export default function ImageViewerPopup({
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center justify-between border-t border-border/60 bg-background/95 px-3 py-2 text-xs text-muted-foreground">
|
||||
<div>{translate("auto.components.editor.ImageViewerPopup.0ef78475e7", "Press Esc to close")}</div>
|
||||
<div>
|
||||
{translate('auto.components.editor.ImageViewerPopup.0ef78475e7', 'Press Esc to close')}
|
||||
</div>
|
||||
<div className="tabular-nums">{zoomPercent}%</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
|
||||
@@ -181,29 +181,56 @@ function NotebookCellHeader({
|
||||
onChange={(event) => onKindChange(event.target.value as IpynbCellKind)}
|
||||
className="h-7 rounded-md border border-input bg-background px-2 text-xs text-foreground"
|
||||
>
|
||||
<option value="code">{translate("auto.components.editor.IpynbViewer.7005960d73", "Code")}</option>
|
||||
<option value="markdown">{translate("auto.components.editor.IpynbViewer.1833dbbc43", "Markdown")}</option>
|
||||
<option value="raw">{translate("auto.components.editor.IpynbViewer.3e4cbf15ea", "Raw")}</option>
|
||||
<option value="code">
|
||||
{translate('auto.components.editor.IpynbViewer.7005960d73', 'Code')}
|
||||
</option>
|
||||
<option value="markdown">
|
||||
{translate('auto.components.editor.IpynbViewer.1833dbbc43', 'Markdown')}
|
||||
</option>
|
||||
<option value="raw">
|
||||
{translate('auto.components.editor.IpynbViewer.3e4cbf15ea', 'Raw')}
|
||||
</option>
|
||||
</select>
|
||||
{cell.kind === "code" ? (
|
||||
<NotebookHeaderButton label={translate("auto.components.editor.IpynbViewer.859bf9fc21", "Run cell")} disabled={running} onClick={onRun}>
|
||||
{cell.kind === 'code' ? (
|
||||
<NotebookHeaderButton
|
||||
label={translate('auto.components.editor.IpynbViewer.859bf9fc21', 'Run cell')}
|
||||
disabled={running}
|
||||
onClick={onRun}
|
||||
>
|
||||
{running ? <Loader2 className="size-3.5 animate-spin" /> : <Play className="size-3.5" />}
|
||||
</NotebookHeaderButton>
|
||||
) : null}
|
||||
<NotebookHeaderButton label={translate("auto.components.editor.IpynbViewer.fd8ac707bc", "Move cell up")} disabled={!canMoveUp} onClick={onMoveUp}>
|
||||
<NotebookHeaderButton
|
||||
label={translate('auto.components.editor.IpynbViewer.fd8ac707bc', 'Move cell up')}
|
||||
disabled={!canMoveUp}
|
||||
onClick={onMoveUp}
|
||||
>
|
||||
<MoveUp className="size-3.5" />
|
||||
</NotebookHeaderButton>
|
||||
<NotebookHeaderButton label={translate("auto.components.editor.IpynbViewer.27e064e2db", "Move cell down")} disabled={!canMoveDown} onClick={onMoveDown}>
|
||||
<NotebookHeaderButton
|
||||
label={translate('auto.components.editor.IpynbViewer.27e064e2db', 'Move cell down')}
|
||||
disabled={!canMoveDown}
|
||||
onClick={onMoveDown}
|
||||
>
|
||||
<MoveDown className="size-3.5" />
|
||||
</NotebookHeaderButton>
|
||||
<NotebookHeaderButton label={translate("auto.components.editor.IpynbViewer.53b839b8a0", "Insert code cell above")} onClick={() => onInsertAbove('code')}>
|
||||
<NotebookHeaderButton
|
||||
label={translate('auto.components.editor.IpynbViewer.53b839b8a0', 'Insert code cell above')}
|
||||
onClick={() => onInsertAbove('code')}
|
||||
>
|
||||
<ArrowUpToLine className="size-3.5" />
|
||||
</NotebookHeaderButton>
|
||||
<NotebookHeaderButton label={translate("auto.components.editor.IpynbViewer.b4208cad7e", "Insert code cell below")} onClick={() => onInsertBelow('code')}>
|
||||
<NotebookHeaderButton
|
||||
label={translate('auto.components.editor.IpynbViewer.b4208cad7e', 'Insert code cell below')}
|
||||
onClick={() => onInsertBelow('code')}
|
||||
>
|
||||
<ArrowDownToLine className="size-3.5" />
|
||||
</NotebookHeaderButton>
|
||||
<NotebookHeaderButton
|
||||
label={translate("auto.components.editor.IpynbViewer.ffc1ac2699", "Insert markdown cell above")}
|
||||
label={translate(
|
||||
'auto.components.editor.IpynbViewer.ffc1ac2699',
|
||||
'Insert markdown cell above'
|
||||
)}
|
||||
onClick={() => onInsertAbove('markdown')}
|
||||
>
|
||||
<span className="relative size-4">
|
||||
@@ -212,7 +239,10 @@ function NotebookCellHeader({
|
||||
</span>
|
||||
</NotebookHeaderButton>
|
||||
<NotebookHeaderButton
|
||||
label={translate("auto.components.editor.IpynbViewer.b42f6a9547", "Insert markdown cell below")}
|
||||
label={translate(
|
||||
'auto.components.editor.IpynbViewer.b42f6a9547',
|
||||
'Insert markdown cell below'
|
||||
)}
|
||||
onClick={() => onInsertBelow('markdown')}
|
||||
>
|
||||
<span className="relative size-4">
|
||||
@@ -220,7 +250,10 @@ function NotebookCellHeader({
|
||||
<MoveDown className="absolute -bottom-0.5 -right-0.5 size-2.5" />
|
||||
</span>
|
||||
</NotebookHeaderButton>
|
||||
<NotebookHeaderButton label={translate("auto.components.editor.IpynbViewer.781abd6926", "Delete cell")} onClick={onDelete}>
|
||||
<NotebookHeaderButton
|
||||
label={translate('auto.components.editor.IpynbViewer.781abd6926', 'Delete cell')}
|
||||
onClick={onDelete}
|
||||
>
|
||||
<Trash2 className="size-3.5" />
|
||||
</NotebookHeaderButton>
|
||||
<span className="ml-auto font-mono">#{index + 1}</span>
|
||||
@@ -438,7 +471,7 @@ function OutputItem({ item }: { item: IpynbOutputItem }): React.JSX.Element | nu
|
||||
})
|
||||
return (
|
||||
<iframe
|
||||
title={translate("auto.components.editor.IpynbViewer.66a3f7d330", "Notebook HTML output")}
|
||||
title={translate('auto.components.editor.IpynbViewer.66a3f7d330', 'Notebook HTML output')}
|
||||
sandbox=""
|
||||
referrerPolicy="no-referrer"
|
||||
loading="lazy"
|
||||
@@ -730,7 +763,12 @@ export default function IpynbViewer({
|
||||
<div className="flex max-w-md items-start gap-3 rounded-md border border-border bg-background p-4">
|
||||
<AlertCircle className="mt-0.5 size-4 text-destructive" />
|
||||
<div>
|
||||
<div className="font-medium text-foreground">{translate("auto.components.editor.IpynbViewer.c1601b23b2", "Unable to render notebook")}</div>
|
||||
<div className="font-medium text-foreground">
|
||||
{translate(
|
||||
'auto.components.editor.IpynbViewer.c1601b23b2',
|
||||
'Unable to render notebook'
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-1">{parsed.error}</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -837,27 +875,35 @@ export default function IpynbViewer({
|
||||
>
|
||||
<div className="sticky top-0 z-10 flex items-center gap-3 border-b border-border/60 bg-background/95 px-4 py-2 text-xs text-muted-foreground backdrop-blur">
|
||||
<span className="font-medium text-foreground">{filePath.split(/[/\\]/).pop()}</span>
|
||||
<span>{notebook.cells.length} {translate("auto.components.editor.IpynbViewer.07e7d96612", "cells")}</span>
|
||||
<span>
|
||||
{notebook.cells.length}{' '}
|
||||
{translate('auto.components.editor.IpynbViewer.07e7d96612', 'cells')}
|
||||
</span>
|
||||
<span>{notebook.language}</span>
|
||||
{notebook.kernelName ? <span>{notebook.kernelName}</span> : null}
|
||||
{runError ? <span className="text-destructive">{runError}</span> : null}
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<NotebookHeaderButton
|
||||
label={translate("auto.components.editor.IpynbViewer.15ec40a735", "Save notebook")}
|
||||
label={translate('auto.components.editor.IpynbViewer.15ec40a735', 'Save notebook')}
|
||||
shortcutKeys={saveShortcutKeys}
|
||||
onClick={() => void saveNotebook()}
|
||||
>
|
||||
<Save className="size-3.5" />
|
||||
</NotebookHeaderButton>
|
||||
<span className="rounded-sm border border-border bg-muted px-1.5 py-0.5 font-medium text-muted-foreground">
|
||||
{translate("auto.components.editor.IpynbViewer.329764e9fc", "BETA")}</span>
|
||||
<span className="font-mono">{translate("auto.components.editor.IpynbViewer.8c3b21369a", "nbformat")}{notebook.nbformat}</span>
|
||||
{translate('auto.components.editor.IpynbViewer.329764e9fc', 'BETA')}
|
||||
</span>
|
||||
<span className="font-mono">
|
||||
{translate('auto.components.editor.IpynbViewer.8c3b21369a', 'nbformat')}
|
||||
{notebook.nbformat}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mx-auto flex max-w-[980px] flex-col gap-3 px-5 py-5">
|
||||
{notebook.cells.length === 0 ? (
|
||||
<div className="flex items-center justify-center rounded-md border border-border bg-background p-8 text-sm text-muted-foreground">
|
||||
{translate("auto.components.editor.IpynbViewer.d6f37a640b", "Empty notebook")}</div>
|
||||
{translate('auto.components.editor.IpynbViewer.d6f37a640b', 'Empty notebook')}
|
||||
</div>
|
||||
) : (
|
||||
notebook.cells.map((cell, index) => {
|
||||
const cellKey = getCellKey(cell, index)
|
||||
@@ -883,7 +929,7 @@ export default function IpynbViewer({
|
||||
onMoveDown={() => moveCell(index, 1)}
|
||||
onDelete={() => deleteCell(index)}
|
||||
/>
|
||||
{cell.kind === "markdown" ? (
|
||||
{cell.kind === 'markdown' ? (
|
||||
<div className="grid gap-0 lg:grid-cols-2">
|
||||
<EditableTextCell
|
||||
source={source}
|
||||
@@ -893,7 +939,7 @@ export default function IpynbViewer({
|
||||
<MarkdownCell source={source} />
|
||||
</div>
|
||||
</div>
|
||||
) : cell.kind === "code" ? (
|
||||
) : cell.kind === 'code' ? (
|
||||
<MemoizedCodeCell
|
||||
cell={cell}
|
||||
source={source}
|
||||
@@ -927,15 +973,23 @@ export default function IpynbViewer({
|
||||
>
|
||||
<DialogContent className="max-w-md sm:max-w-md" showCloseButton={false}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-sm">{translate("auto.components.editor.IpynbViewer.9e06ae5d36", "Run Notebook Code?")}</DialogTitle>
|
||||
<DialogTitle className="text-sm">
|
||||
{translate('auto.components.editor.IpynbViewer.9e06ae5d36', 'Run Notebook Code?')}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-xs">
|
||||
{translate("auto.components.editor.IpynbViewer.10ed04a685", "Notebook cells execute local Python on this machine from the notebook folder. Only run cells from files you trust.")}</DialogDescription>
|
||||
{translate(
|
||||
'auto.components.editor.IpynbViewer.10ed04a685',
|
||||
'Notebook cells execute local Python on this machine from the notebook folder. Only run cells from files you trust.'
|
||||
)}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter className="gap-2">
|
||||
<Button type="button" variant="outline" size="sm" onClick={cancelPendingRun}>
|
||||
{translate("auto.components.editor.IpynbViewer.7f0d7077c6", "Cancel")}</Button>
|
||||
{translate('auto.components.editor.IpynbViewer.7f0d7077c6', 'Cancel')}
|
||||
</Button>
|
||||
<Button type="button" size="sm" autoFocus onClick={confirmPendingRun}>
|
||||
{translate("auto.components.editor.IpynbViewer.859bf9fc21", "Run cell")}</Button>
|
||||
{translate('auto.components.editor.IpynbViewer.859bf9fc21', 'Run cell')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@@ -595,7 +595,7 @@ export default function MarkdownPreview({
|
||||
() => [
|
||||
{
|
||||
id: 'all',
|
||||
label: translate("auto.components.editor.MarkdownPreview.ddf087d12e", "All unsent notes"),
|
||||
label: translate('auto.components.editor.MarkdownPreview.ddf087d12e', 'All unsent notes'),
|
||||
notes: unsentMarkdownReviewNotes,
|
||||
prompt: unsentMarkdownReviewPrompt
|
||||
}
|
||||
@@ -1035,8 +1035,8 @@ export default function MarkdownPreview({
|
||||
<button
|
||||
type="button"
|
||||
className="markdown-annotation-add"
|
||||
aria-label={translate("auto.components.editor.MarkdownPreview.13f94d760c", "Add note")}
|
||||
title={translate("auto.components.editor.MarkdownPreview.13f94d760c", "Add note")}
|
||||
aria-label={translate('auto.components.editor.MarkdownPreview.13f94d760c', 'Add note')}
|
||||
title={translate('auto.components.editor.MarkdownPreview.13f94d760c', 'Add note')}
|
||||
onClick={(event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
@@ -1081,10 +1081,26 @@ export default function MarkdownPreview({
|
||||
type="button"
|
||||
className="orca-diff-comment-pill-btn"
|
||||
title={
|
||||
copiedReviewNoteId === comment.id ? translate("auto.components.editor.MarkdownPreview.94b520a96a", "Copied note") : translate("auto.components.editor.MarkdownPreview.f961e94057", "Copy note for agent")
|
||||
copiedReviewNoteId === comment.id
|
||||
? translate(
|
||||
'auto.components.editor.MarkdownPreview.94b520a96a',
|
||||
'Copied note'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.editor.MarkdownPreview.f961e94057',
|
||||
'Copy note for agent'
|
||||
)
|
||||
}
|
||||
aria-label={
|
||||
copiedReviewNoteId === comment.id ? translate("auto.components.editor.MarkdownPreview.94b520a96a", "Copied note") : translate("auto.components.editor.MarkdownPreview.f961e94057", "Copy note for agent")
|
||||
copiedReviewNoteId === comment.id
|
||||
? translate(
|
||||
'auto.components.editor.MarkdownPreview.94b520a96a',
|
||||
'Copied note'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.editor.MarkdownPreview.f961e94057',
|
||||
'Copy note for agent'
|
||||
)
|
||||
}
|
||||
onClick={(event) => {
|
||||
event.preventDefault()
|
||||
@@ -1264,7 +1280,11 @@ export default function MarkdownPreview({
|
||||
void window.api.shell.pathExists(classified.absolutePath).then((exists) => {
|
||||
if (!exists) {
|
||||
toast.error(
|
||||
translate("auto.components.editor.MarkdownPreview.6c043947ae", "File not found: {{value0}}", { value0: classified.relativePath ?? classified.absolutePath })
|
||||
translate(
|
||||
'auto.components.editor.MarkdownPreview.6c043947ae',
|
||||
'File not found: {{value0}}',
|
||||
{ value0: classified.relativePath ?? classified.absolutePath }
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
@@ -1356,11 +1376,23 @@ export default function MarkdownPreview({
|
||||
absolutePath
|
||||
)
|
||||
if (stats.isDirectory) {
|
||||
toast.error(translate("auto.components.editor.MarkdownPreview.759463a221", "Cannot open directory: {{value0}}", { value0: relativePath }))
|
||||
toast.error(
|
||||
translate(
|
||||
'auto.components.editor.MarkdownPreview.759463a221',
|
||||
'Cannot open directory: {{value0}}',
|
||||
{ value0: relativePath }
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
} catch {
|
||||
toast.error(translate("auto.components.editor.MarkdownPreview.6c043947ae", "File not found: {{value0}}", { value0: relativePath }))
|
||||
toast.error(
|
||||
translate(
|
||||
'auto.components.editor.MarkdownPreview.6c043947ae',
|
||||
'File not found: {{value0}}',
|
||||
{ value0: relativePath }
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1663,14 +1695,20 @@ export default function MarkdownPreview({
|
||||
rootRef.current?.focus()
|
||||
}
|
||||
}}
|
||||
placeholder={translate("auto.components.editor.MarkdownPreview.517aea303b", "Find in preview")}
|
||||
placeholder={translate(
|
||||
'auto.components.editor.MarkdownPreview.517aea303b',
|
||||
'Find in preview'
|
||||
)}
|
||||
className="markdown-preview-search-input h-7 !border-0 bg-transparent px-2 shadow-none focus-visible:!border-0 focus-visible:ring-0"
|
||||
aria-label={translate("auto.components.editor.MarkdownPreview.ec77985138", "Find in markdown preview")}
|
||||
aria-label={translate(
|
||||
'auto.components.editor.MarkdownPreview.ec77985138',
|
||||
'Find in markdown preview'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="markdown-preview-search-status">
|
||||
{query && matchCount === 0
|
||||
? translate("auto.components.editor.MarkdownPreview.c5dc92cfe3", "No results")
|
||||
? translate('auto.components.editor.MarkdownPreview.c5dc92cfe3', 'No results')
|
||||
: `${matchCount === 0 ? 0 : activeMatchIndex + 1}/${matchCount}`}
|
||||
</div>
|
||||
<Button
|
||||
@@ -1679,8 +1717,14 @@ export default function MarkdownPreview({
|
||||
size="icon-xs"
|
||||
onClick={() => moveToMatch(-1)}
|
||||
disabled={matchCount === 0}
|
||||
title={translate("auto.components.editor.MarkdownPreview.1febd97f5c", "Previous match")}
|
||||
aria-label={translate("auto.components.editor.MarkdownPreview.1febd97f5c", "Previous match")}
|
||||
title={translate(
|
||||
'auto.components.editor.MarkdownPreview.1febd97f5c',
|
||||
'Previous match'
|
||||
)}
|
||||
aria-label={translate(
|
||||
'auto.components.editor.MarkdownPreview.1febd97f5c',
|
||||
'Previous match'
|
||||
)}
|
||||
className="markdown-preview-search-button"
|
||||
>
|
||||
<ChevronUp size={14} />
|
||||
@@ -1691,8 +1735,11 @@ export default function MarkdownPreview({
|
||||
size="icon-xs"
|
||||
onClick={() => moveToMatch(1)}
|
||||
disabled={matchCount === 0}
|
||||
title={translate("auto.components.editor.MarkdownPreview.b42c41bd0d", "Next match")}
|
||||
aria-label={translate("auto.components.editor.MarkdownPreview.b42c41bd0d", "Next match")}
|
||||
title={translate('auto.components.editor.MarkdownPreview.b42c41bd0d', 'Next match')}
|
||||
aria-label={translate(
|
||||
'auto.components.editor.MarkdownPreview.b42c41bd0d',
|
||||
'Next match'
|
||||
)}
|
||||
className="markdown-preview-search-button"
|
||||
>
|
||||
<ChevronDown size={14} />
|
||||
@@ -1703,8 +1750,11 @@ export default function MarkdownPreview({
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
onClick={closeSearch}
|
||||
title={translate("auto.components.editor.MarkdownPreview.12052c639c", "Close search")}
|
||||
aria-label={translate("auto.components.editor.MarkdownPreview.12052c639c", "Close search")}
|
||||
title={translate('auto.components.editor.MarkdownPreview.12052c639c', 'Close search')}
|
||||
aria-label={translate(
|
||||
'auto.components.editor.MarkdownPreview.12052c639c',
|
||||
'Close search'
|
||||
)}
|
||||
className="markdown-preview-search-button"
|
||||
>
|
||||
<X size={14} />
|
||||
@@ -1723,11 +1773,19 @@ export default function MarkdownPreview({
|
||||
}
|
||||
}}
|
||||
disabled={markdownReviewNotes.length === 0}
|
||||
title={translate("auto.components.editor.MarkdownPreview.0f9969a159", "Jump to first review note")}
|
||||
aria-label={translate("auto.components.editor.MarkdownPreview.0f9969a159", "Jump to first review note")}
|
||||
title={translate(
|
||||
'auto.components.editor.MarkdownPreview.0f9969a159',
|
||||
'Jump to first review note'
|
||||
)}
|
||||
aria-label={translate(
|
||||
'auto.components.editor.MarkdownPreview.0f9969a159',
|
||||
'Jump to first review note'
|
||||
)}
|
||||
>
|
||||
<MessageSquare className="size-3.5" />
|
||||
<span>{translate("auto.components.editor.MarkdownPreview.322afab6ff", "Review notes")}</span>
|
||||
<span>
|
||||
{translate('auto.components.editor.MarkdownPreview.322afab6ff', 'Review notes')}
|
||||
</span>
|
||||
<span className="markdown-review-count">{markdownReviewNotes.length}</span>
|
||||
</button>
|
||||
<button
|
||||
@@ -1735,8 +1793,14 @@ export default function MarkdownPreview({
|
||||
className="markdown-review-icon-button"
|
||||
onClick={() => void handleCopyMarkdownReviewNotes()}
|
||||
disabled={markdownReviewNotes.length === 0}
|
||||
title={translate("auto.components.editor.MarkdownPreview.bb629de58a", "Copy notes for agent")}
|
||||
aria-label={translate("auto.components.editor.MarkdownPreview.bb629de58a", "Copy notes for agent")}
|
||||
title={translate(
|
||||
'auto.components.editor.MarkdownPreview.bb629de58a',
|
||||
'Copy notes for agent'
|
||||
)}
|
||||
aria-label={translate(
|
||||
'auto.components.editor.MarkdownPreview.bb629de58a',
|
||||
'Copy notes for agent'
|
||||
)}
|
||||
>
|
||||
{reviewNotesCopied ? <Check className="size-3.5" /> : <Copy className="size-3.5" />}
|
||||
</button>
|
||||
@@ -1759,7 +1823,8 @@ export default function MarkdownPreview({
|
||||
{frontMatter && frontmatterVisible ? (
|
||||
<div className="mb-4 rounded border border-border/60 bg-muted/40 px-3 py-2">
|
||||
<div className="mb-1 text-[10px] font-medium uppercase tracking-wider text-muted-foreground">
|
||||
{translate("auto.components.editor.MarkdownPreview.2b2b31382c", "Front Matter")}</div>
|
||||
{translate('auto.components.editor.MarkdownPreview.2b2b31382c', 'Front Matter')}
|
||||
</div>
|
||||
<pre className="max-h-48 overflow-auto whitespace-pre-wrap text-xs text-muted-foreground font-mono scrollbar-editor">
|
||||
{frontMatterInner}
|
||||
</pre>
|
||||
@@ -1820,7 +1885,7 @@ function MarkdownSingleNoteSendMenu({
|
||||
scopes={[
|
||||
{
|
||||
id: 'note',
|
||||
label: translate("auto.components.editor.MarkdownPreview.f37b98999e", "This note"),
|
||||
label: translate('auto.components.editor.MarkdownPreview.f37b98999e', 'This note'),
|
||||
notes: note.sentAt ? [] : [note],
|
||||
prompt: formatMarkdownReviewNotes([note], content)
|
||||
}
|
||||
@@ -1876,11 +1941,16 @@ function MarkdownAnnotationComposer({
|
||||
|
||||
return (
|
||||
<div className="markdown-annotation-composer" onClick={(event) => event.stopPropagation()}>
|
||||
<div className="orca-diff-comment-popover-label">{translate("auto.components.editor.MarkdownPreview.b1bfc04034", "Selected text")}</div>
|
||||
<div className="orca-diff-comment-popover-label">
|
||||
{translate('auto.components.editor.MarkdownPreview.b1bfc04034', 'Selected text')}
|
||||
</div>
|
||||
<textarea
|
||||
ref={focusTextareaRef}
|
||||
className="orca-diff-comment-popover-textarea"
|
||||
placeholder={translate("auto.components.editor.MarkdownPreview.d737791433", "Add note for the AI")}
|
||||
placeholder={translate(
|
||||
'auto.components.editor.MarkdownPreview.d737791433',
|
||||
'Add note for the AI'
|
||||
)}
|
||||
value={body}
|
||||
onChange={(event) => {
|
||||
setBody(event.target.value)
|
||||
@@ -1903,9 +1973,12 @@ function MarkdownAnnotationComposer({
|
||||
/>
|
||||
<div className="orca-diff-comment-popover-footer">
|
||||
<Button variant="ghost" size="sm" onClick={onCancel} disabled={submitting}>
|
||||
{translate("auto.components.editor.MarkdownPreview.e4683f70c4", "Cancel")}</Button>
|
||||
{translate('auto.components.editor.MarkdownPreview.e4683f70c4', 'Cancel')}
|
||||
</Button>
|
||||
<Button size="sm" onClick={() => void submit()} disabled={submitting || !trimmed}>
|
||||
{submitting ? translate("auto.components.editor.MarkdownPreview.d652c87c91", "Saving…") : translate("auto.components.editor.MarkdownPreview.13f94d760c", "Add note")}
|
||||
{submitting
|
||||
? translate('auto.components.editor.MarkdownPreview.d652c87c91', 'Saving…')
|
||||
: translate('auto.components.editor.MarkdownPreview.13f94d760c', 'Add note')}
|
||||
{!submitting && <CornerDownLeft className="ml-1 size-3 opacity-70" />}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -52,7 +52,19 @@ function MarkdownTocRow({
|
||||
<button
|
||||
type="button"
|
||||
className="markdown-toc-disclosure"
|
||||
aria-label={expanded ? translate("auto.components.editor.MarkdownTableOfContentsPanel.97ad46f11f", "Collapse {{value0}}", { value0: item.title }) : translate("auto.components.editor.MarkdownTableOfContentsPanel.65b036a6c8", "Expand {{value0}}", { value0: item.title })}
|
||||
aria-label={
|
||||
expanded
|
||||
? translate(
|
||||
'auto.components.editor.MarkdownTableOfContentsPanel.97ad46f11f',
|
||||
'Collapse {{value0}}',
|
||||
{ value0: item.title }
|
||||
)
|
||||
: translate(
|
||||
'auto.components.editor.MarkdownTableOfContentsPanel.65b036a6c8',
|
||||
'Expand {{value0}}',
|
||||
{ value0: item.title }
|
||||
)
|
||||
}
|
||||
aria-expanded={expanded}
|
||||
onClick={() => onToggleCollapsed(item.id)}
|
||||
>
|
||||
@@ -108,12 +120,30 @@ export function MarkdownTableOfContentsPanel({
|
||||
}
|
||||
|
||||
return (
|
||||
<aside className="markdown-toc-panel" aria-label={translate("auto.components.editor.MarkdownTableOfContentsPanel.27d0a9c49a", "Table of contents")}>
|
||||
<aside
|
||||
className="markdown-toc-panel"
|
||||
aria-label={translate(
|
||||
'auto.components.editor.MarkdownTableOfContentsPanel.27d0a9c49a',
|
||||
'Table of contents'
|
||||
)}
|
||||
>
|
||||
<div className="markdown-toc-header">
|
||||
<ListTree className="size-3.5 text-muted-foreground" />
|
||||
<span>{translate("auto.components.editor.MarkdownTableOfContentsPanel.06357eea60", "Table of Contents")}</span>
|
||||
<span>
|
||||
{translate(
|
||||
'auto.components.editor.MarkdownTableOfContentsPanel.06357eea60',
|
||||
'Table of Contents'
|
||||
)}
|
||||
</span>
|
||||
<div className="markdown-toc-header-actions">
|
||||
<div className="markdown-toc-level-controls" role="group" aria-label={translate("auto.components.editor.MarkdownTableOfContentsPanel.0dc7b2f05a", "Collapse by level")}>
|
||||
<div
|
||||
className="markdown-toc-level-controls"
|
||||
role="group"
|
||||
aria-label={translate(
|
||||
'auto.components.editor.MarkdownTableOfContentsPanel.0dc7b2f05a',
|
||||
'Collapse by level'
|
||||
)}
|
||||
>
|
||||
{TOC_LEVELS.map((level) => (
|
||||
<Button
|
||||
key={level}
|
||||
@@ -122,9 +152,29 @@ export function MarkdownTableOfContentsPanel({
|
||||
size="icon-xs"
|
||||
className="markdown-toc-level-button"
|
||||
aria-label={
|
||||
level === 3 ? translate("auto.components.editor.MarkdownTableOfContentsPanel.f3de856175", "Expand all heading levels") : translate("auto.components.editor.MarkdownTableOfContentsPanel.111e66b85d", "Collapse to heading level {{value0}}", { value0: level })
|
||||
level === 3
|
||||
? translate(
|
||||
'auto.components.editor.MarkdownTableOfContentsPanel.f3de856175',
|
||||
'Expand all heading levels'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.editor.MarkdownTableOfContentsPanel.111e66b85d',
|
||||
'Collapse to heading level {{value0}}',
|
||||
{ value0: level }
|
||||
)
|
||||
}
|
||||
title={
|
||||
level === 3
|
||||
? translate(
|
||||
'auto.components.editor.MarkdownTableOfContentsPanel.a5daadd68b',
|
||||
'Expand all'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.editor.MarkdownTableOfContentsPanel.4680a4b808',
|
||||
'Collapse to H{{value0}}',
|
||||
{ value0: level }
|
||||
)
|
||||
}
|
||||
title={level === 3 ? translate("auto.components.editor.MarkdownTableOfContentsPanel.a5daadd68b", "Expand all") : translate("auto.components.editor.MarkdownTableOfContentsPanel.4680a4b808", "Collapse to H{{value0}}", { value0: level })}
|
||||
onClick={() => collapseToLevel(level)}
|
||||
>
|
||||
H{level}
|
||||
@@ -135,8 +185,14 @@ export function MarkdownTableOfContentsPanel({
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
aria-label={translate("auto.components.editor.MarkdownTableOfContentsPanel.bbe8369097", "Close table of contents")}
|
||||
title={translate("auto.components.editor.MarkdownTableOfContentsPanel.bbe8369097", "Close table of contents")}
|
||||
aria-label={translate(
|
||||
'auto.components.editor.MarkdownTableOfContentsPanel.bbe8369097',
|
||||
'Close table of contents'
|
||||
)}
|
||||
title={translate(
|
||||
'auto.components.editor.MarkdownTableOfContentsPanel.bbe8369097',
|
||||
'Close table of contents'
|
||||
)}
|
||||
onClick={onClose}
|
||||
>
|
||||
<X className="size-3.5" />
|
||||
@@ -156,7 +212,12 @@ export function MarkdownTableOfContentsPanel({
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className="markdown-toc-empty">{translate("auto.components.editor.MarkdownTableOfContentsPanel.de3928b6e4", "No headings")}</div>
|
||||
<div className="markdown-toc-empty">
|
||||
{translate(
|
||||
'auto.components.editor.MarkdownTableOfContentsPanel.de3928b6e4',
|
||||
'No headings'
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
@@ -53,13 +53,26 @@ export function MarkdownTemplatePicker(): JSX.Element {
|
||||
resolveRequest({ type: 'cancel' })
|
||||
}
|
||||
}}
|
||||
title={translate("auto.components.editor.MarkdownTemplatePicker.1829437fce", "New Markdown")}
|
||||
description={translate("auto.components.editor.MarkdownTemplatePicker.7b458e0b7f", "Choose a Markdown template.")}
|
||||
title={translate('auto.components.editor.MarkdownTemplatePicker.1829437fce', 'New Markdown')}
|
||||
description={translate(
|
||||
'auto.components.editor.MarkdownTemplatePicker.7b458e0b7f',
|
||||
'Choose a Markdown template.'
|
||||
)}
|
||||
contentClassName="w-[520px]"
|
||||
>
|
||||
<CommandInput placeholder={translate("auto.components.editor.MarkdownTemplatePicker.22fd4890ad", "Search templates...")} />
|
||||
<CommandInput
|
||||
placeholder={translate(
|
||||
'auto.components.editor.MarkdownTemplatePicker.22fd4890ad',
|
||||
'Search templates...'
|
||||
)}
|
||||
/>
|
||||
<CommandList>
|
||||
<CommandEmpty>{translate("auto.components.editor.MarkdownTemplatePicker.df667919ca", "No matching templates.")}</CommandEmpty>
|
||||
<CommandEmpty>
|
||||
{translate(
|
||||
'auto.components.editor.MarkdownTemplatePicker.df667919ca',
|
||||
'No matching templates.'
|
||||
)}
|
||||
</CommandEmpty>
|
||||
<CommandGroup heading="New Document">
|
||||
<CommandItem
|
||||
value="blank markdown document"
|
||||
@@ -68,8 +81,18 @@ export function MarkdownTemplatePicker(): JSX.Element {
|
||||
>
|
||||
<FileText className="mt-0.5 size-4 text-muted-foreground" />
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="block truncate text-sm font-medium">{translate("auto.components.editor.MarkdownTemplatePicker.6e2e6c04ad", "Blank Markdown")}</span>
|
||||
<span className="block truncate text-xs text-muted-foreground">{translate("auto.components.editor.MarkdownTemplatePicker.22cd94426f", "untitled.md")}</span>
|
||||
<span className="block truncate text-sm font-medium">
|
||||
{translate(
|
||||
'auto.components.editor.MarkdownTemplatePicker.6e2e6c04ad',
|
||||
'Blank Markdown'
|
||||
)}
|
||||
</span>
|
||||
<span className="block truncate text-xs text-muted-foreground">
|
||||
{translate(
|
||||
'auto.components.editor.MarkdownTemplatePicker.22cd94426f',
|
||||
'untitled.md'
|
||||
)}
|
||||
</span>
|
||||
</span>
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
|
||||
@@ -84,7 +84,10 @@ export default function MermaidBlock({
|
||||
if (error) {
|
||||
return (
|
||||
<div className="mermaid-block">
|
||||
<div className="mermaid-error">{translate("auto.components.editor.MermaidBlock.dcc132e691", "Diagram error:")}{error}</div>
|
||||
<div className="mermaid-error">
|
||||
{translate('auto.components.editor.MermaidBlock.dcc132e691', 'Diagram error:')}
|
||||
{error}
|
||||
</div>
|
||||
<pre>
|
||||
<code>{content}</code>
|
||||
</pre>
|
||||
|
||||
@@ -382,7 +382,7 @@ export default function MonacoEditor({
|
||||
)
|
||||
const searchInFilesAction = editorInstance.addAction({
|
||||
id: 'orca.searchInFiles',
|
||||
label: translate("auto.components.editor.MonacoEditor.fd68ae03b3", "Search in Files"),
|
||||
label: translate('auto.components.editor.MonacoEditor.fd68ae03b3', 'Search in Files'),
|
||||
contextMenuGroupId: 'navigation',
|
||||
contextMenuOrder: 2,
|
||||
run: () => {
|
||||
@@ -751,8 +751,14 @@ export default function MonacoEditor({
|
||||
top: Math.max(4, selectionAnnotationTarget.top - 22),
|
||||
left: selectionAnnotationTarget.left ?? 4
|
||||
}}
|
||||
title={translate("auto.components.editor.MonacoEditor.68cb83f4a7", "Add note on selected text")}
|
||||
aria-label={translate("auto.components.editor.MonacoEditor.68cb83f4a7", "Add note on selected text")}
|
||||
title={translate(
|
||||
'auto.components.editor.MonacoEditor.68cb83f4a7',
|
||||
'Add note on selected text'
|
||||
)}
|
||||
aria-label={translate(
|
||||
'auto.components.editor.MonacoEditor.68cb83f4a7',
|
||||
'Add note on selected text'
|
||||
)}
|
||||
onMouseDown={(event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
||||
@@ -45,14 +45,22 @@ export function MonacoGutterContextMenu({
|
||||
onSelect={() => window.api.ui.writeClipboardText(formatPathLineReference(filePath, line))}
|
||||
>
|
||||
<Copy className="w-3.5 h-3.5 mr-1.5" />
|
||||
{translate("auto.components.editor.MonacoGutterContextMenu.4eaa991bde", "Copy Path to Line")}</DropdownMenuItem>
|
||||
{translate(
|
||||
'auto.components.editor.MonacoGutterContextMenu.4eaa991bde',
|
||||
'Copy Path to Line'
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onSelect={() =>
|
||||
window.api.ui.writeClipboardText(formatPathLineReference(relativePath, line))
|
||||
}
|
||||
>
|
||||
<Copy className="w-3.5 h-3.5 mr-1.5" />
|
||||
{translate("auto.components.editor.MonacoGutterContextMenu.2e0b1cdc05", "Copy Rel. Path to Line")}</DropdownMenuItem>
|
||||
{translate(
|
||||
'auto.components.editor.MonacoGutterContextMenu.2e0b1cdc05',
|
||||
'Copy Rel. Path to Line'
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onSelect={async () => {
|
||||
const state = useAppStore.getState()
|
||||
@@ -80,7 +88,11 @@ export function MonacoGutterContextMenu({
|
||||
}}
|
||||
>
|
||||
<ExternalLink className="w-3.5 h-3.5 mr-1.5" />
|
||||
{translate("auto.components.editor.MonacoGutterContextMenu.7b57b1b468", "Copy Remote URL")}</DropdownMenuItem>
|
||||
{translate(
|
||||
'auto.components.editor.MonacoGutterContextMenu.7b57b1b468',
|
||||
'Copy Remote URL'
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
|
||||
@@ -151,7 +151,15 @@ export function NotesSendMenu<TNote>({
|
||||
)}
|
||||
disabled={!hasDeliverableNotes}
|
||||
title={hasDeliverableNotes ? ENABLED_SEND_TOOLTIP : disabledTooltip}
|
||||
aria-label={triggerLabel ? translate("auto.components.editor.NotesSendMenu.433928cd9f", "Send {{value0}} to an agent", { value0: triggerLabel }) : ENABLED_SEND_TOOLTIP}
|
||||
aria-label={
|
||||
triggerLabel
|
||||
? translate(
|
||||
'auto.components.editor.NotesSendMenu.433928cd9f',
|
||||
'Send {{value0}} to an agent',
|
||||
{ value0: triggerLabel }
|
||||
)
|
||||
: ENABLED_SEND_TOOLTIP
|
||||
}
|
||||
onMouseDown={(event) => event.stopPropagation()}
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
@@ -184,7 +192,9 @@ export function NotesSendMenu<TNote>({
|
||||
>
|
||||
{scopes.length > 1 ? (
|
||||
<>
|
||||
<DropdownMenuLabel>{translate("auto.components.editor.NotesSendMenu.44dc5e60a6", "Send notes")}</DropdownMenuLabel>
|
||||
<DropdownMenuLabel>
|
||||
{translate('auto.components.editor.NotesSendMenu.44dc5e60a6', 'Send notes')}
|
||||
</DropdownMenuLabel>
|
||||
{scopes.map((scope) => (
|
||||
<DropdownMenuSub key={scope.id}>
|
||||
<DropdownMenuSubTrigger
|
||||
|
||||
@@ -125,12 +125,17 @@ export default function PdfFind({
|
||||
type="text"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder={translate("auto.components.editor.PdfFind.2fc3ba0ea8", "Find in page...")}
|
||||
placeholder={translate('auto.components.editor.PdfFind.2fc3ba0ea8', 'Find in page...')}
|
||||
className="min-w-0 flex-1 border-none bg-transparent text-sm text-white outline-none placeholder:text-zinc-500"
|
||||
/>
|
||||
{query ? (
|
||||
<span className="shrink-0 text-xs text-zinc-400">
|
||||
{totalMatches > 0 ? translate("auto.components.editor.PdfFind.db56fcd6d2", "{{value0}} of {{value1}}", { value0: activeMatch, value1: totalMatches }) : translate("auto.components.editor.PdfFind.d080ab37d6", "No matches")}
|
||||
{totalMatches > 0
|
||||
? translate('auto.components.editor.PdfFind.db56fcd6d2', '{{value0}} of {{value1}}', {
|
||||
value0: activeMatch,
|
||||
value1: totalMatches
|
||||
})
|
||||
: translate('auto.components.editor.PdfFind.d080ab37d6', 'No matches')}
|
||||
</span>
|
||||
) : null}
|
||||
<div className="mx-0.5 h-4 w-px bg-zinc-700" />
|
||||
@@ -140,7 +145,7 @@ export default function PdfFind({
|
||||
size="icon-xs"
|
||||
onClick={findPrevious}
|
||||
className="flex size-6 shrink-0 items-center justify-center rounded text-zinc-400 hover:text-zinc-200"
|
||||
title={translate("auto.components.editor.PdfFind.30de726ad0", "Previous match")}
|
||||
title={translate('auto.components.editor.PdfFind.30de726ad0', 'Previous match')}
|
||||
>
|
||||
<ChevronUp size={14} />
|
||||
</Button>
|
||||
@@ -150,7 +155,7 @@ export default function PdfFind({
|
||||
size="icon-xs"
|
||||
onClick={findNext}
|
||||
className="flex size-6 shrink-0 items-center justify-center rounded text-zinc-400 hover:text-zinc-200"
|
||||
title={translate("auto.components.editor.PdfFind.eeba2547a1", "Next match")}
|
||||
title={translate('auto.components.editor.PdfFind.eeba2547a1', 'Next match')}
|
||||
>
|
||||
<ChevronDown size={14} />
|
||||
</Button>
|
||||
@@ -161,7 +166,7 @@ export default function PdfFind({
|
||||
size="icon-xs"
|
||||
onClick={onClose}
|
||||
className="flex size-6 shrink-0 items-center justify-center rounded text-zinc-400 hover:text-zinc-200"
|
||||
title={translate("auto.components.editor.PdfFind.cd65b1d6b0", "Close")}
|
||||
title={translate('auto.components.editor.PdfFind.cd65b1d6b0', 'Close')}
|
||||
>
|
||||
<X size={14} />
|
||||
</Button>
|
||||
|
||||
@@ -216,7 +216,7 @@ export default function PdfViewer({ content, filePath }: PdfViewerProps): JSX.El
|
||||
<span className="min-w-0 truncate" title={filename}>
|
||||
{filename}
|
||||
</span>
|
||||
<span>{translate("auto.components.editor.PdfViewer.3e98d500d2", "PDF preview")}</span>
|
||||
<span>{translate('auto.components.editor.PdfViewer.3e98d500d2', 'PDF preview')}</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -252,7 +252,7 @@ export default function PdfViewer({ content, filePath }: PdfViewerProps): JSX.El
|
||||
className="rounded p-1 hover:bg-accent hover:text-foreground disabled:opacity-50"
|
||||
onClick={zoomOut}
|
||||
disabled={scale <= MIN_SCALE}
|
||||
title={translate("auto.components.editor.PdfViewer.fa5d096b00", "Zoom out")}
|
||||
title={translate('auto.components.editor.PdfViewer.fa5d096b00', 'Zoom out')}
|
||||
>
|
||||
<ZoomOut size={14} />
|
||||
</button>
|
||||
@@ -260,7 +260,7 @@ export default function PdfViewer({ content, filePath }: PdfViewerProps): JSX.El
|
||||
type="button"
|
||||
className="rounded p-1 hover:bg-accent hover:text-foreground"
|
||||
onClick={zoomReset}
|
||||
title={translate("auto.components.editor.PdfViewer.c0119616d6", "Fit to width")}
|
||||
title={translate('auto.components.editor.PdfViewer.c0119616d6', 'Fit to width')}
|
||||
>
|
||||
<RotateCcw size={14} />
|
||||
</button>
|
||||
@@ -269,7 +269,7 @@ export default function PdfViewer({ content, filePath }: PdfViewerProps): JSX.El
|
||||
className="rounded p-1 hover:bg-accent hover:text-foreground disabled:opacity-50"
|
||||
onClick={zoomIn}
|
||||
disabled={scale >= MAX_SCALE}
|
||||
title={translate("auto.components.editor.PdfViewer.2b6eb1ccd6", "Zoom in")}
|
||||
title={translate('auto.components.editor.PdfViewer.2b6eb1ccd6', 'Zoom in')}
|
||||
>
|
||||
<ZoomIn size={14} />
|
||||
</button>
|
||||
@@ -279,14 +279,18 @@ export default function PdfViewer({ content, filePath }: PdfViewerProps): JSX.El
|
||||
type="button"
|
||||
className="rounded p-1 hover:bg-accent hover:text-foreground"
|
||||
onClick={() => setFindOpen(true)}
|
||||
title={translate("auto.components.editor.PdfViewer.069ff59932", "Find in PDF ({{value0}})", { value0: findShortcutLabel })}
|
||||
title={translate(
|
||||
'auto.components.editor.PdfViewer.069ff59932',
|
||||
'Find in PDF ({{value0}})',
|
||||
{ value0: findShortcutLabel }
|
||||
)}
|
||||
>
|
||||
<Search size={14} />
|
||||
</button>
|
||||
<span className="min-w-0 truncate" title={filename}>
|
||||
{filename}
|
||||
</span>
|
||||
<span>{translate("auto.components.editor.PdfViewer.3e98d500d2", "PDF preview")}</span>
|
||||
<span>{translate('auto.components.editor.PdfViewer.3e98d500d2', 'PDF preview')}</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -38,19 +38,34 @@ export function ReviewNotesSendMenuContent({
|
||||
if (!hasPrompt || !canSendToActiveAgent) {
|
||||
return
|
||||
}
|
||||
const pending = toast.loading(translate("auto.components.editor.ReviewNotesSendMenuContent.50f7e753ea", "Sending notes to active agent..."))
|
||||
const pending = toast.loading(
|
||||
translate(
|
||||
'auto.components.editor.ReviewNotesSendMenuContent.50f7e753ea',
|
||||
'Sending notes to active agent...'
|
||||
)
|
||||
)
|
||||
void sendNotesToActiveAgentSession({ worktreeId, prompt })
|
||||
.then((result) => {
|
||||
if (result.status === 'sent') {
|
||||
onPromptDelivered?.()
|
||||
toast.success(translate("auto.components.editor.ReviewNotesSendMenuContent.bb9c69a0c9", "Notes sent to active agent."))
|
||||
toast.success(
|
||||
translate(
|
||||
'auto.components.editor.ReviewNotesSendMenuContent.bb9c69a0c9',
|
||||
'Notes sent to active agent.'
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
toast.message(activeAgentNotesSendFailureMessage(result.status))
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to send notes to active agent:', error)
|
||||
toast.error(translate("auto.components.editor.ReviewNotesSendMenuContent.f5096c6e4e", "Could not send notes to the active agent."))
|
||||
toast.error(
|
||||
translate(
|
||||
'auto.components.editor.ReviewNotesSendMenuContent.f5096c6e4e',
|
||||
'Could not send notes to the active agent.'
|
||||
)
|
||||
)
|
||||
})
|
||||
.finally(() => {
|
||||
toast.dismiss(pending)
|
||||
@@ -59,16 +74,24 @@ export function ReviewNotesSendMenuContent({
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuLabel>{translate("auto.components.editor.ReviewNotesSendMenuContent.03378aea75", "Send notes to")}</DropdownMenuLabel>
|
||||
<DropdownMenuLabel>
|
||||
{translate('auto.components.editor.ReviewNotesSendMenuContent.03378aea75', 'Send notes to')}
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuItem
|
||||
disabled={!hasPrompt || !canSendToActiveAgent}
|
||||
onSelect={sendToActiveAgent}
|
||||
className="gap-2 rounded-[7px] px-2 py-1.5 text-[12px] leading-5 font-medium"
|
||||
>
|
||||
<SquareTerminal className="size-3.5" />
|
||||
{translate("auto.components.editor.ReviewNotesSendMenuContent.e84705f223", "Active agent session")}</DropdownMenuItem>
|
||||
{translate(
|
||||
'auto.components.editor.ReviewNotesSendMenuContent.e84705f223',
|
||||
'Active agent session'
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuLabel>{translate("auto.components.editor.ReviewNotesSendMenuContent.a49800405b", "New agent")}</DropdownMenuLabel>
|
||||
<DropdownMenuLabel>
|
||||
{translate('auto.components.editor.ReviewNotesSendMenuContent.a49800405b', 'New agent')}
|
||||
</DropdownMenuLabel>
|
||||
<QuickLaunchAgentMenuItems
|
||||
worktreeId={worktreeId}
|
||||
groupId={groupId}
|
||||
|
||||
@@ -30,8 +30,14 @@ export function RichMarkdownAnnotationOverlay({
|
||||
top: target.buttonTop ?? 56,
|
||||
left: target.buttonLeft ?? 16
|
||||
}}
|
||||
title={translate("auto.components.editor.RichMarkdownAnnotationOverlay.6f2f3a6001", "Add review note")}
|
||||
aria-label={translate("auto.components.editor.RichMarkdownAnnotationOverlay.6f2f3a6001", "Add review note")}
|
||||
title={translate(
|
||||
'auto.components.editor.RichMarkdownAnnotationOverlay.6f2f3a6001',
|
||||
'Add review note'
|
||||
)}
|
||||
aria-label={translate(
|
||||
'auto.components.editor.RichMarkdownAnnotationOverlay.6f2f3a6001',
|
||||
'Add review note'
|
||||
)}
|
||||
onMouseDown={(event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
@@ -56,7 +62,10 @@ export function RichMarkdownAnnotationOverlay({
|
||||
}
|
||||
top={popover.top}
|
||||
left={popover.left}
|
||||
title={translate("auto.components.editor.RichMarkdownAnnotationOverlay.069b5677b8", "Selected text")}
|
||||
title={translate(
|
||||
'auto.components.editor.RichMarkdownAnnotationOverlay.069b5677b8',
|
||||
'Selected text'
|
||||
)}
|
||||
onCancel={onCancelPopover}
|
||||
onSubmit={onSubmit}
|
||||
/>
|
||||
|
||||
@@ -12,31 +12,103 @@ import { translate } from '@/i18n/i18n'
|
||||
* this list is just for quick picking in the UI.
|
||||
*/
|
||||
const LANGUAGES = [
|
||||
{ value: '', label: translate("auto.components.editor.RichMarkdownCodeBlock.13822cdfda", "Plain text") },
|
||||
{ value: 'bash', label: translate("auto.components.editor.RichMarkdownCodeBlock.4227cf50fe", "Bash") },
|
||||
{
|
||||
value: '',
|
||||
label: translate('auto.components.editor.RichMarkdownCodeBlock.13822cdfda', 'Plain text')
|
||||
},
|
||||
{
|
||||
value: 'bash',
|
||||
label: translate('auto.components.editor.RichMarkdownCodeBlock.4227cf50fe', 'Bash')
|
||||
},
|
||||
{ value: 'c', label: 'C' },
|
||||
{ value: 'cpp', label: translate("auto.components.editor.RichMarkdownCodeBlock.4daed43ae3", "C++") },
|
||||
{ value: 'css', label: translate("auto.components.editor.RichMarkdownCodeBlock.026653f21f", "CSS") },
|
||||
{ value: 'diff', label: translate("auto.components.editor.RichMarkdownCodeBlock.bf6ee5caaa", "Diff") },
|
||||
{ value: 'go', label: translate("auto.components.editor.RichMarkdownCodeBlock.edfcc64182", "Go") },
|
||||
{ value: 'graphql', label: translate("auto.components.editor.RichMarkdownCodeBlock.706fd85738", "GraphQL") },
|
||||
{ value: 'html', label: translate("auto.components.editor.RichMarkdownCodeBlock.8c4a3fa02d", "HTML") },
|
||||
{ value: 'java', label: translate("auto.components.editor.RichMarkdownCodeBlock.36536ad539", "Java") },
|
||||
{ value: 'javascript', label: translate("auto.components.editor.RichMarkdownCodeBlock.a209c57063", "JavaScript") },
|
||||
{ value: 'json', label: translate("auto.components.editor.RichMarkdownCodeBlock.78eba32de4", "JSON") },
|
||||
{ value: 'kotlin', label: translate("auto.components.editor.RichMarkdownCodeBlock.bcb236e2d8", "Kotlin") },
|
||||
{ value: 'markdown', label: translate("auto.components.editor.RichMarkdownCodeBlock.983b9576b4", "Markdown") },
|
||||
{ value: 'mermaid', label: translate("auto.components.editor.RichMarkdownCodeBlock.89d6cc14fb", "Mermaid") },
|
||||
{ value: 'python', label: translate("auto.components.editor.RichMarkdownCodeBlock.2391f9cda9", "Python") },
|
||||
{ value: 'ruby', label: translate("auto.components.editor.RichMarkdownCodeBlock.96182a2f64", "Ruby") },
|
||||
{ value: 'rust', label: translate("auto.components.editor.RichMarkdownCodeBlock.e72e6b03f4", "Rust") },
|
||||
{ value: 'scss', label: translate("auto.components.editor.RichMarkdownCodeBlock.5af8251002", "SCSS") },
|
||||
{ value: 'shell', label: translate("auto.components.editor.RichMarkdownCodeBlock.d01f55be57", "Shell") },
|
||||
{ value: 'sql', label: translate("auto.components.editor.RichMarkdownCodeBlock.3009f722b9", "SQL") },
|
||||
{ value: 'swift', label: translate("auto.components.editor.RichMarkdownCodeBlock.9e384d48dc", "Swift") },
|
||||
{ value: 'typescript', label: translate("auto.components.editor.RichMarkdownCodeBlock.88d777bc07", "TypeScript") },
|
||||
{ value: 'xml', label: translate("auto.components.editor.RichMarkdownCodeBlock.5ef5605cb7", "XML") },
|
||||
{ value: 'yaml', label: translate("auto.components.editor.RichMarkdownCodeBlock.74eab1d9b2", "YAML") }
|
||||
{
|
||||
value: 'cpp',
|
||||
label: translate('auto.components.editor.RichMarkdownCodeBlock.4daed43ae3', 'C++')
|
||||
},
|
||||
{
|
||||
value: 'css',
|
||||
label: translate('auto.components.editor.RichMarkdownCodeBlock.026653f21f', 'CSS')
|
||||
},
|
||||
{
|
||||
value: 'diff',
|
||||
label: translate('auto.components.editor.RichMarkdownCodeBlock.bf6ee5caaa', 'Diff')
|
||||
},
|
||||
{
|
||||
value: 'go',
|
||||
label: translate('auto.components.editor.RichMarkdownCodeBlock.edfcc64182', 'Go')
|
||||
},
|
||||
{
|
||||
value: 'graphql',
|
||||
label: translate('auto.components.editor.RichMarkdownCodeBlock.706fd85738', 'GraphQL')
|
||||
},
|
||||
{
|
||||
value: 'html',
|
||||
label: translate('auto.components.editor.RichMarkdownCodeBlock.8c4a3fa02d', 'HTML')
|
||||
},
|
||||
{
|
||||
value: 'java',
|
||||
label: translate('auto.components.editor.RichMarkdownCodeBlock.36536ad539', 'Java')
|
||||
},
|
||||
{
|
||||
value: 'javascript',
|
||||
label: translate('auto.components.editor.RichMarkdownCodeBlock.a209c57063', 'JavaScript')
|
||||
},
|
||||
{
|
||||
value: 'json',
|
||||
label: translate('auto.components.editor.RichMarkdownCodeBlock.78eba32de4', 'JSON')
|
||||
},
|
||||
{
|
||||
value: 'kotlin',
|
||||
label: translate('auto.components.editor.RichMarkdownCodeBlock.bcb236e2d8', 'Kotlin')
|
||||
},
|
||||
{
|
||||
value: 'markdown',
|
||||
label: translate('auto.components.editor.RichMarkdownCodeBlock.983b9576b4', 'Markdown')
|
||||
},
|
||||
{
|
||||
value: 'mermaid',
|
||||
label: translate('auto.components.editor.RichMarkdownCodeBlock.89d6cc14fb', 'Mermaid')
|
||||
},
|
||||
{
|
||||
value: 'python',
|
||||
label: translate('auto.components.editor.RichMarkdownCodeBlock.2391f9cda9', 'Python')
|
||||
},
|
||||
{
|
||||
value: 'ruby',
|
||||
label: translate('auto.components.editor.RichMarkdownCodeBlock.96182a2f64', 'Ruby')
|
||||
},
|
||||
{
|
||||
value: 'rust',
|
||||
label: translate('auto.components.editor.RichMarkdownCodeBlock.e72e6b03f4', 'Rust')
|
||||
},
|
||||
{
|
||||
value: 'scss',
|
||||
label: translate('auto.components.editor.RichMarkdownCodeBlock.5af8251002', 'SCSS')
|
||||
},
|
||||
{
|
||||
value: 'shell',
|
||||
label: translate('auto.components.editor.RichMarkdownCodeBlock.d01f55be57', 'Shell')
|
||||
},
|
||||
{
|
||||
value: 'sql',
|
||||
label: translate('auto.components.editor.RichMarkdownCodeBlock.3009f722b9', 'SQL')
|
||||
},
|
||||
{
|
||||
value: 'swift',
|
||||
label: translate('auto.components.editor.RichMarkdownCodeBlock.9e384d48dc', 'Swift')
|
||||
},
|
||||
{
|
||||
value: 'typescript',
|
||||
label: translate('auto.components.editor.RichMarkdownCodeBlock.88d777bc07', 'TypeScript')
|
||||
},
|
||||
{
|
||||
value: 'xml',
|
||||
label: translate('auto.components.editor.RichMarkdownCodeBlock.5ef5605cb7', 'XML')
|
||||
},
|
||||
{
|
||||
value: 'yaml',
|
||||
label: translate('auto.components.editor.RichMarkdownCodeBlock.74eab1d9b2', 'YAML')
|
||||
}
|
||||
]
|
||||
|
||||
export function RichMarkdownCodeBlock({
|
||||
@@ -128,13 +200,18 @@ export function RichMarkdownCodeBlock({
|
||||
className="code-block-copy-btn"
|
||||
contentEditable={false}
|
||||
onClick={handleCopy}
|
||||
aria-label={translate("auto.components.editor.RichMarkdownCodeBlock.c72beafc0f", "Copy code")}
|
||||
title={translate("auto.components.editor.RichMarkdownCodeBlock.c72beafc0f", "Copy code")}
|
||||
aria-label={translate(
|
||||
'auto.components.editor.RichMarkdownCodeBlock.c72beafc0f',
|
||||
'Copy code'
|
||||
)}
|
||||
title={translate('auto.components.editor.RichMarkdownCodeBlock.c72beafc0f', 'Copy code')}
|
||||
>
|
||||
{copied ? (
|
||||
<>
|
||||
<Check size={14} />
|
||||
<span className="code-block-copy-label">{translate("auto.components.editor.RichMarkdownCodeBlock.232d9ed853", "Copied")}</span>
|
||||
<span className="code-block-copy-label">
|
||||
{translate('auto.components.editor.RichMarkdownCodeBlock.232d9ed853', 'Copied')}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<Copy size={14} />
|
||||
|
||||
@@ -26,10 +26,18 @@ export function RichMarkdownDocLinkMenu({
|
||||
className="rich-markdown-doc-link-menu"
|
||||
style={{ left: menu.left, top: menu.top }}
|
||||
role="listbox"
|
||||
aria-label={translate("auto.components.editor.RichMarkdownDocLinkMenu.0e8489bc11", "Markdown document links")}
|
||||
aria-label={translate(
|
||||
'auto.components.editor.RichMarkdownDocLinkMenu.0e8489bc11',
|
||||
'Markdown document links'
|
||||
)}
|
||||
>
|
||||
{rows.length === 0 ? (
|
||||
<div className="rich-markdown-doc-link-item is-empty">{translate("auto.components.editor.RichMarkdownDocLinkMenu.63ced7cb9b", "No documents found")}</div>
|
||||
<div className="rich-markdown-doc-link-item is-empty">
|
||||
{translate(
|
||||
'auto.components.editor.RichMarkdownDocLinkMenu.63ced7cb9b',
|
||||
'No documents found'
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
rows.map((row, index) => {
|
||||
const rowKey = row.kind === 'document' ? row.document.filePath : row.id
|
||||
@@ -60,11 +68,18 @@ export function RichMarkdownDocLinkMenu({
|
||||
)}
|
||||
{overflow ? (
|
||||
<div className="rich-markdown-doc-link-footer">
|
||||
{translate("auto.components.editor.RichMarkdownDocLinkMenu.2aaf7d9678", "Showing")}{rows.length} {translate("auto.components.editor.RichMarkdownDocLinkMenu.90c5f0e1e4", "of")}{totalMatches}
|
||||
{translate('auto.components.editor.RichMarkdownDocLinkMenu.2aaf7d9678', 'Showing')}
|
||||
{rows.length}{' '}
|
||||
{translate('auto.components.editor.RichMarkdownDocLinkMenu.90c5f0e1e4', 'of')}
|
||||
{totalMatches}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="rich-markdown-doc-link-hint">
|
||||
{translate("auto.components.editor.RichMarkdownDocLinkMenu.e17b987473", "↑↓ navigate ↵ select esc dismiss")}</div>
|
||||
{translate(
|
||||
'auto.components.editor.RichMarkdownDocLinkMenu.e17b987473',
|
||||
'↑↓ navigate ↵ select esc dismiss'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -55,14 +55,23 @@ export class RichMarkdownErrorBoundary extends React.Component<Props, State> {
|
||||
return (
|
||||
<div className="flex h-full min-h-0 flex-col items-center justify-center gap-3 px-6 text-center text-sm text-muted-foreground">
|
||||
<div>
|
||||
{translate("auto.components.editor.RichMarkdownErrorBoundary.dfdf1cacd4", "The rich markdown editor hit an unexpected error and was reset to keep the rest of Orca responsive.")}</div>
|
||||
{translate(
|
||||
'auto.components.editor.RichMarkdownErrorBoundary.dfdf1cacd4',
|
||||
'The rich markdown editor hit an unexpected error and was reset to keep the rest of Orca responsive.'
|
||||
)}
|
||||
</div>
|
||||
<div className="text-xs opacity-70">
|
||||
{translate("auto.components.editor.RichMarkdownErrorBoundary.4a5de9f2f0", "Switch to source mode, or click retry to reload the rich view.")}</div>
|
||||
{translate(
|
||||
'auto.components.editor.RichMarkdownErrorBoundary.4a5de9f2f0',
|
||||
'Switch to source mode, or click retry to reload the rich view.'
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
className="rounded border border-border/60 px-3 py-1 text-xs hover:bg-accent"
|
||||
onClick={this.handleReset}
|
||||
>
|
||||
{translate("auto.components.editor.RichMarkdownErrorBoundary.aad0998127", "Retry")}</button>
|
||||
{translate('auto.components.editor.RichMarkdownErrorBoundary.aad0998127', 'Retry')}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -81,7 +81,10 @@ function LinkEditInput({
|
||||
onCancel()
|
||||
}
|
||||
}}
|
||||
placeholder={translate("auto.components.editor.RichMarkdownLinkBubble.7b0b945fdc", "Paste or type a link…")}
|
||||
placeholder={translate(
|
||||
'auto.components.editor.RichMarkdownLinkBubble.7b0b945fdc',
|
||||
'Paste or type a link…'
|
||||
)}
|
||||
className="rich-markdown-link-input"
|
||||
/>
|
||||
)
|
||||
@@ -130,7 +133,10 @@ export function RichMarkdownLinkBubble({
|
||||
type="button"
|
||||
className="rich-markdown-link-button"
|
||||
onClick={onOpen}
|
||||
title={translate("auto.components.editor.RichMarkdownLinkBubble.bfc813e909", "Open link")}
|
||||
title={translate(
|
||||
'auto.components.editor.RichMarkdownLinkBubble.bfc813e909',
|
||||
'Open link'
|
||||
)}
|
||||
>
|
||||
<ExternalLink size={14} />
|
||||
</button>
|
||||
@@ -138,7 +144,10 @@ export function RichMarkdownLinkBubble({
|
||||
type="button"
|
||||
className="rich-markdown-link-button"
|
||||
onClick={onEditStart}
|
||||
title={translate("auto.components.editor.RichMarkdownLinkBubble.cdfe166f6f", "Edit link")}
|
||||
title={translate(
|
||||
'auto.components.editor.RichMarkdownLinkBubble.cdfe166f6f',
|
||||
'Edit link'
|
||||
)}
|
||||
>
|
||||
<Pencil size={14} />
|
||||
</button>
|
||||
@@ -146,7 +155,10 @@ export function RichMarkdownLinkBubble({
|
||||
type="button"
|
||||
className="rich-markdown-link-button"
|
||||
onClick={onRemove}
|
||||
title={translate("auto.components.editor.RichMarkdownLinkBubble.1c99b726e0", "Remove link")}
|
||||
title={translate(
|
||||
'auto.components.editor.RichMarkdownLinkBubble.1c99b726e0',
|
||||
'Remove link'
|
||||
)}
|
||||
>
|
||||
<Unlink size={14} />
|
||||
</button>
|
||||
|
||||
@@ -49,7 +49,13 @@ export function RichMarkdownReviewNoteLayer({
|
||||
onDelivered
|
||||
}: RichMarkdownReviewNoteLayerProps): React.JSX.Element {
|
||||
return (
|
||||
<div className="rich-markdown-review-note-layer" aria-label={translate("auto.components.editor.RichMarkdownReviewNoteLayer.3ababd949d", "Review notes")}>
|
||||
<div
|
||||
className="rich-markdown-review-note-layer"
|
||||
aria-label={translate(
|
||||
'auto.components.editor.RichMarkdownReviewNoteLayer.3ababd949d',
|
||||
'Review notes'
|
||||
)}
|
||||
>
|
||||
{positions.map(({ comment, top }) => (
|
||||
<div
|
||||
key={comment.id}
|
||||
@@ -81,9 +87,27 @@ export function RichMarkdownReviewNoteLayer({
|
||||
<button
|
||||
type="button"
|
||||
className="rich-markdown-review-note-action"
|
||||
title={copiedCommentId === comment.id ? translate("auto.components.editor.RichMarkdownReviewNoteLayer.117432e2c6", "Copied note") : translate("auto.components.editor.RichMarkdownReviewNoteLayer.9cde7ad994", "Copy note for agent")}
|
||||
title={
|
||||
copiedCommentId === comment.id
|
||||
? translate(
|
||||
'auto.components.editor.RichMarkdownReviewNoteLayer.117432e2c6',
|
||||
'Copied note'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.editor.RichMarkdownReviewNoteLayer.9cde7ad994',
|
||||
'Copy note for agent'
|
||||
)
|
||||
}
|
||||
aria-label={
|
||||
copiedCommentId === comment.id ? translate("auto.components.editor.RichMarkdownReviewNoteLayer.117432e2c6", "Copied note") : translate("auto.components.editor.RichMarkdownReviewNoteLayer.9cde7ad994", "Copy note for agent")
|
||||
copiedCommentId === comment.id
|
||||
? translate(
|
||||
'auto.components.editor.RichMarkdownReviewNoteLayer.117432e2c6',
|
||||
'Copied note'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.editor.RichMarkdownReviewNoteLayer.9cde7ad994',
|
||||
'Copy note for agent'
|
||||
)
|
||||
}
|
||||
onMouseDown={(event) => event.stopPropagation()}
|
||||
onClick={(event) => {
|
||||
@@ -105,7 +129,10 @@ export function RichMarkdownReviewNoteLayer({
|
||||
scopes={[
|
||||
{
|
||||
id: 'note',
|
||||
label: translate("auto.components.editor.RichMarkdownReviewNoteLayer.f3ef92952b", "This note"),
|
||||
label: translate(
|
||||
'auto.components.editor.RichMarkdownReviewNoteLayer.f3ef92952b',
|
||||
'This note'
|
||||
),
|
||||
notes: comment.sentAt ? [] : [comment as MarkdownReviewNote],
|
||||
prompt: formatMarkdownReviewNotes(
|
||||
[comment as MarkdownReviewNote],
|
||||
|
||||
@@ -31,9 +31,29 @@ export function RichMarkdownReviewRailActions({
|
||||
<button
|
||||
type="button"
|
||||
className="rich-markdown-review-rail-toggle"
|
||||
aria-label={railOpen ? translate("auto.components.editor.RichMarkdownReviewRailActions.af02dc2456", "Hide review notes") : translate("auto.components.editor.RichMarkdownReviewRailActions.8aaf2c4c69", "Show review notes")}
|
||||
aria-label={
|
||||
railOpen
|
||||
? translate(
|
||||
'auto.components.editor.RichMarkdownReviewRailActions.af02dc2456',
|
||||
'Hide review notes'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.editor.RichMarkdownReviewRailActions.8aaf2c4c69',
|
||||
'Show review notes'
|
||||
)
|
||||
}
|
||||
aria-expanded={railOpen}
|
||||
title={railOpen ? translate("auto.components.editor.RichMarkdownReviewRailActions.af02dc2456", "Hide review notes") : translate("auto.components.editor.RichMarkdownReviewRailActions.8aaf2c4c69", "Show review notes")}
|
||||
title={
|
||||
railOpen
|
||||
? translate(
|
||||
'auto.components.editor.RichMarkdownReviewRailActions.af02dc2456',
|
||||
'Hide review notes'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.editor.RichMarkdownReviewRailActions.8aaf2c4c69',
|
||||
'Show review notes'
|
||||
)
|
||||
}
|
||||
onClick={onToggleRail}
|
||||
>
|
||||
<MessageSquare className="size-3.5" />
|
||||
@@ -42,8 +62,28 @@ export function RichMarkdownReviewRailActions({
|
||||
<button
|
||||
type="button"
|
||||
className="rich-markdown-review-rail-action"
|
||||
title={notesCopied ? translate("auto.components.editor.RichMarkdownReviewRailActions.a807596997", "Copied notes") : translate("auto.components.editor.RichMarkdownReviewRailActions.636394af72", "Copy notes for agent")}
|
||||
aria-label={notesCopied ? translate("auto.components.editor.RichMarkdownReviewRailActions.a807596997", "Copied notes") : translate("auto.components.editor.RichMarkdownReviewRailActions.636394af72", "Copy notes for agent")}
|
||||
title={
|
||||
notesCopied
|
||||
? translate(
|
||||
'auto.components.editor.RichMarkdownReviewRailActions.a807596997',
|
||||
'Copied notes'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.editor.RichMarkdownReviewRailActions.636394af72',
|
||||
'Copy notes for agent'
|
||||
)
|
||||
}
|
||||
aria-label={
|
||||
notesCopied
|
||||
? translate(
|
||||
'auto.components.editor.RichMarkdownReviewRailActions.a807596997',
|
||||
'Copied notes'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.editor.RichMarkdownReviewRailActions.636394af72',
|
||||
'Copy notes for agent'
|
||||
)
|
||||
}
|
||||
onClick={onCopyNotes}
|
||||
>
|
||||
{notesCopied ? <Check className="size-3.5" /> : <Copy className="size-3.5" />}
|
||||
|
||||
@@ -59,14 +59,20 @@ export function RichMarkdownSearchBar({
|
||||
onClose()
|
||||
}
|
||||
}}
|
||||
placeholder={translate("auto.components.editor.RichMarkdownSearchBar.98b89276f3", "Find in rich editor")}
|
||||
placeholder={translate(
|
||||
'auto.components.editor.RichMarkdownSearchBar.98b89276f3',
|
||||
'Find in rich editor'
|
||||
)}
|
||||
className="rich-markdown-search-input h-7 !border-0 bg-transparent px-2 shadow-none focus-visible:!border-0 focus-visible:ring-0"
|
||||
aria-label={translate("auto.components.editor.RichMarkdownSearchBar.158c645829", "Find in rich markdown editor")}
|
||||
aria-label={translate(
|
||||
'auto.components.editor.RichMarkdownSearchBar.158c645829',
|
||||
'Find in rich markdown editor'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="rich-markdown-search-status">
|
||||
{query && matchCount === 0
|
||||
? translate("auto.components.editor.RichMarkdownSearchBar.a86958d508", "No results")
|
||||
? translate('auto.components.editor.RichMarkdownSearchBar.a86958d508', 'No results')
|
||||
: `${matchCount === 0 ? 0 : activeMatchIndex + 1}/${matchCount}`}
|
||||
</div>
|
||||
<Button
|
||||
@@ -76,8 +82,14 @@ export function RichMarkdownSearchBar({
|
||||
onMouseDown={keepSearchFocus}
|
||||
onClick={() => onMoveToMatch(-1)}
|
||||
disabled={matchCount === 0}
|
||||
title={translate("auto.components.editor.RichMarkdownSearchBar.32ae8d7d57", "Previous match")}
|
||||
aria-label={translate("auto.components.editor.RichMarkdownSearchBar.32ae8d7d57", "Previous match")}
|
||||
title={translate(
|
||||
'auto.components.editor.RichMarkdownSearchBar.32ae8d7d57',
|
||||
'Previous match'
|
||||
)}
|
||||
aria-label={translate(
|
||||
'auto.components.editor.RichMarkdownSearchBar.32ae8d7d57',
|
||||
'Previous match'
|
||||
)}
|
||||
className="rich-markdown-search-button"
|
||||
>
|
||||
<ChevronUp size={14} />
|
||||
@@ -89,8 +101,11 @@ export function RichMarkdownSearchBar({
|
||||
onMouseDown={keepSearchFocus}
|
||||
onClick={() => onMoveToMatch(1)}
|
||||
disabled={matchCount === 0}
|
||||
title={translate("auto.components.editor.RichMarkdownSearchBar.f7bcecbe26", "Next match")}
|
||||
aria-label={translate("auto.components.editor.RichMarkdownSearchBar.f7bcecbe26", "Next match")}
|
||||
title={translate('auto.components.editor.RichMarkdownSearchBar.f7bcecbe26', 'Next match')}
|
||||
aria-label={translate(
|
||||
'auto.components.editor.RichMarkdownSearchBar.f7bcecbe26',
|
||||
'Next match'
|
||||
)}
|
||||
className="rich-markdown-search-button"
|
||||
>
|
||||
<ChevronDown size={14} />
|
||||
@@ -102,8 +117,11 @@ export function RichMarkdownSearchBar({
|
||||
size="icon-xs"
|
||||
onMouseDown={keepSearchFocus}
|
||||
onClick={onClose}
|
||||
title={translate("auto.components.editor.RichMarkdownSearchBar.de68b75bde", "Close search")}
|
||||
aria-label={translate("auto.components.editor.RichMarkdownSearchBar.de68b75bde", "Close search")}
|
||||
title={translate('auto.components.editor.RichMarkdownSearchBar.de68b75bde', 'Close search')}
|
||||
aria-label={translate(
|
||||
'auto.components.editor.RichMarkdownSearchBar.de68b75bde',
|
||||
'Close search'
|
||||
)}
|
||||
className="rich-markdown-search-button"
|
||||
>
|
||||
<X size={14} />
|
||||
|
||||
@@ -30,21 +30,35 @@ export function RichMarkdownSlashMenu({
|
||||
className="rich-markdown-slash-menu"
|
||||
style={{ left: slashMenu.left, top: slashMenu.top }}
|
||||
role="dialog"
|
||||
aria-label={translate("auto.components.editor.RichMarkdownSlashMenu.2e0400b958", "Slash commands")}
|
||||
aria-label={translate(
|
||||
'auto.components.editor.RichMarkdownSlashMenu.2e0400b958',
|
||||
'Slash commands'
|
||||
)}
|
||||
>
|
||||
<div className="rich-markdown-slash-search" onMouseDown={(event) => event.preventDefault()}>
|
||||
<Search className="size-3.5" />
|
||||
<input
|
||||
aria-label={translate("auto.components.editor.RichMarkdownSlashMenu.550189b06c", "Search blocks")}
|
||||
aria-label={translate(
|
||||
'auto.components.editor.RichMarkdownSlashMenu.550189b06c',
|
||||
'Search blocks'
|
||||
)}
|
||||
readOnly
|
||||
type="text"
|
||||
value={slashMenu.query}
|
||||
placeholder={translate("auto.components.editor.RichMarkdownSlashMenu.dbdd2ad15f", "Search blocks...")}
|
||||
placeholder={translate(
|
||||
'auto.components.editor.RichMarkdownSlashMenu.dbdd2ad15f',
|
||||
'Search blocks...'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="rich-markdown-slash-results scrollbar-sleek" role="listbox">
|
||||
{filteredCommands.length === 0 ? (
|
||||
<div className="rich-markdown-slash-empty">{translate("auto.components.editor.RichMarkdownSlashMenu.82c6816ff8", "No blocks found")}</div>
|
||||
<div className="rich-markdown-slash-empty">
|
||||
{translate(
|
||||
'auto.components.editor.RichMarkdownSlashMenu.82c6816ff8',
|
||||
'No blocks found'
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
filteredCommands.map((command, index) => {
|
||||
const showGroup = command.group !== currentGroup
|
||||
|
||||
@@ -34,28 +34,28 @@ export function RichMarkdownToolbar({
|
||||
<div className="rich-markdown-editor-toolbar">
|
||||
<RichMarkdownToolbarButton
|
||||
active={false}
|
||||
label={translate("auto.components.editor.RichMarkdownToolbar.b462641ed2", "Body text")}
|
||||
label={translate('auto.components.editor.RichMarkdownToolbar.b462641ed2', 'Body text')}
|
||||
onClick={() => editor?.chain().focus().setParagraph().run()}
|
||||
>
|
||||
<Pilcrow className="size-3.5" />
|
||||
</RichMarkdownToolbarButton>
|
||||
<RichMarkdownToolbarButton
|
||||
active={false}
|
||||
label={translate("auto.components.editor.RichMarkdownToolbar.abb5100a3d", "Heading 1")}
|
||||
label={translate('auto.components.editor.RichMarkdownToolbar.abb5100a3d', 'Heading 1')}
|
||||
onClick={() => editor?.chain().focus().toggleHeading({ level: 1 }).run()}
|
||||
>
|
||||
<Heading1 className="size-3.5" />
|
||||
</RichMarkdownToolbarButton>
|
||||
<RichMarkdownToolbarButton
|
||||
active={false}
|
||||
label={translate("auto.components.editor.RichMarkdownToolbar.d34a2021c8", "Heading 2")}
|
||||
label={translate('auto.components.editor.RichMarkdownToolbar.d34a2021c8', 'Heading 2')}
|
||||
onClick={() => editor?.chain().focus().toggleHeading({ level: 2 }).run()}
|
||||
>
|
||||
<Heading2 className="size-3.5" />
|
||||
</RichMarkdownToolbarButton>
|
||||
<RichMarkdownToolbarButton
|
||||
active={false}
|
||||
label={translate("auto.components.editor.RichMarkdownToolbar.cf5817d827", "Heading 3")}
|
||||
label={translate('auto.components.editor.RichMarkdownToolbar.cf5817d827', 'Heading 3')}
|
||||
onClick={() => editor?.chain().focus().toggleHeading({ level: 3 }).run()}
|
||||
>
|
||||
<Heading3 className="size-3.5" />
|
||||
@@ -63,21 +63,21 @@ export function RichMarkdownToolbar({
|
||||
<Separator />
|
||||
<RichMarkdownToolbarButton
|
||||
active={false}
|
||||
label={translate("auto.components.editor.RichMarkdownToolbar.4f9e789fe0", "Bold")}
|
||||
label={translate('auto.components.editor.RichMarkdownToolbar.4f9e789fe0', 'Bold')}
|
||||
onClick={() => editor?.chain().focus().toggleBold().run()}
|
||||
>
|
||||
B
|
||||
</RichMarkdownToolbarButton>
|
||||
<RichMarkdownToolbarButton
|
||||
active={false}
|
||||
label={translate("auto.components.editor.RichMarkdownToolbar.6b4ccf9493", "Italic")}
|
||||
label={translate('auto.components.editor.RichMarkdownToolbar.6b4ccf9493', 'Italic')}
|
||||
onClick={() => editor?.chain().focus().toggleItalic().run()}
|
||||
>
|
||||
I
|
||||
</RichMarkdownToolbarButton>
|
||||
<RichMarkdownToolbarButton
|
||||
active={false}
|
||||
label={translate("auto.components.editor.RichMarkdownToolbar.0bea19a988", "Strike")}
|
||||
label={translate('auto.components.editor.RichMarkdownToolbar.0bea19a988', 'Strike')}
|
||||
onClick={() => editor?.chain().focus().toggleStrike().run()}
|
||||
>
|
||||
S
|
||||
@@ -85,21 +85,21 @@ export function RichMarkdownToolbar({
|
||||
<Separator />
|
||||
<RichMarkdownToolbarButton
|
||||
active={false}
|
||||
label={translate("auto.components.editor.RichMarkdownToolbar.5d1539e5a9", "Bullet list")}
|
||||
label={translate('auto.components.editor.RichMarkdownToolbar.5d1539e5a9', 'Bullet list')}
|
||||
onClick={() => editor?.chain().focus().toggleBulletList().run()}
|
||||
>
|
||||
<List className="size-3.5" />
|
||||
</RichMarkdownToolbarButton>
|
||||
<RichMarkdownToolbarButton
|
||||
active={false}
|
||||
label={translate("auto.components.editor.RichMarkdownToolbar.31630ed66e", "Numbered list")}
|
||||
label={translate('auto.components.editor.RichMarkdownToolbar.31630ed66e', 'Numbered list')}
|
||||
onClick={() => editor?.chain().focus().toggleOrderedList().run()}
|
||||
>
|
||||
<ListOrdered className="size-3.5" />
|
||||
</RichMarkdownToolbarButton>
|
||||
<RichMarkdownToolbarButton
|
||||
active={false}
|
||||
label={translate("auto.components.editor.RichMarkdownToolbar.f97031be09", "Checklist")}
|
||||
label={translate('auto.components.editor.RichMarkdownToolbar.f97031be09', 'Checklist')}
|
||||
onClick={() => editor?.chain().focus().toggleTaskList().run()}
|
||||
>
|
||||
<ListTodo className="size-3.5" />
|
||||
@@ -107,15 +107,23 @@ export function RichMarkdownToolbar({
|
||||
<Separator />
|
||||
<RichMarkdownToolbarButton
|
||||
active={false}
|
||||
label={translate("auto.components.editor.RichMarkdownToolbar.f6a51cb9af", "Quote")}
|
||||
label={translate('auto.components.editor.RichMarkdownToolbar.f6a51cb9af', 'Quote')}
|
||||
onClick={() => editor?.chain().focus().toggleBlockquote().run()}
|
||||
>
|
||||
<Quote className="size-3.5" />
|
||||
</RichMarkdownToolbarButton>
|
||||
<RichMarkdownToolbarButton active={false} label={translate("auto.components.editor.RichMarkdownToolbar.6d52624712", "Link")} onClick={onToggleLink}>
|
||||
<RichMarkdownToolbarButton
|
||||
active={false}
|
||||
label={translate('auto.components.editor.RichMarkdownToolbar.6d52624712', 'Link')}
|
||||
onClick={onToggleLink}
|
||||
>
|
||||
<LinkIcon className="size-3.5" />
|
||||
</RichMarkdownToolbarButton>
|
||||
<RichMarkdownToolbarButton active={false} label={translate("auto.components.editor.RichMarkdownToolbar.e935c6b61e", "Image")} onClick={onImagePick}>
|
||||
<RichMarkdownToolbarButton
|
||||
active={false}
|
||||
label={translate('auto.components.editor.RichMarkdownToolbar.e935c6b61e', 'Image')}
|
||||
onClick={onImagePick}
|
||||
>
|
||||
<ImageIcon className="size-3.5" />
|
||||
</RichMarkdownToolbarButton>
|
||||
</div>
|
||||
|
||||
@@ -133,13 +133,21 @@ export function UntitledFileRenameDialog({
|
||||
}}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-sm">{translate("auto.components.editor.UntitledFileRenameDialog.674b046582", "Save as")}</DialogTitle>
|
||||
<DialogTitle className="text-sm">
|
||||
{translate('auto.components.editor.UntitledFileRenameDialog.674b046582', 'Save as')}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-xs">
|
||||
{translate("auto.components.editor.UntitledFileRenameDialog.e365f3c638", "Name your markdown file and pick a folder.")}</DialogDescription>
|
||||
{translate(
|
||||
'auto.components.editor.UntitledFileRenameDialog.e365f3c638',
|
||||
'Name your markdown file and pick a folder.'
|
||||
)}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col gap-3">
|
||||
<div>
|
||||
<label className="text-[11px] font-medium text-muted-foreground mb-1 block">{translate("auto.components.editor.UntitledFileRenameDialog.b6ed807cc6", "Name")}</label>
|
||||
<label className="text-[11px] font-medium text-muted-foreground mb-1 block">
|
||||
{translate('auto.components.editor.UntitledFileRenameDialog.b6ed807cc6', 'Name')}
|
||||
</label>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Input
|
||||
ref={setNameInputNode}
|
||||
@@ -154,16 +162,22 @@ export function UntitledFileRenameDialog({
|
||||
handleSubmit()
|
||||
}
|
||||
}}
|
||||
placeholder={translate("auto.components.editor.UntitledFileRenameDialog.c8ac7868e6", "file name")}
|
||||
placeholder={translate(
|
||||
'auto.components.editor.UntitledFileRenameDialog.c8ac7868e6',
|
||||
'file name'
|
||||
)}
|
||||
className="h-8 text-sm"
|
||||
aria-invalid={!!displayError}
|
||||
/>
|
||||
<span className="text-xs text-muted-foreground shrink-0">{translate("auto.components.editor.UntitledFileRenameDialog.2d7d39dc63", ".md")}</span>
|
||||
<span className="text-xs text-muted-foreground shrink-0">
|
||||
{translate('auto.components.editor.UntitledFileRenameDialog.2d7d39dc63', '.md')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-[11px] font-medium text-muted-foreground mb-1 block">
|
||||
{translate("auto.components.editor.UntitledFileRenameDialog.30099dca46", "Folder")}</label>
|
||||
{translate('auto.components.editor.UntitledFileRenameDialog.30099dca46', 'Folder')}
|
||||
</label>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Input
|
||||
value={dir}
|
||||
@@ -187,7 +201,15 @@ export function UntitledFileRenameDialog({
|
||||
disabled={disableBrowse}
|
||||
onClick={() => void handleBrowse()}
|
||||
title={
|
||||
disableBrowse ? translate("auto.components.editor.UntitledFileRenameDialog.5e7f0d8a80", "Folder picker unavailable for remote files") : translate("auto.components.editor.UntitledFileRenameDialog.725868c75d", "Browse folders")
|
||||
disableBrowse
|
||||
? translate(
|
||||
'auto.components.editor.UntitledFileRenameDialog.5e7f0d8a80',
|
||||
'Folder picker unavailable for remote files'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.editor.UntitledFileRenameDialog.725868c75d',
|
||||
'Browse folders'
|
||||
)
|
||||
}
|
||||
>
|
||||
<FolderOpen className="size-3.5" />
|
||||
@@ -198,9 +220,11 @@ export function UntitledFileRenameDialog({
|
||||
{displayError && <p className="text-xs text-destructive mt-1">{displayError}</p>}
|
||||
<DialogFooter className="mt-1">
|
||||
<Button variant="outline" size="sm" onClick={onClose}>
|
||||
{translate("auto.components.editor.UntitledFileRenameDialog.949711deb4", "Cancel")}</Button>
|
||||
{translate('auto.components.editor.UntitledFileRenameDialog.949711deb4', 'Cancel')}
|
||||
</Button>
|
||||
<Button size="sm" onClick={handleSubmit}>
|
||||
{translate("auto.components.editor.UntitledFileRenameDialog.a7dd27b0bc", "Save")}</Button>
|
||||
{translate('auto.components.editor.UntitledFileRenameDialog.a7dd27b0bc', 'Save')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@@ -16,14 +16,23 @@ export async function exportActiveMarkdownToPdf(): Promise<void> {
|
||||
return
|
||||
}
|
||||
|
||||
const toastId = toast.loading(translate("auto.components.editor.export.active.markdown.d4a901e0ad", "Exporting PDF..."))
|
||||
const toastId = toast.loading(
|
||||
translate('auto.components.editor.export.active.markdown.d4a901e0ad', 'Exporting PDF...')
|
||||
)
|
||||
try {
|
||||
const result = await window.api.export.htmlToPdf({
|
||||
html: payload.html,
|
||||
title: payload.title
|
||||
})
|
||||
if (result.success) {
|
||||
toast.success(translate("auto.components.editor.export.active.markdown.51c4244904", "Exported to {{value0}}", { value0: result.filePath }), { id: toastId })
|
||||
toast.success(
|
||||
translate(
|
||||
'auto.components.editor.export.active.markdown.51c4244904',
|
||||
'Exported to {{value0}}',
|
||||
{ value0: result.filePath }
|
||||
),
|
||||
{ id: toastId }
|
||||
)
|
||||
return
|
||||
}
|
||||
if (result.cancelled) {
|
||||
@@ -32,7 +41,14 @@ export async function exportActiveMarkdownToPdf(): Promise<void> {
|
||||
toast.dismiss(toastId)
|
||||
return
|
||||
}
|
||||
toast.error(result.error ?? translate("auto.components.editor.export.active.markdown.eda2cea3ad", "Failed to export PDF"), { id: toastId })
|
||||
toast.error(
|
||||
result.error ??
|
||||
translate(
|
||||
'auto.components.editor.export.active.markdown.eda2cea3ad',
|
||||
'Failed to export PDF'
|
||||
),
|
||||
{ id: toastId }
|
||||
)
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Failed to export PDF'
|
||||
toast.error(message, { id: toastId })
|
||||
|
||||
@@ -17,7 +17,10 @@ type UnsupportedMatch = {
|
||||
const UNSUPPORTED_PATTERNS: UnsupportedMatch[] = [
|
||||
{
|
||||
reason: 'html-or-jsx',
|
||||
message: translate("auto.components.editor.markdown.rich.mode.57128b73e1", "Editable only in code mode because this file contains HTML, JSX, or MDX."),
|
||||
message: translate(
|
||||
'auto.components.editor.markdown.rich.mode.57128b73e1',
|
||||
'Editable only in code mode because this file contains HTML, JSX, or MDX.'
|
||||
),
|
||||
// Why: the rich editor preserves common embedded markup via placeholder
|
||||
// tokens before parsing, but any HTML shape that still fails round-trip
|
||||
// must fall back instead of risking silent source corruption.
|
||||
@@ -25,12 +28,18 @@ const UNSUPPORTED_PATTERNS: UnsupportedMatch[] = [
|
||||
},
|
||||
{
|
||||
reason: 'reference-links',
|
||||
message: translate("auto.components.editor.markdown.rich.mode.2fd2b44073", "Editable only in code mode because this file contains reference-style links."),
|
||||
message: translate(
|
||||
'auto.components.editor.markdown.rich.mode.2fd2b44073',
|
||||
'Editable only in code mode because this file contains reference-style links.'
|
||||
),
|
||||
pattern: /^\[[^\]]+\]:\s+\S+/m
|
||||
},
|
||||
{
|
||||
reason: 'footnotes',
|
||||
message: translate("auto.components.editor.markdown.rich.mode.7a8ce7c7da", "Editable only in code mode because this file contains footnotes."),
|
||||
message: translate(
|
||||
'auto.components.editor.markdown.rich.mode.7a8ce7c7da',
|
||||
'Editable only in code mode because this file contains footnotes.'
|
||||
),
|
||||
pattern: /^\[\^[^\]]+\]:\s+/m
|
||||
}
|
||||
]
|
||||
|
||||
@@ -198,7 +198,13 @@ function openMarkdownLinkInClientOs({
|
||||
if (classified.kind === 'markdown') {
|
||||
void window.api.shell.pathExists(classified.absolutePath).then((exists) => {
|
||||
if (!exists) {
|
||||
toast.error(translate("auto.components.editor.rich.markdown.editor.click.routing.2d5fb9335d", "File not found: {{value0}}", { value0: classified.relativePath }))
|
||||
toast.error(
|
||||
translate(
|
||||
'auto.components.editor.rich.markdown.editor.click.routing.2d5fb9335d',
|
||||
'File not found: {{value0}}',
|
||||
{ value0: classified.relativePath }
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
void window.api.shell.openFileUri(toFileUrlForOsEscape(classified.absolutePath))
|
||||
|
||||
@@ -35,7 +35,8 @@ export function useContextualCopySetup() {
|
||||
className="pointer-events-none fixed z-50 rounded-md bg-foreground px-2 py-1 text-xs text-background shadow-sm"
|
||||
style={{ left: copyToast.left, top: copyToast.top }}
|
||||
>
|
||||
{translate("auto.components.editor.useContextualCopySetup.059bfb0d94", "Context copied")}</div>
|
||||
{translate('auto.components.editor.useContextualCopySetup.059bfb0d94', 'Context copied')}
|
||||
</div>
|
||||
) : null
|
||||
|
||||
return { setupCopy, toastNode }
|
||||
|
||||
@@ -37,7 +37,12 @@ export function useLocalImagePick(
|
||||
if (settings?.activeRuntimeEnvironmentId?.trim() || connectionId) {
|
||||
const worktreePath = getWorktreePath(worktreeId)
|
||||
if (settings?.activeRuntimeEnvironmentId?.trim() && !worktreePath) {
|
||||
toast.error(translate("auto.components.editor.useLocalImagePick.91d835dc88", "Worktree path not available."))
|
||||
toast.error(
|
||||
translate(
|
||||
'auto.components.editor.useLocalImagePick.91d835dc88',
|
||||
'Worktree path not available.'
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
// Why: picked images are client-local files while remote markdown lives
|
||||
@@ -55,7 +60,12 @@ export function useLocalImagePick(
|
||||
)
|
||||
const imported = results.find((result) => result.status === 'imported')
|
||||
if (!imported) {
|
||||
toast.error(translate("auto.components.editor.useLocalImagePick.175cb8b8ce", "Failed to insert image."))
|
||||
toast.error(
|
||||
translate(
|
||||
'auto.components.editor.useLocalImagePick.175cb8b8ce',
|
||||
'Failed to insert image.'
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
editor
|
||||
|
||||
@@ -57,7 +57,10 @@ export function useRichMarkdownReviewData({
|
||||
return [
|
||||
{
|
||||
id: 'all',
|
||||
label: translate("auto.components.editor.useRichMarkdownReviewData.f9d2acd6b0", "All unsent notes"),
|
||||
label: translate(
|
||||
'auto.components.editor.useRichMarkdownReviewData.f9d2acd6b0',
|
||||
'All unsent notes'
|
||||
),
|
||||
notes: unsentNotes,
|
||||
prompt: formatMarkdownReviewNotes(unsentNotes, markdownReviewContent)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user