mirror of
https://github.com/stablyai/orca.git
synced 2026-09-26 16:02:43 +00:00
#14397 split `shared/types.ts` into 46 per-domain modules but kept the path as a re-export barrel so the import sites did not have to change. This removes the barrel: every consumer now imports from the module that actually declares the type, and `src/shared/types.ts` is deleted. Barrels hide where a type lives, make every consumer look like it depends on the whole domain, and let an unrelated edit invalidate a module that ~2,000 files transitively import. 2,323 import declarations across 2,321 files. Rewritten mechanically: each specifier was resolved to an absolute path via the TypeScript AST and recomputed, rather than string-substituted, so alias forms (`@/../../shared/ types`) and per-specifier `type` modifiers survive. Four cases the mechanical pass had to handle, each found by a gate rather than by reading the diff: - Modules inside `src/shared` import the barrel as `./types`, not `shared/types`. A pre-filter on the latter string skipped 176 of them and left imports dangling at a deleted file, which surfaced as confusing `Property 'x' is optional in type 'Repo' but required in Pick<Repo, ...>` errors rather than "module not found". - The barrel RENAMED one type on the way through (`WorkspaceSource as WorkspaceCreateTelemetrySource`), so the original name in the owning module has to be re-aliased at each consumer. - Three test files put `;(globalThis as ...)` on the line after the import. TypeScript parses that `;` as the import statement's terminator, so replacing through `statement.getEnd()` deletes it and breaks ASI. The rewrite now stops at the module specifier. - A file that already imported directly from a module got a SECOND import from it, because the barrel re-exported those same names — which trips `import/no-duplicates` under `--deny-warnings`. A post-pass merges declarations sharing a specifier and type-only-ness; the `import type` plus `import` pair from one module is left alone, since that form is allowed. Splitting one barrel import into several genuinely adds lines, which pushed `terminal-layout-pty-ownership.ts` to 301 counted lines: its 107-character import must wrap, and neither local type collapses onto one line (101 and 116 characters). Rather than contort a type declaration to fit a line budget, `collectLeafIds` and `pruneLeaves` move to `terminal-pane-layout-tree.ts` — they are pure structural operations on the layout tree and independent of PTY ownership. `visible-worktrees.ts` similarly loses its own mini-barrel re-export of `isDefaultBranchWorkspace`, with the four real consumers repointed at the declaring module. No `max-lines` bypass added. Verified: cold `tsc --noEmit` green on node, cli, and web (buildinfo deleted first — these projects are `composite: true` and reuse stale caches); the full `pnpm lint` green, not just bare oxlint — the narrower local check is what let the duplicate imports reach CI; max-lines ratchet OK at 344.
131 lines
5.5 KiB
TypeScript
131 lines
5.5 KiB
TypeScript
import { Image, StyleSheet, Text, View } from 'react-native'
|
|
import type { ImageSourcePropType } from 'react-native'
|
|
import { Terminal } from 'lucide-react-native'
|
|
import type { TuiAgent } from '../../../src/shared/tui-agent'
|
|
import Svg, { Defs, G, LinearGradient, Path, Stop } from 'react-native-svg'
|
|
import { colors } from '../theme/mobile-theme'
|
|
import { MOBILE_AGENT_CATALOG } from '../tasks/mobile-agent-catalog'
|
|
import { MOBILE_AGENT_ICON_ASSETS } from './mobile-agent-icon-assets'
|
|
import { ClaudeIcon, OpenAIIcon } from './AgentIcons'
|
|
|
|
// Why: agent branding should match the desktop/new-worktree picker everywhere
|
|
// mobile lets users choose the agent that will own a workspace.
|
|
|
|
function PiIcon({ size = 16 }: { size?: number }) {
|
|
return (
|
|
<Svg width={size} height={size} viewBox="0 0 800 800">
|
|
<Path
|
|
fill={colors.textPrimary}
|
|
fillRule="evenodd"
|
|
d="M165.29 165.29 H517.36 V400 H400 V517.36 H282.65 V634.72 H165.29 Z M282.65 282.65 V400 H400 V282.65 Z"
|
|
/>
|
|
<Path fill={colors.textPrimary} d="M517.36 400 H634.72 V634.72 H517.36 Z" />
|
|
</Svg>
|
|
)
|
|
}
|
|
|
|
function OmpIcon({ size = 16 }: { size?: number }) {
|
|
// SVG sourced from omp.sh's transparent homepage mark. Why: react-native-svg
|
|
// does not support the homepage's CSS oklch stops, so use its favicon hex stops.
|
|
return (
|
|
<Svg width={size} height={size} viewBox="0 0 64 64">
|
|
<Defs>
|
|
<LinearGradient id="ompMarkGradient" x1="0" y1="0" x2="1" y2="1">
|
|
<Stop offset="0" stopColor="#ed4abf" />
|
|
<Stop offset="0.5" stopColor="#9b4dff" />
|
|
<Stop offset="1" stopColor="#5ad8e6" />
|
|
</LinearGradient>
|
|
</Defs>
|
|
<Path fill="url(#ompMarkGradient)" d="M10 14h44v9H43v33h-9V23h-9v22h-9V23H10z" />
|
|
</Svg>
|
|
)
|
|
}
|
|
|
|
function AiderIcon({ size = 16 }: { size?: number }) {
|
|
return (
|
|
<Svg width={size} height={size} viewBox="0 0 436 436">
|
|
<G transform="translate(0,436) scale(0.1,-0.1)" fill={colors.textPrimary} stroke="none">
|
|
<Path d="M0 2180 l0 -2180 2180 0 2180 0 0 2180 0 2180 -2180 0 -2180 0 0 -2180z m2705 1818 c20 -20 28 -121 30 -398 l2 -305 216 -5 c118 -3 218 -8 222 -12 3 -3 10 -46 15 -95 5 -48 16 -126 25 -172 17 -86 17 -81 -17 -233 -14 -67 -13 -365 2 -438 21 -100 22 -159 5 -247 -24 -122 -24 -363 1 -458 23 -88 23 -213 1 -330 -9 -49 -17 -109 -17 -132 l0 -43 203 0 c111 0 208 -4 216 -9 10 -6 18 -51 27 -148 8 -76 16 -152 20 -168 7 -39 -23 -361 -37 -387 -10 -18 -21 -19 -214 -16 -135 2 -208 7 -215 14 -22 22 -33 301 -21 501 6 102 8 189 5 194 -8 13 -417 12 -431 -2 -12 -12 -8 -146 8 -261 8 -55 8 -95 1 -140 -6 -35 -14 -99 -17 -143 -9 -123 -14 -141 -41 -154 -18 -8 -217 -11 -679 -11 l-653 0 -11 33 c-31 97 -43 336 -27 533 5 56 6 113 2 128 l-6 26 -194 0 c-211 0 -252 4 -261 28 -12 33 -17 392 -6 522 15 186 -2 174 260 180 115 3 213 8 217 12 4 4 1 52 -5 105 -7 54 -17 130 -22 168 -7 56 -5 91 11 171 10 55 22 130 26 166 4 36 10 72 15 79 7 12 128 15 665 19 l658 5 8 30 c5 18 4 72 -3 130 -12 115 -7 346 11 454 10 61 10 75 -1 82 -8 5 -300 9 -650 9 l-636 0 -27 25 c-18 16 -26 34 -26 57 0 18 -5 87 -10 153 -10 128 5 449 22 472 5 7 26 13 46 15 78 6 1281 3 1287 -4z" />
|
|
<Path d="M1360 1833 c0 -5 -1 -164 -3 -356 l-2 -347 625 -1 c704 -1 708 -1 722 7 5 4 7 20 4 38 -29 141 -32 491 -6 595 9 38 8 45 -7 57 -15 11 -139 13 -675 14 -362 0 -658 -3 -658 -7z" />
|
|
</G>
|
|
</Svg>
|
|
)
|
|
}
|
|
|
|
function BundledIcon({ source, size = 16 }: { source: ImageSourcePropType; size?: number }) {
|
|
return <Image source={source} style={{ width: size, height: size, borderRadius: 2 }} />
|
|
}
|
|
|
|
function FaviconIcon({ domain, size = 16 }: { domain: string; size?: number }) {
|
|
return (
|
|
<Image
|
|
source={{ uri: `https://www.google.com/s2/favicons?domain=${domain}&sz=64` }}
|
|
style={{ width: size, height: size, borderRadius: 2 }}
|
|
/>
|
|
)
|
|
}
|
|
|
|
function AgentLetterIcon({ letter, size = 16 }: { letter: string; size?: number }) {
|
|
return (
|
|
<View
|
|
style={[
|
|
styles.letterIcon,
|
|
{
|
|
width: size,
|
|
height: size,
|
|
borderRadius: size * 0.22,
|
|
backgroundColor: colors.textMuted + '33'
|
|
}
|
|
]}
|
|
>
|
|
<Text style={[styles.letterIconText, { fontSize: size * 0.55, color: colors.textPrimary }]}>
|
|
{letter}
|
|
</Text>
|
|
</View>
|
|
)
|
|
}
|
|
|
|
export function MobileAgentIcon({ agentId, size = 16 }: { agentId: string; size?: number }) {
|
|
if (agentId === 'claude' || agentId === 'claude-agent-teams') {
|
|
return <ClaudeIcon size={size} />
|
|
}
|
|
if (agentId === 'codex') {
|
|
return <OpenAIIcon size={size} />
|
|
}
|
|
if (agentId === 'pi') {
|
|
return <PiIcon size={size} />
|
|
}
|
|
if (agentId === 'omp') {
|
|
return <OmpIcon size={size} />
|
|
}
|
|
if (agentId === 'aider') {
|
|
return <AiderIcon size={size} />
|
|
}
|
|
if (agentId === '__blank__' || agentId === 'blank') {
|
|
return <Terminal size={size} color={colors.textMuted} />
|
|
}
|
|
|
|
// Why: prefer the favicon bundled into the app over Google's favicon service,
|
|
// which is unreachable in some regions and offline (#8451).
|
|
const bundledIcon = MOBILE_AGENT_ICON_ASSETS[agentId as TuiAgent]
|
|
if (bundledIcon) {
|
|
return <BundledIcon source={bundledIcon} size={size} />
|
|
}
|
|
const agent = MOBILE_AGENT_CATALOG.find((entry) => entry.id === agentId)
|
|
if (agent?.faviconDomain) {
|
|
return <FaviconIcon domain={agent.faviconDomain} size={size} />
|
|
}
|
|
const label = agent?.label ?? agentId
|
|
return <AgentLetterIcon letter={label.charAt(0).toUpperCase()} size={size} />
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
letterIcon: {
|
|
alignItems: 'center',
|
|
justifyContent: 'center'
|
|
},
|
|
letterIconText: {
|
|
fontWeight: '700'
|
|
}
|
|
})
|