@omercnet/paseo-gas-city 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/LICENSE +21 -0
- package/README.md +140 -0
- package/bun.lock +1217 -0
- package/client/city-operations.tsx +1505 -0
- package/client/contribute.tsx +96 -0
- package/client/dispatch-intent.ts +53 -0
- package/client/factory-panel.tsx +373 -0
- package/client/gas-city-surface.tsx +343 -0
- package/client/settings-screen.tsx +286 -0
- package/client/view-model.ts +318 -0
- package/docs/images/paseo-gas-city-compact-overview.webp +0 -0
- package/docs/images/paseo-gas-city-dispatch-confirmation.webp +0 -0
- package/docs/images/paseo-gas-city-wide-events.webp +0 -0
- package/docs/images/paseo-gas-city-wide-overview.webp +0 -0
- package/icon.svg +5 -0
- package/index.client.tsx +1 -0
- package/index.server.ts +42 -0
- package/package.json +73 -0
- package/paseo-plugin.json +4 -0
- package/server/gas-city-client.ts +668 -0
- package/server/handlers.ts +790 -0
- package/server/workspace-mapping.ts +170 -0
- package/shared/index.ts +4 -0
- package/shared/limits.ts +28 -0
- package/shared/rpc.ts +99 -0
- package/shared/schemas.ts +430 -0
- package/shared/settings.ts +89 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { PluginClientContext } from "@getpaseo/plugin/client";
|
|
2
|
+
import { queueSlingIntent } from "./dispatch-intent";
|
|
3
|
+
import { FactoryPanel } from "./factory-panel";
|
|
4
|
+
import { GasCitySurface } from "./gas-city-surface";
|
|
5
|
+
import { GasCitySettingsScreen } from "./settings-screen";
|
|
6
|
+
|
|
7
|
+
export function registerGasCityClient(client: PluginClientContext) {
|
|
8
|
+
const cleanups = [
|
|
9
|
+
client.addSettingsScreen({
|
|
10
|
+
id: "gas-city",
|
|
11
|
+
title: "Gas City settings",
|
|
12
|
+
icon: "Settings",
|
|
13
|
+
Component: GasCitySettingsScreen,
|
|
14
|
+
}),
|
|
15
|
+
client.addSurface("gas-city", GasCitySurface),
|
|
16
|
+
client.addSidebarItem({
|
|
17
|
+
id: "gas-city",
|
|
18
|
+
title: "Gas City",
|
|
19
|
+
icon: "Factory",
|
|
20
|
+
surface: "gas-city",
|
|
21
|
+
}),
|
|
22
|
+
client.addWorkspacePanel({
|
|
23
|
+
id: "gas-city-factory",
|
|
24
|
+
title: "Factory",
|
|
25
|
+
icon: "Factory",
|
|
26
|
+
context: "workspace",
|
|
27
|
+
locations: ["workspace", "explorer"],
|
|
28
|
+
Component: FactoryPanel,
|
|
29
|
+
}),
|
|
30
|
+
client.addCommandCenterItem({
|
|
31
|
+
id: "open-gas-city",
|
|
32
|
+
title: "Open Gas City",
|
|
33
|
+
icon: "Factory",
|
|
34
|
+
keywords: ["gas city", "supervisor", "sessions", "convoys", "operator"],
|
|
35
|
+
context: "global",
|
|
36
|
+
onSelect({ openSurface }) {
|
|
37
|
+
openSurface("gas-city");
|
|
38
|
+
},
|
|
39
|
+
}),
|
|
40
|
+
client.addCommandCenterItem({
|
|
41
|
+
id: "configure-gas-city",
|
|
42
|
+
title: "Configure Gas City",
|
|
43
|
+
icon: "Settings",
|
|
44
|
+
keywords: ["gas city", "endpoint", "observe only", "mutations"],
|
|
45
|
+
context: "global",
|
|
46
|
+
onSelect({ openSettings }) {
|
|
47
|
+
openSettings("gas-city");
|
|
48
|
+
},
|
|
49
|
+
}),
|
|
50
|
+
client.addCommandCenterItem({
|
|
51
|
+
id: "open-gas-city-factory",
|
|
52
|
+
title: "Open Gas City Factory",
|
|
53
|
+
icon: "Factory",
|
|
54
|
+
keywords: ["gas city", "workspace", "rig", "sessions", "convoys"],
|
|
55
|
+
context: "workspace",
|
|
56
|
+
onSelect({ openPanel }) {
|
|
57
|
+
openPanel("gas-city-factory");
|
|
58
|
+
},
|
|
59
|
+
}),
|
|
60
|
+
client.addCommandCenterItem({
|
|
61
|
+
id: "sling-gas-city-work",
|
|
62
|
+
title: "Sling work in Gas City",
|
|
63
|
+
icon: "Send",
|
|
64
|
+
keywords: ["gas city", "dispatch", "bead", "agent role"],
|
|
65
|
+
context: "workspace",
|
|
66
|
+
onSelect({ workspace, openPanel }) {
|
|
67
|
+
queueSlingIntent(workspace.id, "");
|
|
68
|
+
openPanel("gas-city-factory");
|
|
69
|
+
},
|
|
70
|
+
}),
|
|
71
|
+
client.addCommandCenterItem({
|
|
72
|
+
id: "open-agent-gas-city-factory",
|
|
73
|
+
title: "Open Gas City Factory",
|
|
74
|
+
icon: "Factory",
|
|
75
|
+
keywords: ["gas city", "workspace", "agent", "session"],
|
|
76
|
+
context: "agent",
|
|
77
|
+
onSelect({ openPanel }) {
|
|
78
|
+
openPanel("gas-city-factory");
|
|
79
|
+
},
|
|
80
|
+
}),
|
|
81
|
+
client.addSlashCommand({
|
|
82
|
+
name: "sling",
|
|
83
|
+
description: "Confirm and dispatch a Gas City bead to an agent role",
|
|
84
|
+
argumentHint: "<bead-id> [agent-role]",
|
|
85
|
+
context: "workspace",
|
|
86
|
+
onSubmit({ workspace, args, openPanel }) {
|
|
87
|
+
queueSlingIntent(workspace.id, args);
|
|
88
|
+
openPanel("gas-city-factory");
|
|
89
|
+
},
|
|
90
|
+
}),
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
return () => {
|
|
94
|
+
for (const cleanup of cleanups.reverse()) cleanup();
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { useSyncExternalStore } from "react";
|
|
2
|
+
import { parseSlingArguments, type SlingArguments } from "./view-model";
|
|
3
|
+
|
|
4
|
+
export interface SlingIntent extends SlingArguments {
|
|
5
|
+
id: number;
|
|
6
|
+
parseError: string | null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const intents = new Map<string, SlingIntent>();
|
|
10
|
+
const listeners = new Set<() => void>();
|
|
11
|
+
let nextId = 1;
|
|
12
|
+
|
|
13
|
+
function emit() {
|
|
14
|
+
for (const listener of listeners) listener();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function queueSlingIntent(workspaceId: string, args: string): SlingIntent {
|
|
18
|
+
const parsed = parseSlingArguments(args);
|
|
19
|
+
const intent: SlingIntent = {
|
|
20
|
+
id: nextId++,
|
|
21
|
+
beadId: parsed?.beadId ?? "",
|
|
22
|
+
agent: parsed?.agent ?? "",
|
|
23
|
+
parseError: parsed
|
|
24
|
+
? null
|
|
25
|
+
: "Use /sling <bead-id> [agent-role]. Quote roles that contain spaces.",
|
|
26
|
+
};
|
|
27
|
+
intents.set(workspaceId, intent);
|
|
28
|
+
emit();
|
|
29
|
+
return intent;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function getSlingIntent(workspaceId: string): SlingIntent | null {
|
|
33
|
+
return intents.get(workspaceId) ?? null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function dismissSlingIntent(workspaceId: string, id: number): void {
|
|
37
|
+
if (intents.get(workspaceId)?.id !== id) return;
|
|
38
|
+
intents.delete(workspaceId);
|
|
39
|
+
emit();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function subscribeSlingIntent(listener: () => void): () => void {
|
|
43
|
+
listeners.add(listener);
|
|
44
|
+
return () => listeners.delete(listener);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function useSlingIntent(workspaceId: string): SlingIntent | null {
|
|
48
|
+
return useSyncExternalStore(
|
|
49
|
+
subscribeSlingIntent,
|
|
50
|
+
() => getSlingIntent(workspaceId),
|
|
51
|
+
() => null,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type PluginWorkspacePanelProps,
|
|
3
|
+
useRpc,
|
|
4
|
+
useSettings,
|
|
5
|
+
useWorkspace,
|
|
6
|
+
} from "@getpaseo/plugin/client";
|
|
7
|
+
import { Icon } from "@getpaseo/plugin/client/react-native";
|
|
8
|
+
import { useQuery } from "@tanstack/react-query";
|
|
9
|
+
import { useCallback, useMemo } from "react";
|
|
10
|
+
import { ActivityIndicator, Pressable, ScrollView, StyleSheet, Text, View } from "react-native";
|
|
11
|
+
import {
|
|
12
|
+
type GasCitySettings,
|
|
13
|
+
gasCitySettings,
|
|
14
|
+
resolveWorkspaceRig,
|
|
15
|
+
toGasCityRpcSettings,
|
|
16
|
+
type WorkspaceRigMapping,
|
|
17
|
+
} from "../shared";
|
|
18
|
+
import { CityOperations } from "./city-operations";
|
|
19
|
+
import { dismissSlingIntent, useSlingIntent } from "./dispatch-intent";
|
|
20
|
+
|
|
21
|
+
function errorMessage(error: unknown): string {
|
|
22
|
+
return error instanceof Error ? error.message : "An unexpected error occurred.";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function FactoryPanel(props: PluginWorkspacePanelProps) {
|
|
26
|
+
const settings = useSettings(gasCitySettings);
|
|
27
|
+
if (settings.status === "loading") {
|
|
28
|
+
return (
|
|
29
|
+
<FactoryState
|
|
30
|
+
{...props}
|
|
31
|
+
title="Loading Factory"
|
|
32
|
+
body="Reading persisted Gas City settings."
|
|
33
|
+
loading
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
if (settings.status !== "ready") {
|
|
38
|
+
return (
|
|
39
|
+
<FactoryState
|
|
40
|
+
{...props}
|
|
41
|
+
title="Factory settings unavailable"
|
|
42
|
+
body={settings.error}
|
|
43
|
+
onRetry={() => void settings.reload()}
|
|
44
|
+
/>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
return (
|
|
48
|
+
<ReadyFactoryPanel
|
|
49
|
+
key={`${props.workspaceId}:${settings.revision}`}
|
|
50
|
+
{...props}
|
|
51
|
+
settings={settings.values}
|
|
52
|
+
/>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function ReadyFactoryPanel({
|
|
57
|
+
theme,
|
|
58
|
+
layout,
|
|
59
|
+
host,
|
|
60
|
+
navigation,
|
|
61
|
+
workspaceId,
|
|
62
|
+
settings,
|
|
63
|
+
}: PluginWorkspacePanelProps & { settings: GasCitySettings }) {
|
|
64
|
+
const styles = useMemo(() => createStyles(theme, layout.compact), [layout.compact, theme]);
|
|
65
|
+
const rpcSettings = useMemo(() => toGasCityRpcSettings(settings), [settings]);
|
|
66
|
+
const workspace = useWorkspace(workspaceId, ({ directory, name, title }) => ({
|
|
67
|
+
directory,
|
|
68
|
+
name,
|
|
69
|
+
title,
|
|
70
|
+
}));
|
|
71
|
+
const loadMapping = useRpc(resolveWorkspaceRig);
|
|
72
|
+
const mapping = useQuery({
|
|
73
|
+
queryKey: ["gas-city", host.id, settings.endpointUrl, "workspace-mapping", workspaceId],
|
|
74
|
+
queryFn: () => loadMapping({ settings: rpcSettings, workspaceId }),
|
|
75
|
+
refetchInterval: settings.refreshIntervalMs,
|
|
76
|
+
});
|
|
77
|
+
const retryMapping = () => void mapping.refetch();
|
|
78
|
+
const slingIntent = useSlingIntent(workspaceId);
|
|
79
|
+
const dismissIntent = useCallback(
|
|
80
|
+
(id: number) => dismissSlingIntent(workspaceId, id),
|
|
81
|
+
[workspaceId],
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
if (mapping.isPending && !mapping.data) {
|
|
85
|
+
return (
|
|
86
|
+
<FactoryState
|
|
87
|
+
theme={theme}
|
|
88
|
+
layout={layout}
|
|
89
|
+
host={host}
|
|
90
|
+
navigation={navigation}
|
|
91
|
+
context="workspace"
|
|
92
|
+
workspaceId={workspaceId}
|
|
93
|
+
title="Mapping workspace"
|
|
94
|
+
body="Resolving this workspace to a Gas City rig."
|
|
95
|
+
loading
|
|
96
|
+
/>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
if (mapping.error && !mapping.data) {
|
|
100
|
+
return (
|
|
101
|
+
<FactoryState
|
|
102
|
+
theme={theme}
|
|
103
|
+
layout={layout}
|
|
104
|
+
host={host}
|
|
105
|
+
navigation={navigation}
|
|
106
|
+
context="workspace"
|
|
107
|
+
workspaceId={workspaceId}
|
|
108
|
+
title="Could not map workspace"
|
|
109
|
+
body={errorMessage(mapping.error)}
|
|
110
|
+
onRetry={retryMapping}
|
|
111
|
+
/>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
if (!mapping.data) {
|
|
115
|
+
return (
|
|
116
|
+
<FactoryState
|
|
117
|
+
theme={theme}
|
|
118
|
+
layout={layout}
|
|
119
|
+
host={host}
|
|
120
|
+
navigation={navigation}
|
|
121
|
+
context="workspace"
|
|
122
|
+
workspaceId={workspaceId}
|
|
123
|
+
title="No workspace mapping"
|
|
124
|
+
body="The supervisor returned no usable mapping result."
|
|
125
|
+
onRetry={retryMapping}
|
|
126
|
+
/>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const resolved = mapping.data;
|
|
131
|
+
if (resolved.state !== "mapped" || !resolved.cityName || !resolved.rigName) {
|
|
132
|
+
return (
|
|
133
|
+
<MappingState
|
|
134
|
+
theme={theme}
|
|
135
|
+
layout={layout}
|
|
136
|
+
host={host}
|
|
137
|
+
navigation={navigation}
|
|
138
|
+
context="workspace"
|
|
139
|
+
workspaceId={workspaceId}
|
|
140
|
+
mapping={resolved}
|
|
141
|
+
onRetry={retryMapping}
|
|
142
|
+
/>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return (
|
|
147
|
+
<View style={styles.screen}>
|
|
148
|
+
<View style={styles.mappingBar}>
|
|
149
|
+
<View style={styles.mappingIcon}>
|
|
150
|
+
<Icon name="Factory" size={15} color={theme.colors.accent} />
|
|
151
|
+
</View>
|
|
152
|
+
<View style={styles.mappingText}>
|
|
153
|
+
<Text style={styles.mappingTitle} numberOfLines={1}>
|
|
154
|
+
{workspace?.title?.trim() || workspace?.name || "Workspace Factory"}
|
|
155
|
+
</Text>
|
|
156
|
+
<Text style={styles.mappingMeta} numberOfLines={1}>
|
|
157
|
+
{resolved.cityName} / {resolved.rigName} ·{" "}
|
|
158
|
+
{resolved.source === "explicit" ? "explicit mapping" : "path mapping"}
|
|
159
|
+
</Text>
|
|
160
|
+
</View>
|
|
161
|
+
{mapping.isFetching ? <ActivityIndicator size="small" color={theme.colors.accent} /> : null}
|
|
162
|
+
</View>
|
|
163
|
+
{resolved.diagnostics.length > 0 ? (
|
|
164
|
+
<View accessibilityRole="alert" style={styles.diagnostics}>
|
|
165
|
+
<Icon name="TriangleAlert" size={14} color={theme.colors.statusWarning} />
|
|
166
|
+
<Text style={styles.diagnosticText} numberOfLines={2}>
|
|
167
|
+
{resolved.diagnostics.map((diagnostic) => diagnostic.message).join(" · ")}
|
|
168
|
+
</Text>
|
|
169
|
+
</View>
|
|
170
|
+
) : null}
|
|
171
|
+
<View style={styles.body}>
|
|
172
|
+
<CityOperations
|
|
173
|
+
key={`${settings.endpointUrl}:${resolved.cityName}:${resolved.rigName}`}
|
|
174
|
+
theme={theme}
|
|
175
|
+
layout={layout}
|
|
176
|
+
host={host}
|
|
177
|
+
cityName={resolved.cityName}
|
|
178
|
+
rigName={resolved.rigName}
|
|
179
|
+
settings={settings}
|
|
180
|
+
slingIntent={slingIntent}
|
|
181
|
+
onDismissSlingIntent={dismissIntent}
|
|
182
|
+
/>
|
|
183
|
+
</View>
|
|
184
|
+
</View>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function MappingState({
|
|
189
|
+
theme,
|
|
190
|
+
layout,
|
|
191
|
+
host,
|
|
192
|
+
navigation,
|
|
193
|
+
workspaceId,
|
|
194
|
+
mapping,
|
|
195
|
+
onRetry,
|
|
196
|
+
}: PluginWorkspacePanelProps & { mapping: WorkspaceRigMapping; onRetry: () => void }) {
|
|
197
|
+
const styles = useMemo(() => createStyles(theme, layout.compact), [layout.compact, theme]);
|
|
198
|
+
const title =
|
|
199
|
+
mapping.state === "ambiguous"
|
|
200
|
+
? "Choose an explicit mapping"
|
|
201
|
+
: mapping.state === "unavailable"
|
|
202
|
+
? "Supervisor mapping unavailable"
|
|
203
|
+
: "Workspace is not mapped";
|
|
204
|
+
const body =
|
|
205
|
+
mapping.diagnostics.map((diagnostic) => diagnostic.message).join(" ") ||
|
|
206
|
+
(mapping.state === "ambiguous"
|
|
207
|
+
? "Multiple Gas City rigs contain this workspace. Add an explicit override in Gas City settings."
|
|
208
|
+
: "No Gas City rig contains this workspace path. Add an explicit override in Gas City settings.");
|
|
209
|
+
return (
|
|
210
|
+
<View style={styles.mappingStateScreen}>
|
|
211
|
+
<FactoryState
|
|
212
|
+
theme={theme}
|
|
213
|
+
layout={layout}
|
|
214
|
+
host={host}
|
|
215
|
+
navigation={navigation}
|
|
216
|
+
context="workspace"
|
|
217
|
+
workspaceId={workspaceId}
|
|
218
|
+
title={title}
|
|
219
|
+
body={body}
|
|
220
|
+
onRetry={onRetry}
|
|
221
|
+
/>
|
|
222
|
+
{mapping.candidates.length > 0 ? (
|
|
223
|
+
<ScrollView style={styles.candidates} contentContainerStyle={styles.candidatesContent}>
|
|
224
|
+
<Text accessibilityRole="header" style={styles.candidatesTitle}>
|
|
225
|
+
Mapping candidates
|
|
226
|
+
</Text>
|
|
227
|
+
{mapping.candidates.map((candidate) => (
|
|
228
|
+
<View key={`${candidate.cityName}:${candidate.rigPath}`} style={styles.candidateRow}>
|
|
229
|
+
<Icon name="GitBranch" size={14} color={theme.colors.foregroundMuted} />
|
|
230
|
+
<View style={styles.mappingText}>
|
|
231
|
+
<Text style={styles.mappingTitle}>
|
|
232
|
+
{candidate.cityName} / {candidate.rigName}
|
|
233
|
+
</Text>
|
|
234
|
+
<Text style={styles.mappingMeta} numberOfLines={1}>
|
|
235
|
+
{candidate.rigPath}
|
|
236
|
+
</Text>
|
|
237
|
+
</View>
|
|
238
|
+
</View>
|
|
239
|
+
))}
|
|
240
|
+
</ScrollView>
|
|
241
|
+
) : null}
|
|
242
|
+
</View>
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function FactoryState({
|
|
247
|
+
theme,
|
|
248
|
+
title,
|
|
249
|
+
body,
|
|
250
|
+
loading,
|
|
251
|
+
onRetry,
|
|
252
|
+
}: PluginWorkspacePanelProps & {
|
|
253
|
+
title: string;
|
|
254
|
+
body: string;
|
|
255
|
+
loading?: boolean;
|
|
256
|
+
onRetry?: () => void;
|
|
257
|
+
}) {
|
|
258
|
+
const styles = useMemo(() => createStyles(theme, true), [theme]);
|
|
259
|
+
return (
|
|
260
|
+
<View style={styles.stateCard}>
|
|
261
|
+
{loading ? (
|
|
262
|
+
<ActivityIndicator color={theme.colors.accent} />
|
|
263
|
+
) : (
|
|
264
|
+
<Icon name="Factory" size={28} color={theme.colors.foregroundMuted} />
|
|
265
|
+
)}
|
|
266
|
+
<Text accessibilityRole="header" style={styles.stateTitle}>
|
|
267
|
+
{title}
|
|
268
|
+
</Text>
|
|
269
|
+
<Text style={styles.stateBody}>{body}</Text>
|
|
270
|
+
{onRetry ? (
|
|
271
|
+
<Pressable
|
|
272
|
+
accessibilityRole="button"
|
|
273
|
+
accessibilityLabel="Retry Gas City workspace mapping"
|
|
274
|
+
onPress={onRetry}
|
|
275
|
+
style={({ pressed }) => [styles.retryButton, pressed && styles.pressed]}
|
|
276
|
+
>
|
|
277
|
+
<Text style={styles.retryText}>Retry</Text>
|
|
278
|
+
</Pressable>
|
|
279
|
+
) : null}
|
|
280
|
+
</View>
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function createStyles(theme: PluginWorkspacePanelProps["theme"], compact: boolean) {
|
|
285
|
+
return StyleSheet.create({
|
|
286
|
+
screen: { flex: 1, backgroundColor: theme.colors.surface0 },
|
|
287
|
+
body: { flex: 1 },
|
|
288
|
+
mappingBar: {
|
|
289
|
+
minHeight: 54,
|
|
290
|
+
flexDirection: "row",
|
|
291
|
+
alignItems: "center",
|
|
292
|
+
gap: 9,
|
|
293
|
+
paddingHorizontal: compact ? 12 : 18,
|
|
294
|
+
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
295
|
+
borderBottomColor: theme.colors.border,
|
|
296
|
+
backgroundColor: theme.colors.surface0,
|
|
297
|
+
},
|
|
298
|
+
mappingIcon: {
|
|
299
|
+
width: 30,
|
|
300
|
+
height: 30,
|
|
301
|
+
alignItems: "center",
|
|
302
|
+
justifyContent: "center",
|
|
303
|
+
borderRadius: 6,
|
|
304
|
+
backgroundColor: theme.colors.surface0,
|
|
305
|
+
},
|
|
306
|
+
mappingText: { flex: 1, minWidth: 0, gap: 2 },
|
|
307
|
+
mappingTitle: { color: theme.colors.foreground, fontSize: 12, fontWeight: "700" },
|
|
308
|
+
mappingMeta: { color: theme.colors.foregroundMuted, fontSize: 10 },
|
|
309
|
+
diagnostics: {
|
|
310
|
+
flexDirection: "row",
|
|
311
|
+
alignItems: "center",
|
|
312
|
+
gap: 7,
|
|
313
|
+
paddingHorizontal: compact ? 12 : 18,
|
|
314
|
+
paddingVertical: 8,
|
|
315
|
+
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
316
|
+
borderBottomColor: theme.colors.border,
|
|
317
|
+
},
|
|
318
|
+
diagnosticText: { flex: 1, color: theme.colors.statusWarning, fontSize: 10 },
|
|
319
|
+
mappingStateScreen: { flex: 1, backgroundColor: theme.colors.surface0 },
|
|
320
|
+
stateCard: {
|
|
321
|
+
alignItems: "center",
|
|
322
|
+
justifyContent: "center",
|
|
323
|
+
gap: 9,
|
|
324
|
+
padding: 24,
|
|
325
|
+
backgroundColor: theme.colors.surface0,
|
|
326
|
+
},
|
|
327
|
+
stateTitle: {
|
|
328
|
+
color: theme.colors.foreground,
|
|
329
|
+
fontSize: 17,
|
|
330
|
+
fontWeight: "800",
|
|
331
|
+
textAlign: "center",
|
|
332
|
+
},
|
|
333
|
+
stateBody: {
|
|
334
|
+
maxWidth: 480,
|
|
335
|
+
color: theme.colors.foregroundMuted,
|
|
336
|
+
fontSize: 12,
|
|
337
|
+
lineHeight: 18,
|
|
338
|
+
textAlign: "center",
|
|
339
|
+
},
|
|
340
|
+
retryButton: {
|
|
341
|
+
minHeight: 36,
|
|
342
|
+
justifyContent: "center",
|
|
343
|
+
paddingHorizontal: 13,
|
|
344
|
+
borderRadius: 6,
|
|
345
|
+
backgroundColor: theme.colors.accent,
|
|
346
|
+
},
|
|
347
|
+
retryText: { color: theme.colors.accentForeground, fontSize: 12, fontWeight: "700" },
|
|
348
|
+
candidates: {
|
|
349
|
+
flex: 1,
|
|
350
|
+
borderTopWidth: StyleSheet.hairlineWidth,
|
|
351
|
+
borderTopColor: theme.colors.border,
|
|
352
|
+
},
|
|
353
|
+
candidatesContent: { gap: 7, padding: compact ? 12 : 18 },
|
|
354
|
+
candidatesTitle: {
|
|
355
|
+
color: theme.colors.foreground,
|
|
356
|
+
fontSize: 10,
|
|
357
|
+
fontWeight: "700",
|
|
358
|
+
letterSpacing: 0.9,
|
|
359
|
+
textTransform: "uppercase",
|
|
360
|
+
marginBottom: 4,
|
|
361
|
+
},
|
|
362
|
+
candidateRow: {
|
|
363
|
+
flexDirection: "row",
|
|
364
|
+
alignItems: "center",
|
|
365
|
+
gap: 8,
|
|
366
|
+
paddingVertical: 12,
|
|
367
|
+
paddingHorizontal: 10,
|
|
368
|
+
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
369
|
+
borderColor: theme.colors.border,
|
|
370
|
+
},
|
|
371
|
+
pressed: { opacity: 0.72 },
|
|
372
|
+
});
|
|
373
|
+
}
|