@clef-sh/ui 0.1.15 → 0.1.16

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.
@@ -19,6 +19,7 @@ interface IdentityInfo {
19
19
  description: string;
20
20
  namespaces: string[];
21
21
  environments: Record<string, EnvInfo>;
22
+ packOnly?: boolean;
22
23
  }
23
24
 
24
25
  interface EnvBackendConfig {
@@ -49,12 +50,16 @@ export function ServiceIdentitiesScreen({ manifest }: ServiceIdentitiesScreenPro
49
50
  const [description, setDescription] = useState("");
50
51
  const [selectedNamespaces, setSelectedNamespaces] = useState<Set<string>>(new Set());
51
52
  const [envBackends, setEnvBackends] = useState<Record<string, EnvBackendConfig>>({});
53
+ const [role, setRole] = useState<"ci" | "runtime">("ci");
54
+ const [sharedRecipient, setSharedRecipient] = useState(true); // CI default
55
+ const [sharedRecipientOverridden, setSharedRecipientOverridden] = useState(false);
52
56
  const [creating, setCreating] = useState(false);
53
57
  const [createError, setCreateError] = useState("");
54
58
 
55
59
  // Post-create / post-rotate keys
56
60
  const [privateKeys, setPrivateKeys] = useState<Record<string, string>>({});
57
61
  const [createdName, setCreatedName] = useState("");
62
+ const [wasSharedRecipient, setWasSharedRecipient] = useState(false);
58
63
 
59
64
  // Update form state
60
65
  const [updateEnvBackends, setUpdateEnvBackends] = useState<Record<string, UpdateEnvState>>({});
@@ -96,6 +101,9 @@ export function ServiceIdentitiesScreen({ manifest }: ServiceIdentitiesScreenPro
96
101
  defaults[env.name] = { type: "age", provider: "aws", keyId: "" };
97
102
  }
98
103
  setEnvBackends(defaults);
104
+ setRole("ci");
105
+ setSharedRecipient(true); // CI default
106
+ setSharedRecipientOverridden(false);
99
107
  setCreateError("");
100
108
  setView("create");
101
109
  }, [manifest]);
@@ -146,7 +154,12 @@ export function ServiceIdentitiesScreen({ manifest }: ServiceIdentitiesScreenPro
146
154
  description: description.trim(),
147
155
  namespaces: Array.from(selectedNamespaces),
148
156
  };
