mirror of
https://github.com/stablyai/orca.git
synced 2026-10-23 08:03:39 +00:00
feat(sidebar): show each notice row's host with the project-on-host glyph
Notice rows on a multi-host project already carried a host label, but two hosts can share one user-facing name, and the label truncates first in a narrow sidebar. Each row now also carries its host's glyph. Deliberately the same indicator worktree cards use (worktree-card-header): a Server glyph, ServerOff when a paired runtime has no live status, and a "Project on ..." tooltip naming the host — SSH and runtime keep their distinct tooltip wording. Local hosts draw nothing, as on the cards. The glyph is shrink-0, so unlike the text label it survives the sidebar narrowing, and the row keeps an identifying mark either way. Rows now carry the host id alongside the label, since the label alone cannot select a glyph or its tooltip. Catalog entries for the new copy ship with the change rather than relying on inline fallbacks.
This commit is contained in:
@@ -4,6 +4,8 @@ import { ChevronRight, X } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { cn } from '@/lib/utils'
|
||||
import NoticeHostGlyph from './NoticeHostGlyph'
|
||||
import type { ExecutionHostId } from '../../../../shared/execution-host'
|
||||
import { getExternalWorktreeParentPath } from '../../../../shared/external-worktree-visibility'
|
||||
import { normalizeRuntimePathForComparison } from '../../../../shared/cross-platform-path'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
@@ -22,6 +24,7 @@ type ImportedWorktreesVisibilityLineProps = {
|
||||
/** Host this checkout lives on. Set only when the project is checked out on
|
||||
* more than one host, where the line alone cannot identify the row. */
|
||||
hostContextLabel?: string
|
||||
hostContextHostId?: ExecutionHostId
|
||||
hiddenWorktrees: readonly ImportedWorktreeVisibilityPreview[]
|
||||
placement: ImportedWorktreesVisibilityPlacement
|
||||
pending: boolean
|
||||
@@ -78,6 +81,7 @@ export function groupWorktreesByParentPath(
|
||||
export default function ImportedWorktreesVisibilityLine({
|
||||
repoDisplayName,
|
||||
hostContextLabel,
|
||||
hostContextHostId,
|
||||
hiddenWorktrees,
|
||||
placement,
|
||||
pending,
|
||||
@@ -153,8 +157,13 @@ export default function ImportedWorktreesVisibilityLine({
|
||||
</Button>
|
||||
<span className="min-w-0 flex-1 truncate">{lineText}</span>
|
||||
{hostContextLabel && placement !== 'pinned-fallback' ? (
|
||||
<span className="min-w-0 shrink truncate text-[10px] leading-none text-muted-foreground">
|
||||
{hostContextLabel}
|
||||
<span className="inline-flex min-w-0 shrink items-center gap-1">
|
||||
{hostContextHostId ? (
|
||||
<NoticeHostGlyph hostId={hostContextHostId} hostLabel={hostContextLabel} />
|
||||
) : null}
|
||||
<span className="min-w-0 truncate text-[10px] leading-none text-muted-foreground">
|
||||
{hostContextLabel}
|
||||
</span>
|
||||
</span>
|
||||
) : null}
|
||||
{onKeepHidden ? (
|
||||
|
||||
@@ -4,6 +4,8 @@ import { ChevronRight, X } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { cn } from '@/lib/utils'
|
||||
import NoticeHostGlyph from './NoticeHostGlyph'
|
||||
import type { ExecutionHostId } from '../../../../shared/execution-host'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
|
||||
type NewExternalWorktreesInboxLineProps = {
|
||||
@@ -11,6 +13,7 @@ type NewExternalWorktreesInboxLineProps = {
|
||||
/** Host this checkout lives on. Set only when the project is checked out on
|
||||
* more than one host, where the count alone cannot identify the row. */
|
||||
hostContextLabel?: string
|
||||
hostContextHostId?: ExecutionHostId
|
||||
inboxCount: number
|
||||
pending: boolean
|
||||
error: string | null
|
||||
@@ -22,6 +25,7 @@ type NewExternalWorktreesInboxLineProps = {
|
||||
export default function NewExternalWorktreesInboxLine({
|
||||
repoDisplayName,
|
||||
hostContextLabel,
|
||||
hostContextHostId,
|
||||
inboxCount,
|
||||
pending,
|
||||
error,
|
||||
@@ -97,8 +101,13 @@ export default function NewExternalWorktreesInboxLine({
|
||||
</span>
|
||||
<span className="min-w-0 flex-1 truncate text-left">{countLabel}</span>
|
||||
{hostContextLabel ? (
|
||||
<span className="min-w-0 shrink truncate text-[10px] leading-none text-muted-foreground">
|
||||
{hostContextLabel}
|
||||
<span className="inline-flex min-w-0 shrink items-center gap-1">
|
||||
{hostContextHostId ? (
|
||||
<NoticeHostGlyph hostId={hostContextHostId} hostLabel={hostContextLabel} />
|
||||
) : null}
|
||||
<span className="min-w-0 truncate text-[10px] leading-none text-muted-foreground">
|
||||
{hostContextLabel}
|
||||
</span>
|
||||
</span>
|
||||
) : null}
|
||||
<ChevronRight
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
/**
|
||||
* Notice rows reuse the worktree card's host indicator so one project's rows
|
||||
* read the same way wherever they appear: a server glyph with a "Project on …"
|
||||
* tooltip, nothing at all for local.
|
||||
*/
|
||||
import { act, type ReactNode } from 'react'
|
||||
import { createRoot, type Root } from 'react-dom/client'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import NoticeHostGlyph from './NoticeHostGlyph'
|
||||
import en from '../../i18n/locales/en.json'
|
||||
|
||||
const runtimeStatusByEnvironmentId = new Map<string, { status?: unknown }>()
|
||||
|
||||
vi.mock('@/store', () => ({
|
||||
useAppStore: (selector: (state: unknown) => unknown) => selector({ runtimeStatusByEnvironmentId })
|
||||
}))
|
||||
|
||||
vi.mock('@/components/ui/tooltip', () => ({
|
||||
Tooltip: ({ children }: { children: ReactNode }) => <>{children}</>,
|
||||
TooltipTrigger: ({ children }: { children: ReactNode }) => <>{children}</>,
|
||||
TooltipContent: ({ children }: { children: ReactNode }) => (
|
||||
<span data-testid="tooltip">{children}</span>
|
||||
)
|
||||
}))
|
||||
|
||||
const roots: Root[] = []
|
||||
|
||||
async function render(hostId: string, hostLabel = 'openclaw'): Promise<HTMLDivElement> {
|
||||
const container = document.createElement('div')
|
||||
document.body.appendChild(container)
|
||||
const root = createRoot(container)
|
||||
roots.push(root)
|
||||
await act(async () => {
|
||||
root.render(<NoticeHostGlyph hostId={hostId as never} hostLabel={hostLabel} />)
|
||||
})
|
||||
return container
|
||||
}
|
||||
|
||||
describe('NoticeHostGlyph', () => {
|
||||
beforeEach(() => {
|
||||
globalThis.IS_REACT_ACT_ENVIRONMENT = true
|
||||
runtimeStatusByEnvironmentId.clear()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
roots.splice(0).forEach((root) => act(() => root.unmount()))
|
||||
document.body.replaceChildren()
|
||||
})
|
||||
|
||||
it('names the SSH host it would act on', async () => {
|
||||
const container = await render('ssh:openclaw-target')
|
||||
|
||||
expect(container.querySelector('[data-notice-host-kind="ssh"]')).not.toBeNull()
|
||||
expect(container.querySelector('[data-testid="tooltip"]')?.textContent).toBe(
|
||||
'Project on SSH host openclaw'
|
||||
)
|
||||
})
|
||||
|
||||
it('names the paired runtime separately from the SSH host of the same name', async () => {
|
||||
runtimeStatusByEnvironmentId.set('openclaw-env', { status: 'ready' })
|
||||
const container = await render('runtime:openclaw-env')
|
||||
|
||||
expect(container.querySelector('[data-notice-host-kind="runtime"]')).not.toBeNull()
|
||||
expect(container.querySelector('[data-testid="tooltip"]')?.textContent).toBe(
|
||||
'Project on openclaw'
|
||||
)
|
||||
})
|
||||
|
||||
it('marks a paired runtime with no live status as disconnected', async () => {
|
||||
const container = await render('runtime:openclaw-env')
|
||||
|
||||
expect(container.querySelector('[data-testid="tooltip"]')?.textContent).toBe(
|
||||
'openclaw disconnected'
|
||||
)
|
||||
})
|
||||
|
||||
it('draws nothing for a local host, matching the worktree card', async () => {
|
||||
const container = await render('local', 'Local Mac')
|
||||
|
||||
expect(container.textContent).toBe('')
|
||||
})
|
||||
|
||||
it('keeps its copy in the English catalog', async () => {
|
||||
// A key referenced only in the component silently falls back to its inline
|
||||
// default and never reaches translators.
|
||||
expect(en.auto.components.sidebar.NoticeHostGlyph).toMatchObject({
|
||||
hostDisconnected: '{{hostName}} disconnected',
|
||||
sshHostProject: 'Project on SSH host {{hostName}}',
|
||||
runtimeHostProject: 'Project on {{hostName}}'
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,71 @@
|
||||
import React from 'react'
|
||||
import { Server, ServerOff } from 'lucide-react'
|
||||
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { useAppStore } from '@/store'
|
||||
import { parseExecutionHostId, type ExecutionHostId } from '../../../../shared/execution-host'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
|
||||
type NoticeHostGlyphProps = {
|
||||
hostId: ExecutionHostId
|
||||
hostLabel: string
|
||||
}
|
||||
|
||||
/**
|
||||
* The host indicator for a discovery-notice row.
|
||||
*
|
||||
* Deliberately the same glyph and "Project on …" tooltip worktree cards use
|
||||
* (worktree-card-header.tsx), so one project's rows read the same way wherever
|
||||
* they appear. Local hosts get none, matching the cards.
|
||||
*/
|
||||
export default function NoticeHostGlyph({
|
||||
hostId,
|
||||
hostLabel
|
||||
}: NoticeHostGlyphProps): React.JSX.Element | null {
|
||||
const host = parseExecutionHostId(hostId)
|
||||
const isDisconnected = useAppStore((s) => {
|
||||
if (host?.kind !== 'runtime') {
|
||||
return false
|
||||
}
|
||||
return !s.runtimeStatusByEnvironmentId.get(host.environmentId)?.status
|
||||
})
|
||||
|
||||
if (!host || host.kind === 'local') {
|
||||
return null
|
||||
}
|
||||
|
||||
const tooltip = isDisconnected
|
||||
? translate(
|
||||
'auto.components.sidebar.NoticeHostGlyph.hostDisconnected',
|
||||
'{{hostName}} disconnected',
|
||||
{ hostName: hostLabel }
|
||||
)
|
||||
: host.kind === 'ssh'
|
||||
? translate(
|
||||
'auto.components.sidebar.NoticeHostGlyph.sshHostProject',
|
||||
'Project on SSH host {{hostName}}',
|
||||
{ hostName: hostLabel }
|
||||
)
|
||||
: translate(
|
||||
'auto.components.sidebar.NoticeHostGlyph.runtimeHostProject',
|
||||
'Project on {{hostName}}',
|
||||
{ hostName: hostLabel }
|
||||
)
|
||||
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="inline-flex shrink-0 items-center" data-notice-host-kind={host.kind}>
|
||||
{isDisconnected ? (
|
||||
<ServerOff className="size-3 text-destructive" aria-hidden="true" />
|
||||
) : (
|
||||
<Server className="size-3 text-muted-foreground" aria-hidden="true" />
|
||||
)}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={4}>
|
||||
{tooltip}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
@@ -126,10 +126,13 @@ function noticeRows(args: {
|
||||
|
||||
function summarize(
|
||||
rows: Extract<Row, { type: 'new-external-worktrees-inbox' }>[]
|
||||
): { repoId: string; label: string | undefined; count: number }[] {
|
||||
): { repoId: string; label: string | undefined; hostId: string | undefined; count: number }[] {
|
||||
return rows.map((row) => ({
|
||||
repoId: row.repo.id,
|
||||
label: row.hostContextLabel,
|
||||
// Carried so the row can draw the host glyph; two hosts sharing a label
|
||||
// differ only here.
|
||||
hostId: row.hostContextHostId,
|
||||
count: row.inboxWorktrees.length
|
||||
}))
|
||||
}
|
||||
@@ -154,8 +157,8 @@ describe('discovery notice rows on a multi-host project', () => {
|
||||
})
|
||||
)
|
||||
).toEqual([
|
||||
{ repoId: sshTwin.id, label: 'openclaw', count: 61 },
|
||||
{ repoId: envTwin.id, label: 'openclaw', count: 134 }
|
||||
{ repoId: sshTwin.id, label: 'openclaw', hostId: SSH_HOST_ID, count: 61 },
|
||||
{ repoId: envTwin.id, label: 'openclaw', hostId: ENV_HOST_ID, count: 134 }
|
||||
])
|
||||
})
|
||||
|
||||
@@ -163,7 +166,7 @@ describe('discovery notice rows on a multi-host project', () => {
|
||||
// Kills notice-row-scoped membership: only one record emits a row, but the
|
||||
// project still spans hosts, so the row must say which host it is.
|
||||
expect(summarize(noticeRows({ eligibleRepoIds: [envTwin.id] }))).toEqual([
|
||||
{ repoId: envTwin.id, label: 'openclaw', count: 134 }
|
||||
{ repoId: envTwin.id, label: 'openclaw', hostId: ENV_HOST_ID, count: 134 }
|
||||
])
|
||||
})
|
||||
|
||||
@@ -171,10 +174,10 @@ describe('discovery notice rows on a multi-host project', () => {
|
||||
// Kills the collapse (both rows survive with distinct counts) and proves the
|
||||
// gate reads the unfiltered universe (each filtered survivor keeps its label).
|
||||
expect(summarize(noticeRows({ eligibleRepoIds: [sshTwin.id] }))).toEqual([
|
||||
{ repoId: sshTwin.id, label: 'openclaw', count: 61 }
|
||||
{ repoId: sshTwin.id, label: 'openclaw', hostId: SSH_HOST_ID, count: 61 }
|
||||
])
|
||||
expect(summarize(noticeRows({ eligibleRepoIds: [envTwin.id] }))).toEqual([
|
||||
{ repoId: envTwin.id, label: 'openclaw', count: 134 }
|
||||
{ repoId: envTwin.id, label: 'openclaw', hostId: ENV_HOST_ID, count: 134 }
|
||||
])
|
||||
})
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from '../../workspace-status'
|
||||
import { PROJECT_GROUP_META, PR_GROUP_META } from './group-keys'
|
||||
import type { PRGroupKey } from './group-keys'
|
||||
import type { NoticeHostContext } from './host-labels'
|
||||
import {
|
||||
getLaneHostWorktreeCounts,
|
||||
getLaneHostWorktreeIds,
|
||||
@@ -44,7 +45,7 @@ export type SectionAppendContext = {
|
||||
newExternalWorktreesInboxByRepo: ReadonlyMap<string, NewExternalWorktreesInboxCandidate>
|
||||
pendingByRepo: ReadonlyMap<string, PendingCreationRef[]>
|
||||
mixedWorktreeHostContextLabels: Map<string, string> | undefined
|
||||
noticeHostContextLabelByRepoId: Map<string, string> | undefined
|
||||
noticeHostContextLabelByRepoId: Map<string, NoticeHostContext> | undefined
|
||||
lineageById: Record<string, WorktreeLineage>
|
||||
worktreeMap: Map<string, Worktree>
|
||||
nestLineage: boolean
|
||||
|
||||
@@ -70,13 +70,20 @@ export function getMixedHostContextLabels(
|
||||
* eligible for a label. Deriving both from the filtered set would make a label
|
||||
* appear and disappear with the sidebar's host filter.
|
||||
*/
|
||||
export type NoticeHostContext = {
|
||||
label: string
|
||||
/** Carried so the row can draw the same host glyph and "Project on …"
|
||||
* tooltip worktree cards use, which the label alone cannot select. */
|
||||
hostId: ExecutionHostId
|
||||
}
|
||||
|
||||
export function getNoticeHostContextLabels(
|
||||
noticeRepoIds: Iterable<string>,
|
||||
allRepoIds: Iterable<string>,
|
||||
repoMap: Map<string, Repo>,
|
||||
projectIndex: ProjectGroupingIndex | null,
|
||||
hostLabelById: ReadonlyMap<string, string> | undefined
|
||||
): Map<string, string> | undefined {
|
||||
): Map<string, NoticeHostContext> | undefined {
|
||||
const eligible = new Set(noticeRepoIds)
|
||||
if (eligible.size === 0) {
|
||||
return undefined
|
||||
@@ -85,7 +92,7 @@ export function getNoticeHostContextLabels(
|
||||
// that project spans hosts just the same — counting labels hides exactly the
|
||||
// case where the rows are hardest to tell apart.
|
||||
const hostIdsForProject = new Map<string, Set<string>>()
|
||||
const labelsByRepoId = new Map<string, string>()
|
||||
const labelsByRepoId = new Map<string, NoticeHostContext>()
|
||||
const projectKeyByRepoId = new Map<string, string>()
|
||||
for (const repoId of allRepoIds) {
|
||||
const label = getRepoHostLabel(repoId, repoMap, projectIndex, hostLabelById)
|
||||
@@ -99,16 +106,16 @@ export function getNoticeHostContextLabels(
|
||||
hostIds.add(hostId)
|
||||
hostIdsForProject.set(projectKey, hostIds)
|
||||
}
|
||||
if (eligible.has(repoId)) {
|
||||
labelsByRepoId.set(repoId, label)
|
||||
if (eligible.has(repoId) && hostId) {
|
||||
labelsByRepoId.set(repoId, { label, hostId: hostId as ExecutionHostId })
|
||||
projectKeyByRepoId.set(repoId, projectKey)
|
||||
}
|
||||
}
|
||||
const mixed = new Map<string, string>()
|
||||
for (const [repoId, label] of labelsByRepoId) {
|
||||
const mixed = new Map<string, NoticeHostContext>()
|
||||
for (const [repoId, context] of labelsByRepoId) {
|
||||
const projectKey = projectKeyByRepoId.get(repoId)
|
||||
if (projectKey && (hostIdsForProject.get(projectKey)?.size ?? 0) > 1) {
|
||||
mixed.set(repoId, label)
|
||||
mixed.set(repoId, context)
|
||||
}
|
||||
}
|
||||
return mixed.size > 0 ? mixed : undefined
|
||||
|
||||
@@ -5,6 +5,7 @@ import { getWorktreeExecutionHostId } from '../../../../../../shared/execution-h
|
||||
import type { ExecutionHostId } from '../../../../../../shared/execution-host'
|
||||
import { PINNED_GROUP_KEY, PINNED_GROUP_META } from './group-keys'
|
||||
import { appendWorktreeRows, buildImportedWorktreesCardRow } from './row-builders'
|
||||
import type { NoticeHostContext } from './host-labels'
|
||||
import type { ImportedWorktreesCardCandidate, Row } from './row-types'
|
||||
|
||||
/**
|
||||
@@ -26,7 +27,7 @@ export function emitPinnedGroup(
|
||||
worktreeMap: Map<string, Worktree>,
|
||||
nestLineage: boolean,
|
||||
cyclicLineageIds: ReadonlySet<string>,
|
||||
noticeHostContextLabelByRepoId?: ReadonlyMap<string, string>
|
||||
noticeHostContextLabelByRepoId?: ReadonlyMap<string, NoticeHostContext>
|
||||
): void {
|
||||
if (pinnedSectionWorktrees.length === 0) {
|
||||
return
|
||||
|
||||
@@ -5,6 +5,7 @@ import { getWorktreeHostIdentity } from '../../../../../../shared/worktree/host-
|
||||
import { isValidResolvedWorktreeLineageEdge } from '../../../../../../shared/resolved-worktree-lineage'
|
||||
import { getProjectedWorktreeLineage } from '../../worktree-lineage-projection'
|
||||
import { getWorktreeLineageGroupKey } from './group-keys'
|
||||
import type { NoticeHostContext } from './host-labels'
|
||||
import type { RenderableFolderWorkspace } from './folder-workspace-lanes'
|
||||
import type {
|
||||
FolderWorkspaceRow,
|
||||
@@ -33,7 +34,7 @@ export function buildPendingCreationRow(
|
||||
export function buildImportedWorktreesCardRow(
|
||||
candidate: ImportedWorktreesCardCandidate,
|
||||
placement: ImportedWorktreesCardRow['placement'],
|
||||
hostContextLabel?: string
|
||||
hostContext?: NoticeHostContext
|
||||
): ImportedWorktreesCardRow {
|
||||
return {
|
||||
type: 'imported-worktrees-card',
|
||||
@@ -41,20 +42,24 @@ export function buildImportedWorktreesCardRow(
|
||||
repo: candidate.repo,
|
||||
hiddenWorktrees: candidate.hiddenWorktrees,
|
||||
placement,
|
||||
...(hostContextLabel ? { hostContextLabel } : {})
|
||||
...(hostContext
|
||||
? { hostContextLabel: hostContext.label, hostContextHostId: hostContext.hostId }
|
||||
: {})
|
||||
}
|
||||
}
|
||||
|
||||
export function buildNewExternalWorktreesInboxRow(
|
||||
candidate: NewExternalWorktreesInboxCandidate,
|
||||
hostContextLabel?: string
|
||||
hostContext?: NoticeHostContext
|
||||
): NewExternalWorktreesInboxRow {
|
||||
return {
|
||||
type: 'new-external-worktrees-inbox',
|
||||
key: `new-external-worktrees-inbox:${candidate.repo.id}`,
|
||||
repo: candidate.repo,
|
||||
inboxWorktrees: candidate.inboxWorktrees,
|
||||
...(hostContextLabel ? { hostContextLabel } : {})
|
||||
...(hostContext
|
||||
? { hostContextLabel: hostContext.label, hostContextHostId: hostContext.hostId }
|
||||
: {})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ export type ImportedWorktreesCardRow = {
|
||||
placement: 'repo-group' | 'pinned-fallback'
|
||||
/** Set only when the row's project is checked out on more than one host. */
|
||||
hostContextLabel?: string
|
||||
hostContextHostId?: ExecutionHostId
|
||||
}
|
||||
|
||||
export type NewExternalWorktreesInboxCandidate = {
|
||||
@@ -73,6 +74,7 @@ export type NewExternalWorktreesInboxRow = {
|
||||
inboxWorktrees: DetectedWorktree[]
|
||||
/** Set only when the row's project is checked out on more than one host. */
|
||||
hostContextLabel?: string
|
||||
hostContextHostId?: ExecutionHostId
|
||||
}
|
||||
|
||||
export type PendingCreationRow = {
|
||||
|
||||
@@ -63,6 +63,7 @@ export function renderImportedWorktreesVirtualRow(args: {
|
||||
<ImportedWorktreesVisibilityLine
|
||||
repoDisplayName={row.repo.displayName}
|
||||
hostContextLabel={row.hostContextLabel}
|
||||
hostContextHostId={row.hostContextHostId}
|
||||
hiddenWorktrees={row.hiddenWorktrees}
|
||||
placement={row.placement}
|
||||
pending={actionState?.pending ?? false}
|
||||
@@ -96,6 +97,7 @@ export function renderNewExternalWorktreesInboxVirtualRow(args: {
|
||||
<NewExternalWorktreesInboxLine
|
||||
repoDisplayName={row.repo.displayName}
|
||||
hostContextLabel={row.hostContextLabel}
|
||||
hostContextHostId={row.hostContextHostId}
|
||||
inboxCount={row.inboxWorktrees.length}
|
||||
pending={actionState?.pending ?? false}
|
||||
error={actionState?.error ?? null}
|
||||
|
||||
@@ -5730,6 +5730,11 @@
|
||||
"c3e8a1f4b2": "Don't show again",
|
||||
"6c07f3a91e": "{{value0}} on {{value1}}"
|
||||
},
|
||||
"NoticeHostGlyph": {
|
||||
"hostDisconnected": "{{hostName}} disconnected",
|
||||
"sshHostProject": "Project on SSH host {{hostName}}",
|
||||
"runtimeHostProject": "Project on {{hostName}}"
|
||||
},
|
||||
"newExternalWorktreesInboxActions": {
|
||||
"a11c2f6d89": "Could not keep external worktrees hidden. Try again.",
|
||||
"b7e4d1a062": "Could not import external worktrees. Try again.",
|
||||
|
||||
Reference in New Issue
Block a user