Files
orca/src/shared/worktree-card-properties.ts
T
Jinjing 74563b6498 feat(jira): link Jira issues from the workspace create dialog (#11296)
* Link Jira issues from workspace create dialog

Add Jira issue linking to workspace creation, matching existing GitHub and Linear workflows. Users can paste Jira issue URLs in the smart name field to auto-populate workspace names and link the issue to the created workspace/worktree.

Linked Jira issues appear on workspace cards via the new 'jira-issue' card property. Implements cancellable searches and summary reads to prevent stalled requests from blocking the shared Jira pool. Persists paired issue + source context metadata with validation of provider/site identity.

Fixes git-username rate-limit handling to reject malformed JSON responses so garbage never becomes branch prefixes.

* feat(jira): link issues during workspace creation

- Display linked Jira issues on worktree cards
- Fetch issue summaries and timestamps via Jira API
- Gate Jira linking behind runtime capability check
- Preserve user-typed names during async lookups

* Enforce git check-ref-format rules in login validation

Extend isBranchSafeHostedLogin to reject usernames that git rejects as
invalid branch components: trailing dots, consecutive dots, and .lock
suffix. Prevents invalid branch names from login usernames.

* Enforce filesystem filename cap for branch-safe logins

Loose refs store logins as single filenames, so the real constraint is the
255-byte filesystem cap, not git check-ref-format rules. This allows longer
provider-agnostic logins while staying platform-safe.
2026-07-29 19:50:18 -07:00

122 lines
3.6 KiB
TypeScript

import type {
GlobalSettings,
PersistedUIState,
WorktreeCardMode,
WorktreeCardProperty
} from './types'
const FIXED_WORKTREE_CARD_PROPERTIES: WorktreeCardProperty[] = ['status', 'unread']
export const TASK_WORKTREE_CARD_PROPERTIES: WorktreeCardProperty[] = [
'issue',
'linear-issue',
'jira-issue'
]
export const DEFAULT_WORKTREE_CARD_PROPERTIES: WorktreeCardProperty[] = [
...FIXED_WORKTREE_CARD_PROPERTIES,
...TASK_WORKTREE_CARD_PROPERTIES,
'pr',
'automation',
'cli',
'comment',
'ports',
// Why: agent activity is the primary reason users opt into the feature, so
// the Default mode keeps it inline on each card while Compact removes the
// extra row.
'inline-agents'
]
// Why: compact cards default to the quiet preset; metadata icons remain opt-in
// through Show properties instead of appearing automatically.
export const COMPACT_WORKTREE_CARD_PROPERTIES: WorktreeCardProperty[] = ['status']
const NORMALIZED_COMPACT_WORKTREE_CARD_PROPERTIES: WorktreeCardProperty[] = ['status', 'unread']
const LEGACY_COMPACT_WORKTREE_CARD_PROPERTIES_WITH_AUTOMATION: WorktreeCardProperty[] = [
'status',
'automation'
]
const LEGACY_NORMALIZED_COMPACT_WORKTREE_CARD_PROPERTIES_WITH_AUTOMATION: WorktreeCardProperty[] = [
'status',
'unread',
'automation'
]
/** Every card property, in canonical render order. Client schemas derive their
* accepted value domain from this so a new property cannot drift out of them. */
export const WORKTREE_CARD_PROPERTIES = [
'status',
'unread',
'ci',
'branch',
'issue',
'linear-issue',
'jira-issue',
'pr',
'automation',
'cli',
'comment',
'ports',
'inline-agents'
] as const satisfies readonly WorktreeCardProperty[]
export function normalizeWorktreeCardProperties(
properties: readonly unknown[] | null | undefined
): WorktreeCardProperty[] {
const normalized: WorktreeCardProperty[] = [...FIXED_WORKTREE_CARD_PROPERTIES]
const source = properties ?? DEFAULT_WORKTREE_CARD_PROPERTIES
for (const property of WORKTREE_CARD_PROPERTIES) {
if (source.includes(property) && !normalized.includes(property)) {
normalized.push(property)
}
}
return normalized
}
export function getWorktreeCardModeProperties(mode: WorktreeCardMode): WorktreeCardProperty[] {
return mode === 'Compact'
? [...COMPACT_WORKTREE_CARD_PROPERTIES]
: [...DEFAULT_WORKTREE_CARD_PROPERTIES]
}
export function getWorktreeCardModeUpdates(mode: WorktreeCardMode): {
settings: Pick<GlobalSettings, 'compactWorktreeCards'>
ui: Pick<PersistedUIState, 'worktreeCardProperties' | '_worktreeCardModeDefaulted'>
} {
return {
settings: { compactWorktreeCards: mode === 'Compact' },
ui: {
worktreeCardProperties: getWorktreeCardModeProperties(mode),
_worktreeCardModeDefaulted: true
}
}
}
export function isDefaultedCompactWorktreeCardProperties(
properties: readonly unknown[] | null | undefined
): boolean {
return (
matchesWorktreeCardProperties(properties, COMPACT_WORKTREE_CARD_PROPERTIES) ||
matchesWorktreeCardProperties(properties, NORMALIZED_COMPACT_WORKTREE_CARD_PROPERTIES) ||
matchesWorktreeCardProperties(
properties,
LEGACY_COMPACT_WORKTREE_CARD_PROPERTIES_WITH_AUTOMATION
) ||
matchesWorktreeCardProperties(
properties,
LEGACY_NORMALIZED_COMPACT_WORKTREE_CARD_PROPERTIES_WITH_AUTOMATION
)
)
}
function matchesWorktreeCardProperties(
properties: readonly unknown[] | null | undefined,
expected: readonly WorktreeCardProperty[]
): boolean {
if (properties?.length !== expected.length) {
return false
}
return expected.every((property, index) => properties[index] === property)
}