149
- if (Object.keys(kmsEnvConfigs).length > 0) {
157
+ if (role === "runtime") {
158
+ body.packOnly = true;
159
+ }
160
+ if (sharedRecipient) {
161
+ body.sharedRecipient = true;
162
+ } else if (Object.keys(kmsEnvConfigs).length > 0) {
150
163
  body.kmsEnvConfigs = kmsEnvConfigs;
151
164
  }
152
165
  const res = await apiFetch("/api/service-identities", {
@@ -161,6 +174,7 @@ export function ServiceIdentitiesScreen({ manifest }: ServiceIdentitiesScreenPro
161
174
  }
162
175
  setCreatedName(data.identity.name);
163
176
  setPrivateKeys(data.privateKeys ?? {});
177
+ setWasSharedRecipient(data.sharedRecipient === true);
164
178
  setView("keys");
165
179
  } catch {
166
180
  setCreateError("Network error. Check that the UI server is running.");
@@ -284,7 +298,7 @@ export function ServiceIdentitiesScreen({ manifest }: ServiceIdentitiesScreenPro
284
298
  <div style={{ fontSize: 28, marginBottom: 12, opacity: 0.4 }}>{"\uD83D\uDD11"}</div>
285
299
  No service identities configured.
286
300
  {manifest && (
287
- <div style={{ marginTop: 16 }}>
301
+ <div style={{ marginTop: 16, display: "flex", justifyContent: "center" }}>
288
302
  <Button variant="primary" onClick={openCreate}>
289
303
  Create the first one
290
304
  </Button>
@@ -337,6 +351,22 @@ export function ServiceIdentitiesScreen({ manifest }: ServiceIdentitiesScreenPro
337
351
  >
338
352
  {si.name}
339
353
  </span>
354
+ {si.packOnly && (
355
+ <span
356
+ data-testid={`si-runtime-badge-${si.name}`}
357
+ style={{
358
+ fontFamily: theme.mono,
359
+ fontSize: 9,
360
+ color: theme.yellow,
361
+ background: `${theme.yellow}15`,
362
+ border: `1px solid ${theme.yellow}33`,
363
+ borderRadius: 3,
364
+ padding: "1px 6px",
365
+ }}
366
+ >
367
+ runtime
368
+ </span>
369
+ )}
340
370
  </div>
341
371
  <div
342
372
  style={{
@@ -441,6 +471,26 @@ export function ServiceIdentitiesScreen({ manifest }: ServiceIdentitiesScreenPro
441
471
  </div>
442
472
  </div>
443
473
 
474
+ {selectedIdentity.packOnly && (
475
+ <div
476
+ data-testid="runtime-info-banner"
477
+ style={{
478
+ background: `${theme.yellow}10`,
479
+ border: `1px solid ${theme.yellow}33`,
480
+ borderRadius: 8,
481
+ padding: "10px 16px",
482
+ marginBottom: 20,
483
+ fontFamily: theme.sans,
484
+ fontSize: 12,
485
+ color: theme.yellow,
486
+ lineHeight: 1.5,
487
+ }}
488
+ >
489
+ Runtime identity — keys are not registered on encrypted files. This identity can
490
+ only decrypt packed artifacts.
491
+ </div>
492
+ )}
493
+
444
494
  <Label>Environment keys</Label>
445
495
 
446
496
  {manifest?.environments.map((env) => {
@@ -938,6 +988,10 @@ export function ServiceIdentitiesScreen({ manifest }: ServiceIdentitiesScreenPro
938
988
  // ── Keys result view (post-creation) ─────────────────────────────────────────
939
989
  if (view === "keys") {
940
990
  const hasAgeKeys = Object.keys(privateKeys).length > 0;
991
+ // For shared mode, all entries hold the same key — grab it once
992
+ const sharedKey = wasSharedRecipient ? Object.values(privateKeys)[0] : undefined;
993
+ const sharedEnvNames = wasSharedRecipient ? Object.keys(privateKeys) : [];
994
+
941
995
  return (
942
996
  <div style={{ flex: 1, display: "flex", flexDirection: "column", overflow: "hidden" }}>
943
997
  <TopBar title={`${createdName} created`} subtitle="Service identity ready" />
@@ -961,8 +1015,9 @@ export function ServiceIdentitiesScreen({ manifest }: ServiceIdentitiesScreenPro
961
1015
  >
962
1016
  <span style={{ fontSize: 16, flexShrink: 0 }}>⚠</span>
963
1017
  <span>
964
- Copy these private keys now — they will not be shown again. Store each key
965
- securely and provision it to the relevant runtime.
1018
+ {wasSharedRecipient
1019
+ ? `Copy this key now — it will not be shown again. Set it as CLEF_AGE_KEY in your CI. It decrypts: ${sharedEnvNames.join(", ")}.`
1020
+ : "Copy these private keys now — they will not be shown again. Store each key securely and provision it to the relevant runtime."}
966
1021
  </span>
967
1022
  </div>
968
1023
  )}
@@ -986,12 +1041,13 @@ export function ServiceIdentitiesScreen({ manifest }: ServiceIdentitiesScreenPro
986
1041
  )}
987
1042
 
988
1043
  <Label>Private keys</Label>
989
- {Object.entries(privateKeys).map(([envName, key]) => (
1044
+
1045
+ {wasSharedRecipient && sharedKey ? (
1046
+ // Shared mode: one block, all env badges, labeled CLEF_AGE_KEY
990
1047
  <div
991
- key={envName}
992
1048
  style={{
993
1049
  background: theme.surface,
994
- border: `1px solid ${theme.border}`,
1050
+ border: `1px solid ${theme.accent}44`,
995
1051
  borderRadius: 8,
996
1052
  padding: "14px 18px",
997
1053
  marginBottom: 10,
@@ -1005,8 +1061,25 @@ export function ServiceIdentitiesScreen({ manifest }: ServiceIdentitiesScreenPro
1005
1061
  marginBottom: 10,
1006
1062
  }}
1007
1063
  >
1008
- <EnvBadge env={envName} />
1009
- <CopyButton text={key} />
1064
+ <div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
1065
+ <span
1066
+ style={{
1067
+ fontFamily: theme.mono,
1068
+ fontSize: 11,
1069
+ color: theme.accent,
1070
+ fontWeight: 600,
1071
+ }}
1072
+ >
1073
+ CLEF_AGE_KEY
1074
+ </span>
1075
+ <span style={{ fontFamily: theme.sans, fontSize: 11, color: theme.textDim }}>
1076
+
1077
+ </span>
1078
+ {sharedEnvNames.map((e) => (
1079
+ <EnvBadge key={e} env={e} small />
1080
+ ))}
1081
+ </div>
1082
+ <CopyButton text={sharedKey} />
1010
1083
  </div>
1011
1084
  <div
1012
1085
  style={{
@@ -1019,10 +1092,48 @@ export function ServiceIdentitiesScreen({ manifest }: ServiceIdentitiesScreenPro
1019
1092
  padding: "8px 10px",
1020
1093
  }}
1021
1094
  >
1022
- {key}
1095
+ {sharedKey}
1023
1096
  </div>
1024
1097
  </div>
1025
- ))}
1098
+ ) : (
1099
+ Object.entries(privateKeys).map(([envName, key]) => (
1100
+ <div
1101
+ key={envName}
1102
+ style={{
1103
+ background: theme.surface,
1104
+ border: `1px solid ${theme.border}`,
1105
+ borderRadius: 8,
1106
+ padding: "14px 18px",
1107
+ marginBottom: 10,
1108
+ }}
1109
+ >
1110
+ <div
1111
+ style={{
1112
+ display: "flex",
1113
+ alignItems: "center",
1114
+ justifyContent: "space-between",
1115
+ marginBottom: 10,
1116
+ }}
1117
+ >
1118
+ <EnvBadge env={envName} />
1119
+ <CopyButton text={key} />
1120
+ </div>
1121
+ <div
1122
+ style={{
1123
+ fontFamily: theme.mono,
1124
+ fontSize: 11,
1125
+ color: theme.textMuted,
1126
+ wordBreak: "break-all",
1127
+ background: theme.bg,
1128
+ borderRadius: 4,
1129
+ padding: "8px 10px",
1130
+ }}
1131
+ >
1132
+ {key}
1133
+ </div>
1134
+ </div>
1135
+ ))
1136
+ )}
1026
1137
 
1027
1138
  <div style={{ marginTop: 8, display: "flex", justifyContent: "flex-end" }}>
1028
1139
  <Button
@@ -1120,6 +1231,61 @@ export function ServiceIdentitiesScreen({ manifest }: ServiceIdentitiesScreenPro
1120
1231
  />
1121
1232
  </div>
1122
1233
 
1234
+ {/* Role */}
1235
+ <div style={{ marginBottom: 24 }}>
1236
+ <FieldLabel>Role</FieldLabel>
1237
+ <div
1238
+ style={{
1239
+ display: "flex",
1240
+ gap: 0,
1241
+ borderRadius: 6,
1242
+ overflow: "hidden",
1243
+ border: `1px solid ${theme.border}`,
1244
+ width: "fit-content",
1245
+ marginBottom: 8,
1246
+ }}
1247
+ >
1248
+ {(["ci", "runtime"] as const).map((r) => (
1249
+ <button
1250
+ key={r}
1251
+ data-testid={`role-${r}`}
1252
+ onClick={() => {
1253
+ setRole(r);
1254
+ // Auto-set shared-recipient to the role's natural default
1255
+ const newDefault = r === "ci";
1256
+ setSharedRecipient(newDefault);
1257
+ setSharedRecipientOverridden(false);
1258
+ }}
1259
+ style={{
1260
+ background: role === r ? theme.accent : "transparent",
1261
+ border: "none",
1262
+ padding: "7px 18px",
1263
+ cursor: "pointer",
1264
+ fontFamily: theme.sans,
1265
+ fontSize: 12,
1266
+ fontWeight: role === r ? 600 : 400,
1267
+ color: role === r ? "#fff" : theme.textMuted,
1268
+ transition: "all 0.12s",
1269
+ }}
1270
+ >
1271
+ {r === "ci" ? "CI" : "Runtime"}
1272
+ </button>
1273
+ ))}
1274
+ </div>
1275
+ <div
1276
+ style={{
1277
+ fontFamily: theme.sans,
1278
+ fontSize: 12,
1279
+ color: theme.textMuted,
1280
+ lineHeight: 1.5,
1281
+ }}
1282
+ >
1283
+ {role === "ci"
1284
+ ? "Decrypts files directly. Keys are registered on encrypted SOPS files. Use for CI pipelines and local tools."
1285
+ : "Decrypts packed artifacts only. Keys are NOT added to encrypted files — smaller blast radius for deployment targets (Lambda, ECS, containers)."}
1286
+ </div>
1287
+ </div>
1288
+
1123
1289
  {/* Namespaces */}
1124
1290
  <div style={{ marginBottom: 24 }}>
1125
1291
  <FieldLabel>Namespaces</FieldLabel>
@@ -1192,7 +1358,68 @@ export function ServiceIdentitiesScreen({ manifest }: ServiceIdentitiesScreenPro
1192
1358
 
1193
1359
  {/* Per-environment backend */}
1194
1360
  <div style={{ marginBottom: 28 }}>
1195
- <FieldLabel>Environment backends</FieldLabel>
1361
+ <div
1362
+ style={{
1363
+ display: "flex",
1364
+ alignItems: "center",
1365
+ justifyContent: "space-between",
1366
+ marginBottom: 6,
1367
+ }}
1368
+ >
1369
+ <FieldLabel>Environment backends</FieldLabel>
1370
+ {/* Shared recipient toggle */}
1371
+ <label
1372
+ data-testid="shared-recipient-toggle"
1373
+ style={{
1374
+ display: "flex",
1375
+ alignItems: "center",
1376
+ gap: 7,
1377
+ cursor: "pointer",
1378
+ fontFamily: theme.sans,
1379
+ fontSize: 11,
1380
+ color: sharedRecipient ? theme.accent : theme.textMuted,
1381
+ userSelect: "none",
1382
+ }}
1383
+ >
1384
+ <div
1385
+ style={{
1386
+ width: 28,
1387
+ height: 16,
1388
+ borderRadius: 8,
1389
+ background: sharedRecipient ? theme.accent : theme.border,
1390
+ position: "relative",
1391
+ transition: "background 0.15s",
1392
+ flexShrink: 0,
1393
+ }}
1394
+ >
1395
+ <div
1396
+ style={{
1397
+ position: "absolute",
1398
+ top: 2,
1399
+ left: sharedRecipient ? 14 : 2,
1400
+ width: 12,
1401
+ height: 12,
1402
+ borderRadius: "50%",
1403
+ background: "#fff",
1404
+ transition: "left 0.15s",
1405
+ }}
1406
+ />
1407
+ <input
1408
+ type="checkbox"
1409
+ checked={sharedRecipient}
1410
+ onChange={(e) => {
1411
+ setSharedRecipient(e.target.checked);
1412
+ // Track whether user manually overrode the role's default
1413
+ const roleDefault = role === "ci";
1414
+ setSharedRecipientOverridden(e.target.checked !== roleDefault);
1415
+ }}
1416
+ style={{ position: "absolute", opacity: 0, width: 0, height: 0 }}
1417
+ />
1418
+ </div>
1419
+ Shared age key
1420
+ </label>
1421
+ </div>
1422
+
1196
1423
  <div
1197
1424
  style={{
1198
1425
  fontFamily: theme.sans,
@@ -1201,112 +1428,166 @@ export function ServiceIdentitiesScreen({ manifest }: ServiceIdentitiesScreenPro
1201
1428
  marginBottom: 10,
1202
1429
  }}
1203
1430
  >
1204
- Age generates a key pair per environment. KMS uses your cloud provider — no key
1205
- material is provisioned.
1431
+ {sharedRecipient
1432
+ ? "One age key pair for all environments — one CI secret, easier to provision."
1433
+ : "Age generates a key pair per environment. KMS uses your cloud provider — no key material is provisioned."}
1206
1434
  </div>
1435
+
1436
+ {/* Warning when shared-recipient is overridden from role default */}
1437
+ {sharedRecipientOverridden && (
1438
+ <div
1439
+ data-testid="shared-recipient-warning"
1440
+ style={{
1441
+ background: "#1a1200",
1442
+ border: `1px solid ${theme.yellow}55`,
1443
+ borderRadius: 6,
1444
+ padding: "10px 14px",
1445
+ marginBottom: 10,
1446
+ fontFamily: theme.sans,
1447
+ fontSize: 12,
1448
+ color: theme.yellow,
1449
+ lineHeight: 1.5,
1450
+ }}
1451
+ >
1452
+ {role === "ci" && !sharedRecipient
1453
+ ? "Most CI pipelines use a single key. Per-environment keys are useful when your CI environments have separate secret access controls (e.g. GitHub environment protection rules)."
1454
+ : "Runtime workloads typically use per-environment keys for isolation. A shared key means a compromised key in any environment decrypts artifacts for all environments."}
1455
+ </div>
1456
+ )}
1457
+
1207
1458
  <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
1208
- {environments.map((env) => {
1209
- const cfg = envBackends[env.name] ?? { type: "age", provider: "aws", keyId: "" };
1210
- return (
1211
- <div
1212
- key={env.name}
1459
+ {sharedRecipient ? (
1460
+ <div
1461
+ style={{
1462
+ background: theme.accentDim,
1463
+ border: `1px solid ${theme.accent}44`,
1464
+ borderRadius: 8,
1465
+ padding: "14px 16px",
1466
+ display: "flex",
1467
+ alignItems: "center",
1468
+ gap: 12,
1469
+ }}
1470
+ >
1471
+ <div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
1472
+ {environments.map((env) => (
1473
+ <EnvBadge key={env.name} env={env.name} small />
1474
+ ))}
1475
+ </div>
1476
+ <span
1213
1477
  style={{
1214
- background: theme.surface,
1215
- border: `1px solid ${theme.border}`,
1216
- borderRadius: 8,
1217
- padding: "14px 16px",
1478
+ fontFamily: theme.mono,
1479
+ fontSize: 11,
1480
+ color: theme.accent,
1481
+ marginLeft: "auto",
1218
1482
  }}
1219
1483
  >
1484
+ age (shared)
1485
+ </span>
1486
+ </div>
1487
+ ) : (
1488
+ environments.map((env) => {
1489
+ const cfg = envBackends[env.name] ?? { type: "age", provider: "aws", keyId: "" };
1490
+ return (
1220
1491
  <div
1492
+ key={env.name}
1221
1493
  style={{
1222
- display: "flex",
1223
- alignItems: "center",
1224
- justifyContent: "space-between",
1225
- marginBottom: cfg.type === "kms" ? 12 : 0,
1494
+ background: theme.surface,
1495
+ border: `1px solid ${theme.border}`,
1496
+ borderRadius: 8,
1497
+ padding: "14px 16px",
1226
1498
  }}
1227
1499
  >
1228
- <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
1229
- <EnvBadge env={env.name} />
1230
- {env.protected && (
1231
- <span style={{ fontSize: 11, color: theme.red }}>{"\uD83D\uDD12"}</span>
1232
- )}
1500
+ <div
1501
+ style={{
1502
+ display: "flex",
1503
+ alignItems: "center",
1504
+ justifyContent: "space-between",
1505
+ marginBottom: cfg.type === "kms" ? 12 : 0,
1506
+ }}
1507
+ >
1508
+ <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
1509
+ <EnvBadge env={env.name} />
1510
+ {env.protected && (
1511
+ <span style={{ fontSize: 11, color: theme.red }}>{"\uD83D\uDD12"}</span>
1512
+ )}
1513
+ </div>
1514
+ <div style={{ display: "flex", gap: 4 }}>
1515
+ {(["age", "kms"] as const).map((t) => (
1516
+ <button
1517
+ key={t}
1518
+ onClick={() =>
1519
+ setEnvBackends((prev) => ({
1520
+ ...prev,
1521
+ [env.name]: { ...cfg, type: t },
1522
+ }))
1523
+ }
1524
+ style={{
1525
+ background:
1526
+ cfg.type === t
1527
+ ? t === "kms"
1528
+ ? theme.purple
1529
+ : theme.accent
1530
+ : "transparent",
1531
+ border: `1px solid ${
1532
+ cfg.type === t
1533
+ ? t === "kms"
1534
+ ? theme.purple
1535
+ : theme.accent
1536
+ : theme.border
1537
+ }`,
1538
+ borderRadius: 4,
1539
+ padding: "3px 10px",
1540
+ cursor: "pointer",
1541
+ fontFamily: theme.mono,
1542
+ fontSize: 11,
1543
+ color: cfg.type === t ? "#fff" : theme.textMuted,
1544
+ transition: "all 0.1s",
1545
+ }}
1546
+ >
1547
+ {t.toUpperCase()}
1548
+ </button>
1549
+ ))}
1550
+ </div>
1233
1551
  </div>
1234
- <div style={{ display: "flex", gap: 4 }}>
1235
- {(["age", "kms"] as const).map((t) => (
1236
- <button
1237
- key={t}
1238
- onClick={() =>
1552
+
1553
+ {cfg.type === "kms" && (
1554
+ <div style={{ display: "flex", gap: 8 }}>
1555
+ <select
1556
+ value={cfg.provider}
1557
+ onChange={(e) =>
1239
1558
  setEnvBackends((prev) => ({
1240
1559
  ...prev,
1241
- [env.name]: { ...cfg, type: t },
1560
+ [env.name]: { ...cfg, provider: e.target.value },
1242
1561
  }))
1243
1562
  }
1244
1563
  style={{
1245
- background:
1246
- cfg.type === t
1247
- ? t === "kms"
1248
- ? theme.purple
1249
- : theme.accent
1250
- : "transparent",
1251
- border: `1px solid ${
1252
- cfg.type === t
1253
- ? t === "kms"
1254
- ? theme.purple
1255
- : theme.accent
1256
- : theme.border
1257
- }`,
1258
- borderRadius: 4,
1259
- padding: "3px 10px",
1260
- cursor: "pointer",
1261
- fontFamily: theme.mono,
1262
- fontSize: 11,
1263
- color: cfg.type === t ? "#fff" : theme.textMuted,
1264
- transition: "all 0.1s",
1564
+ ...inputStyle,
1565
+ width: 90,
1566
+ flexShrink: 0,
1567
+ padding: "7px 8px",
1265
1568
  }}
1266
1569
  >
1267
- {t.toUpperCase()}
1268
- </button>
1269
- ))}
1270
- </div>
1570
+ <option value="aws">AWS</option>
1571
+ <option value="gcp">GCP</option>
1572
+ <option value="azure">Azure</option>
1573
+ </select>
1574
+ <input
1575
+ value={cfg.keyId}
1576
+ onChange={(e) =>
1577
+ setEnvBackends((prev) => ({
1578
+ ...prev,
1579
+ [env.name]: { ...cfg, keyId: e.target.value },
1580
+ }))
1581
+ }
1582
+ placeholder="arn:aws:kms:… or key resource ID"
1583
+ style={{ ...inputStyle, flex: 1 }}
1584
+ />
1585
+ </div>
1586
+ )}
1271
1587
  </div>
1272
-
1273
- {cfg.type === "kms" && (
1274
- <div style={{ display: "flex", gap: 8 }}>
1275
- <select
1276
- value={cfg.provider}
1277
- onChange={(e) =>
1278
- setEnvBackends((prev) => ({
1279
- ...prev,
1280
- [env.name]: { ...cfg, provider: e.target.value },
1281
- }))
1282
- }
1283
- style={{
1284
- ...inputStyle,
1285
- width: 90,
1286
- flexShrink: 0,
1287
- padding: "7px 8px",
1288
- }}
1289
- >
1290
- <option value="aws">AWS</option>
1291
- <option value="gcp">GCP</option>
1292
- <option value="azure">Azure</option>
1293
- </select>
1294
- <input
1295
- value={cfg.keyId}
1296
- onChange={(e) =>
1297
- setEnvBackends((prev) => ({
1298
- ...prev,
1299
- [env.name]: { ...cfg, keyId: e.target.value },
1300
- }))
1301
- }
1302
- placeholder="arn:aws:kms:… or key resource ID"
1303
- style={{ ...inputStyle, flex: 1 }}
1304
- />
1305
- </div>
1306
- )}
1307
- </div>
1308
- );
1309
- })}
1588
+ );
1589
+ })
1590
+ )}
1310
1591
  </div>
1311
1592
  </div>
1312
1593