@le-space/ui 0.4.0 → 0.4.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@le-space/ui",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Floating Aleph sponsor relay UI components for Svelte and React.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -36,8 +36,8 @@
36
36
  "access": "public"
37
37
  },
38
38
  "dependencies": {
39
- "@le-space/browser": "0.4.0",
40
- "@le-space/core": "0.4.0",
39
+ "@le-space/browser": "0.4.1",
40
+ "@le-space/core": "0.4.1",
41
41
  "viem": "^2.38.5"
42
42
  },
43
43
  "peerDependencies": {
package/react/index.d.ts CHANGED
@@ -189,7 +189,6 @@ type DeploymentProgressListener = (event: DeploymentProgressEvent) => void;
189
189
 
190
190
  type SponsorRelayHealthTone = 'ok' | 'caution' | 'error' | 'idle';
191
191
  interface SponsorRelayProps {
192
- libp2p?: unknown;
193
192
  debug?: boolean;
194
193
  manifestUrl?: string;
195
194
  manifestJson?: string;
@@ -224,16 +223,6 @@ interface SponsorRelayRootfsHealth {
224
223
  label: string;
225
224
  detail: string | null;
226
225
  }
227
- interface RelayPingState {
228
- tone: SponsorRelayHealthTone;
229
- sent: boolean;
230
- received: boolean;
231
- lastPeerId: string | null;
232
- lastLatencyMs: number | null;
233
- lastSentAt: number | null;
234
- lastReceivedAt: number | null;
235
- error: string | null;
236
- }
237
226
  interface CompactInstanceDetails {
238
227
  messageStatus: string;
239
228
  allocationSource: string | null;
@@ -303,7 +292,6 @@ interface SponsorRelayState {
303
292
  instances: CompactInstanceRecord[];
304
293
  bootstrapRegistrations: CompactBootstrapRegistrationRecord[];
305
294
  orphanBootstrapRegistrations: CompactBootstrapRegistrationRecord[];
306
- relayPing: RelayPingState;
307
295
  lastDeploymentHash: string | null;
308
296
  deploymentProgress: DeploymentProgressEvent;
309
297
  }
@@ -314,7 +302,6 @@ declare class SponsorRelayController {
314
302
  private subscribers;
315
303
  private client;
316
304
  private refreshTimer;
317
- private pingTimer;
318
305
  private manifestRefreshTimer;
319
306
  private stopWalletWatch;
320
307
  private props;
@@ -354,7 +341,6 @@ declare class SponsorRelayController {
354
341
  connectWallet(): Promise<void>;
355
342
  private refreshWalletDerivedState;
356
343
  refresh(): Promise<void>;
357
- refreshRelayPing(): Promise<void>;
358
344
  deploy(): Promise<void>;
359
345
  deleteInstance(instanceHash: string): Promise<void>;
360
346
  deleteBootstrapRegistration(registrationHash: string): Promise<void>;
package/react/index.js CHANGED
@@ -10,16 +10,6 @@ var ROOTFS_MISSING_STATE = {
10
10
  label: "manifest missing",
11
11
  detail: "Provide a manifest URL or paste manifest JSON."
12
12
  };
13
- var RELAY_PING_IDLE_STATE = {
14
- tone: "idle",
15
- sent: false,
16
- received: false,
17
- lastPeerId: null,
18
- lastLatencyMs: null,
19
- lastSentAt: null,
20
- lastReceivedAt: null,
21
- error: null
22
- };
23
13
  var IDLE_DEPLOYMENT_PROGRESS = {
24
14
  stage: "idle",
25
15
  label: "Ready",
@@ -31,7 +21,6 @@ var IDLE_DEPLOYMENT_PROGRESS = {
31
21
  timestamp: Date.now()
32
22
  };
33
23
  var REFRESH_INTERVAL_MS = 3e4;
34
- var RELAY_PING_INTERVAL_MS = 2e4;
35
24
  var MANIFEST_SOURCE_REFRESH_DEBOUNCE_MS = 400;
36
25
  var INSTANCE_RUNTIME_STALE_REFRESH_MS = 5 * 6e4;
37
26
  var STALE_INSTANCE_ALLOCATION_COOLDOWN_MS = 5 * 60 * 1e3;
@@ -39,7 +28,7 @@ var RECENT_INSTANCE_RUNTIME_GRACE_MS = 10 * 60 * 1e3;
39
28
  var DEPLOYMENT_PENDING_WARNING_MS = 3 * 60 * 1e3;
40
29
 
41
30
  // src/shared/package-version.ts
42
- var UI_PACKAGE_VERSION = "0.4.0";
31
+ var UI_PACKAGE_VERSION = "0.4.1";
43
32
 
44
33
  // src/shared/format.ts
45
34
  function shortHash(value, head = 8, tail = 6) {
@@ -2562,7 +2551,7 @@ async function waitForRelayBootstrapRegistration(args) {
2562
2551
  return false;
2563
2552
  });
2564
2553
  if (match?.content) {
2565
- return {
2554
+ const visibleRegistration = {
2566
2555
  itemHash: match.itemHash ?? null,
2567
2556
  hash: match.hash ?? null,
2568
2557
  sender: typeof match.address === "string" ? match.address : null,
@@ -2571,7 +2560,10 @@ async function waitForRelayBootstrapRegistration(args) {
2571
2560
  multiaddrs: Array.isArray(match.content.multiaddrs) ? match.content.multiaddrs : [],
2572
2561
  browserMultiaddrs: Array.isArray(match.content.browserMultiaddrs) ? match.content.browserMultiaddrs : []
2573
2562
  };
2563
+ args.onAttempt?.(visibleRegistration, attempt + 1, attempts);
2564
+ return visibleRegistration;
2574
2565
  }
2566
+ args.onAttempt?.(null, attempt + 1, attempts);
2575
2567
  if (attempt < attempts - 1) {
2576
2568
  await sleep(delayMs);
2577
2569
  }
@@ -2944,7 +2936,6 @@ function defaultState(props = {}) {
2944
2936
  instances: [],
2945
2937
  bootstrapRegistrations: [],
2946
2938
  orphanBootstrapRegistrations: [],
2947
- relayPing: RELAY_PING_IDLE_STATE,
2948
2939
  lastDeploymentHash: null,
2949
2940
  deploymentProgress: IDLE_DEPLOYMENT_PROGRESS
2950
2941
  };
@@ -3171,57 +3162,6 @@ function compatibleCrnsForTier(crns, state) {
3171
3162
  );
3172
3163
  return filterDeployableCrns(crns, { spec });
3173
3164
  }
3174
- async function pingPeer(libp2p) {
3175
- if (!libp2p || typeof libp2p !== "object") {
3176
- return RELAY_PING_IDLE_STATE;
3177
- }
3178
- const candidate = libp2p;
3179
- const peers = candidate.getPeers?.() ?? [];
3180
- const firstPeer = peers[0];
3181
- if (!firstPeer) {
3182
- return {
3183
- ...RELAY_PING_IDLE_STATE,
3184
- tone: "caution",
3185
- error: "No connected relay peers available."
3186
- };
3187
- }
3188
- const sentAt = Date.now();
3189
- try {
3190
- const pingFn = candidate.services?.ping?.ping?.bind(candidate.services.ping) ?? candidate.ping?.bind(candidate);
3191
- if (!pingFn) {
3192
- return {
3193
- ...RELAY_PING_IDLE_STATE,
3194
- tone: "caution",
3195
- sent: true,
3196
- lastPeerId: String(firstPeer),
3197
- lastSentAt: sentAt,
3198
- error: "libp2p ping service not available."
3199
- };
3200
- }
3201
- const latency = await pingFn(firstPeer);
3202
- return {
3203
- tone: "ok",
3204
- sent: true,
3205
- received: true,
3206
- lastPeerId: String(firstPeer),
3207
- lastLatencyMs: Number(latency),
3208
- lastSentAt: sentAt,
3209
- lastReceivedAt: Date.now(),
3210
- error: null
3211
- };
3212
- } catch (error) {
3213
- return {
3214
- tone: "error",
3215
- sent: true,
3216
- received: false,
3217
- lastPeerId: String(firstPeer),
3218
- lastLatencyMs: null,
3219
- lastSentAt: sentAt,
3220
- lastReceivedAt: null,
3221
- error: error instanceof Error ? error.message : String(error)
3222
- };
3223
- }
3224
- }
3225
3165
  var UI_DEPLOY_WAIT_ATTEMPTS = 60;
3226
3166
  var UI_DEPLOY_WAIT_DELAY_MS = 5e3;
3227
3167
  var UI_RUNTIME_WAIT_ATTEMPTS = 40;
@@ -3252,7 +3192,6 @@ var SponsorRelayController = class {
3252
3192
  subscribers = /* @__PURE__ */ new Set();
3253
3193
  client;
3254
3194
  refreshTimer = null;
3255
- pingTimer = null;
3256
3195
  manifestRefreshTimer = null;
3257
3196
  stopWalletWatch = null;
3258
3197
  props;
@@ -3308,8 +3247,7 @@ var SponsorRelayController = class {
3308
3247
  ...patch,
3309
3248
  busy: patch.busy ? { ...this.state.busy, ...patch.busy } : this.state.busy,
3310
3249
  wallet: patch.wallet ? { ...this.state.wallet, ...patch.wallet } : this.state.wallet,
3311
- pricingSummary: patch.pricingSummary ? { ...this.state.pricingSummary, ...patch.pricingSummary } : this.state.pricingSummary,
3312
- relayPing: patch.relayPing ? { ...this.state.relayPing, ...patch.relayPing } : this.state.relayPing
3250
+ pricingSummary: patch.pricingSummary ? { ...this.state.pricingSummary, ...patch.pricingSummary } : this.state.pricingSummary
3313
3251
  };
3314
3252
  this.emit();
3315
3253
  }
@@ -3362,7 +3300,17 @@ var SponsorRelayController = class {
3362
3300
  fetch: (url, init) => fetch(url, init),
3363
3301
  attempts: 15,
3364
3302
  delayMs: 4e3,
3365
- httpTimeoutMs: 1e4
3303
+ httpTimeoutMs: 1e4,
3304
+ onAttempt: (result, attempt, attempts) => {
3305
+ this.emitProgress({
3306
+ stage: "deployment-confirmed",
3307
+ label: "Waiting for guest setup",
3308
+ progress: 93,
3309
+ status: result.ok ? "success" : "info",
3310
+ itemHash: args.itemHash,
3311
+ detail: result.ok ? `Setup endpoint ${attempt}/${attempts}: ready at http://${args.runtime.hostIpv4}:${setupPort}/health.` : `Setup endpoint ${attempt}/${attempts}: waiting for http://${args.runtime.hostIpv4}:${setupPort}/health.`
3312
+ });
3313
+ }
3366
3314
  });
3367
3315
  if (!setupHealth.ok) {
3368
3316
  throw new Error(
@@ -3491,6 +3439,14 @@ var SponsorRelayController = class {
3491
3439
  metadata?.browser_bootstrap_multiaddrs
3492
3440
  ) ? metadata.browser_bootstrap_multiaddrs.length : 0
3493
3441
  });
3442
+ this.emitProgress({
3443
+ stage: "publishing-bootstrap",
3444
+ label: "Reading relay multiaddrs",
3445
+ progress: 95,
3446
+ status: result.ready ? "success" : "info",
3447
+ itemHash: args.itemHash,
3448
+ detail: result.ready ? `Relay metadata ${result.attempt}/${result.attempts}: public multiaddrs available.` : `Relay metadata ${result.attempt}/${result.attempts}: waiting for the relay to report public multiaddrs.`
3449
+ });
3494
3450
  }
3495
3451
  });
3496
3452
  if (!relayMetadata) {
@@ -3516,7 +3472,17 @@ var SponsorRelayController = class {
3516
3472
  fetch: asJsonFetch,
3517
3473
  apiHost: this.client.apiHost,
3518
3474
  attempts: 24,
3519
- delayMs: 2500
3475
+ delayMs: 2500,
3476
+ onAttempt: (match, attempt, attempts) => {
3477
+ this.emitProgress({
3478
+ stage: "publishing-bootstrap",
3479
+ label: "Waiting for guest bootstrap registration",
3480
+ progress: 97,
3481
+ status: match ? "success" : "info",
3482
+ itemHash: args.itemHash,
3483
+ detail: match ? `Guest bootstrap registration ${attempt}/${attempts}: visible on Aleph.` : `Guest bootstrap registration ${attempt}/${attempts}: still waiting for the relay VM registration on Aleph.`
3484
+ });
3485
+ }
3520
3486
  });
3521
3487
  this.trace("deploy:bootstrap-registration-visible", {
3522
3488
  itemHash: args.itemHash,
@@ -3567,7 +3533,17 @@ var SponsorRelayController = class {
3567
3533
  fetch: asJsonFetch,
3568
3534
  apiHost: this.client.apiHost,
3569
3535
  attempts: 24,
3570
- delayMs: 2500
3536
+ delayMs: 2500,
3537
+ onAttempt: (match, attempt, attempts) => {
3538
+ this.emitProgress({
3539
+ stage: "publishing-bootstrap",
3540
+ label: "Waiting for bootstrap fallback",
3541
+ progress: 98,
3542
+ status: match ? "success" : "warning",
3543
+ itemHash: args.itemHash,
3544
+ detail: match ? `Bootstrap fallback ${attempt}/${attempts}: visible on Aleph.` : `Bootstrap fallback ${attempt}/${attempts}: waiting for the browser-published registration on Aleph.`
3545
+ });
3546
+ }
3571
3547
  });
3572
3548
  this.trace("deploy:bootstrap-registration-fallback-visible", {
3573
3549
  itemHash: args.itemHash,
@@ -3743,16 +3719,11 @@ var SponsorRelayController = class {
3743
3719
  this.refreshTimer = setInterval(() => {
3744
3720
  void this.refresh();
3745
3721
  }, REFRESH_INTERVAL_MS);
3746
- this.pingTimer = setInterval(() => {
3747
- void this.refreshRelayPing();
3748
- }, RELAY_PING_INTERVAL_MS);
3749
- await this.refreshRelayPing();
3750
3722
  this.patch({ ready: true });
3751
3723
  this.trace("init:ready");
3752
3724
  }
3753
3725
  destroy() {
3754
3726
  if (this.refreshTimer) clearInterval(this.refreshTimer);
3755
- if (this.pingTimer) clearInterval(this.pingTimer);
3756
3727
  if (this.manifestRefreshTimer) clearTimeout(this.manifestRefreshTimer);
3757
3728
  this.stopWalletWatch?.();
3758
3729
  }
@@ -4047,11 +4018,6 @@ var SponsorRelayController = class {
4047
4018
  });
4048
4019
  }
4049
4020
  }
4050
- async refreshRelayPing() {
4051
- const relayPing = await pingPeer(this.props.libp2p);
4052
- this.patch({ relayPing });
4053
- this.trace("libp2p:relay-ping", relayPing);
4054
- }
4055
4021
  async deploy() {
4056
4022
  this.trace("deploy:start", {
4057
4023
  wallet: this.state.wallet.address,
@@ -4168,7 +4134,7 @@ var SponsorRelayController = class {
4168
4134
  fetch: (url, init) => fetch(url, init),
4169
4135
  attempts: UI_DEPLOY_WAIT_ATTEMPTS,
4170
4136
  delayMs: UI_DEPLOY_WAIT_DELAY_MS,
4171
- onAttempt: (inspectionResult) => {
4137
+ onAttempt: (inspectionResult, attempt, attempts) => {
4172
4138
  if (inspectionResult.status === "processed") {
4173
4139
  this.emitProgress({
4174
4140
  stage: "deployment-confirmed",
@@ -4176,7 +4142,7 @@ var SponsorRelayController = class {
4176
4142
  progress: 82,
4177
4143
  status: "success",
4178
4144
  itemHash: result.itemHash,
4179
- detail: "Aleph processed the instance message."
4145
+ detail: `Aleph processing ${attempt}/${attempts}: instance message processed.`
4180
4146
  });
4181
4147
  return;
4182
4148
  }
@@ -4198,7 +4164,7 @@ var SponsorRelayController = class {
4198
4164
  progress: 76,
4199
4165
  status: "info",
4200
4166
  itemHash: result.itemHash,
4201
- detail: "Deployment submitted. Waiting for Aleph to process the instance message."
4167
+ detail: `Aleph processing ${attempt}/${attempts}: waiting for the instance message to be processed.`
4202
4168
  });
4203
4169
  }
4204
4170
  });
@@ -4264,7 +4230,7 @@ var SponsorRelayController = class {
4264
4230
  crnListUrl: this.props.crnListUrl,
4265
4231
  attempts: UI_RUNTIME_WAIT_ATTEMPTS,
4266
4232
  delayMs: UI_RUNTIME_WAIT_DELAY_MS,
4267
- onAttempt: (runtime2) => {
4233
+ onAttempt: (runtime2, attempt, attempts) => {
4268
4234
  if (runtime2.hostIpv4 && Object.keys(runtime2.mappedPorts ?? {}).length > 0) {
4269
4235
  this.emitProgress({
4270
4236
  stage: "deployment-confirmed",
@@ -4272,7 +4238,7 @@ var SponsorRelayController = class {
4272
4238
  progress: 92,
4273
4239
  status: "info",
4274
4240
  itemHash: result.itemHash,
4275
- detail: "Runtime networking and mapped ports are now available. Finishing relay setup and bootstrap verification."
4241
+ detail: `Runtime ${attempt}/${attempts}: networking and mapped ports are now available.`
4276
4242
  });
4277
4243
  return;
4278
4244
  }
@@ -4282,7 +4248,7 @@ var SponsorRelayController = class {
4282
4248
  progress: 90,
4283
4249
  status: "warning",
4284
4250
  itemHash: result.itemHash,
4285
- detail: runtime2.diagnostics?.reason ?? "Waiting for CRN runtime networking and mapped ports."
4251
+ detail: runtime2.diagnostics?.reason ? `Runtime ${attempt}/${attempts}: ${runtime2.diagnostics.reason}` : `Runtime ${attempt}/${attempts}: waiting for CRN runtime networking and mapped ports.`
4286
4252
  });
4287
4253
  }
4288
4254
  });
@@ -4589,10 +4555,9 @@ function useSponsorRelayController(props) {
4589
4555
  );
4590
4556
  useEffect(() => {
4591
4557
  controller.updateProps({
4592
- libp2p: props.libp2p,
4593
4558
  debug: props.debug
4594
4559
  });
4595
- }, [controller, props.debug, props.libp2p]);
4560
+ }, [controller, props.debug]);
4596
4561
  useEffect(() => {
4597
4562
  void controller.init();
4598
4563
  return () => {
@@ -4633,6 +4598,53 @@ var fieldStyle = {
4633
4598
  fontSize: "0.98rem",
4634
4599
  lineHeight: 1.45
4635
4600
  };
4601
+ var secondaryButtonStyle = {
4602
+ borderRadius: "0.95rem",
4603
+ border: "1px solid rgba(255,255,255,0.22)",
4604
+ background: "rgba(255,255,255,0.10)",
4605
+ color: "#f8fafc",
4606
+ padding: "0.72rem 0.95rem",
4607
+ fontFamily: '"DM Sans", "Inter", sans-serif',
4608
+ fontSize: "0.92rem",
4609
+ fontWeight: 700,
4610
+ lineHeight: 1.1,
4611
+ boxShadow: "inset 0 1px 0 rgba(255,255,255,0.06)",
4612
+ backdropFilter: "blur(8px)"
4613
+ };
4614
+ var primaryButtonStyle = {
4615
+ borderRadius: "0.95rem",
4616
+ border: "1px solid rgba(251, 191, 36, 0.42)",
4617
+ background: "linear-gradient(135deg, #f6c453 0%, #f59e0b 100%)",
4618
+ color: "#281606",
4619
+ padding: "0.82rem 1rem",
4620
+ fontFamily: '"DM Sans", "Inter", sans-serif',
4621
+ fontSize: "0.96rem",
4622
+ fontWeight: 800,
4623
+ lineHeight: 1.1,
4624
+ boxShadow: "0 10px 24px rgba(245, 158, 11, 0.24)"
4625
+ };
4626
+ var dangerButtonStyle = {
4627
+ borderRadius: "0.95rem",
4628
+ border: "1px solid rgba(248, 113, 113, 0.45)",
4629
+ background: "rgba(239, 68, 68, 0.18)",
4630
+ color: "#ffe2e2",
4631
+ padding: "0.72rem 0.95rem",
4632
+ fontFamily: '"DM Sans", "Inter", sans-serif',
4633
+ fontSize: "0.9rem",
4634
+ fontWeight: 700,
4635
+ lineHeight: 1.1
4636
+ };
4637
+ var warningButtonStyle = {
4638
+ borderRadius: "0.95rem",
4639
+ border: "1px solid rgba(251, 146, 60, 0.4)",
4640
+ background: "rgba(251, 146, 60, 0.16)",
4641
+ color: "#fed7aa",
4642
+ padding: "0.72rem 0.95rem",
4643
+ fontFamily: '"DM Sans", "Inter", sans-serif',
4644
+ fontSize: "0.9rem",
4645
+ fontWeight: 700,
4646
+ lineHeight: 1.1
4647
+ };
4636
4648
  var themedLauncherBackground = "linear-gradient(135deg, var(--le-space-sponsor-relay-launcher-start, #4f46e5) 0%, var(--le-space-sponsor-relay-launcher-end, #6366f1) 100%)";
4637
4649
  var themedLauncherBorder = "var(--le-space-sponsor-relay-launcher-border, rgba(199, 210, 254, 0.42))";
4638
4650
  var themedLauncherBadgeBackground = "var(--le-space-sponsor-relay-launcher-badge-bg, rgba(49, 46, 129, 0.92))";
@@ -5038,7 +5050,15 @@ function SponsorRelayFab(props) {
5038
5050
  }
5039
5051
  )
5040
5052
  ] }),
5041
- /* @__PURE__ */ jsx("button", { type: "button", onClick: () => void controller.refresh(), children: state.busy.refreshing ? "Syncing" : "Refresh" })
5053
+ /* @__PURE__ */ jsx(
5054
+ "button",
5055
+ {
5056
+ type: "button",
5057
+ style: secondaryButtonStyle,
5058
+ onClick: () => void controller.refresh(),
5059
+ children: state.busy.refreshing ? "Syncing" : "Refresh"
5060
+ }
5061
+ )
5042
5062
  ]
5043
5063
  }
5044
5064
  ),
@@ -5397,7 +5417,7 @@ function SponsorRelayFab(props) {
5397
5417
  {
5398
5418
  type: "button",
5399
5419
  onClick: () => void (state.wallet.connected ? controller.deploy() : controller.connectWallet()),
5400
- style: { width: "100%", marginTop: "1rem" },
5420
+ style: { ...primaryButtonStyle, width: "100%", marginTop: "1rem" },
5401
5421
  children: state.wallet.connected ? state.busy.deploying ? "Deploying\u2026" : "Deploy Relay" : "Connect MetaMask"
5402
5422
  }
5403
5423
  ),
@@ -5454,7 +5474,7 @@ function SponsorRelayFab(props) {
5454
5474
  }
5455
5475
  }
5456
5476
  ),
5457
- "Registration confirmed"
5477
+ "Aleph bootstrap registered"
5458
5478
  ]
5459
5479
  }
5460
5480
  ) : null
@@ -5514,6 +5534,7 @@ function SponsorRelayFab(props) {
5514
5534
  "button",
5515
5535
  {
5516
5536
  type: "button",
5537
+ style: dangerButtonStyle,
5517
5538
  onClick: () => void controller.deleteInstance(
5518
5539
  entry.instance.item_hash
5519
5540
  ),
@@ -5579,6 +5600,7 @@ function SponsorRelayFab(props) {
5579
5600
  "button",
5580
5601
  {
5581
5602
  type: "button",
5603
+ style: warningButtonStyle,
5582
5604
  disabled: !registrationHash || state.busy.deletingRegistrationHash === registrationHash,
5583
5605
  onClick: () => registrationHash ? void controller.deleteBootstrapRegistration(
5584
5606
  registrationHash
package/shared/index.d.ts CHANGED
@@ -187,7 +187,6 @@ type DeploymentProgressListener = (event: DeploymentProgressEvent) => void;
187
187
 
188
188
  type SponsorRelayHealthTone = 'ok' | 'caution' | 'error' | 'idle';
189
189
  interface SponsorRelayProps {
190
- libp2p?: unknown;
191
190
  debug?: boolean;
192
191
  manifestUrl?: string;
193
192
  manifestJson?: string;
@@ -222,16 +221,6 @@ interface SponsorRelayRootfsHealth {
222
221
  label: string;
223
222
  detail: string | null;
224
223
  }
225
- interface RelayPingState {
226
- tone: SponsorRelayHealthTone;
227
- sent: boolean;
228
- received: boolean;
229
- lastPeerId: string | null;
230
- lastLatencyMs: number | null;
231
- lastSentAt: number | null;
232
- lastReceivedAt: number | null;
233
- error: string | null;
234
- }
235
224
  interface CompactInstanceDetails {
236
225
  messageStatus: string;
237
226
  allocationSource: string | null;
@@ -301,7 +290,6 @@ interface SponsorRelayState {
301
290
  instances: CompactInstanceRecord[];
302
291
  bootstrapRegistrations: CompactBootstrapRegistrationRecord[];
303
292
  orphanBootstrapRegistrations: CompactBootstrapRegistrationRecord[];
304
- relayPing: RelayPingState;
305
293
  lastDeploymentHash: string | null;
306
294
  deploymentProgress: DeploymentProgressEvent;
307
295
  }
@@ -311,10 +299,8 @@ declare const DEFAULT_INSTANCE_NAME = "sponsor-relay";
311
299
  declare const DEFAULT_MANIFEST_URL = "./rootfs-manifest.json";
312
300
  declare const DEFAULT_TIER_ID = "tier-1";
313
301
  declare const ROOTFS_MISSING_STATE: SponsorRelayRootfsHealth;
314
- declare const RELAY_PING_IDLE_STATE: RelayPingState;
315
302
  declare const IDLE_DEPLOYMENT_PROGRESS: DeploymentProgressEvent;
316
303
  declare const REFRESH_INTERVAL_MS = 30000;
317
- declare const RELAY_PING_INTERVAL_MS = 20000;
318
304
  declare const MANIFEST_SOURCE_REFRESH_DEBOUNCE_MS = 400;
319
305
  declare const INSTANCE_RUNTIME_READY_REFRESH_MS = 30000;
320
306
  declare const INSTANCE_RUNTIME_PENDING_REFRESH_MS = 45000;
@@ -323,7 +309,7 @@ declare const STALE_INSTANCE_ALLOCATION_COOLDOWN_MS: number;
323
309
  declare const RECENT_INSTANCE_RUNTIME_GRACE_MS: number;
324
310
  declare const DEPLOYMENT_PENDING_WARNING_MS: number;
325
311
 
326
- declare const UI_PACKAGE_VERSION = "0.4.0";
312
+ declare const UI_PACKAGE_VERSION = "0.4.1";
327
313
 
328
314
  interface RootfsRequiredPortForward {
329
315
  port: number;
@@ -364,7 +350,6 @@ declare class SponsorRelayController {
364
350
  private subscribers;
365
351
  private client;
366
352
  private refreshTimer;
367
- private pingTimer;
368
353
  private manifestRefreshTimer;
369
354
  private stopWalletWatch;
370
355
  private props;
@@ -404,7 +389,6 @@ declare class SponsorRelayController {
404
389
  connectWallet(): Promise<void>;
405
390
  private refreshWalletDerivedState;
406
391
  refresh(): Promise<void>;
407
- refreshRelayPing(): Promise<void>;
408
392
  deploy(): Promise<void>;
409
393
  deleteInstance(instanceHash: string): Promise<void>;
410
394
  deleteBootstrapRegistration(registrationHash: string): Promise<void>;
@@ -418,4 +402,4 @@ interface DeploymentProgressEmitter {
418
402
  }
419
403
  declare function createDeploymentProgressEmitter(): DeploymentProgressEmitter;
420
404
 
421
- export { type CompactBootstrapRegistrationRecord, type CompactInstanceDetails, type CompactInstanceRecord, DEFAULT_INSTANCE_NAME, DEFAULT_MANIFEST_URL, DEFAULT_TIER_ID, DEPLOYMENT_PENDING_WARNING_MS, type DeploymentProgressEmitter, type EthereumProviderLike, IDLE_DEPLOYMENT_PROGRESS, INSTANCE_RUNTIME_PENDING_REFRESH_MS, INSTANCE_RUNTIME_READY_REFRESH_MS, INSTANCE_RUNTIME_STALE_REFRESH_MS, MANIFEST_SOURCE_REFRESH_DEBOUNCE_MS, RECENT_INSTANCE_RUNTIME_GRACE_MS, REFRESH_INTERVAL_MS, RELAY_PING_IDLE_STATE, RELAY_PING_INTERVAL_MS, ROOTFS_MISSING_STATE, type RelayPingState, STALE_INSTANCE_ALLOCATION_COOLDOWN_MS, SponsorRelayController, type SponsorRelayHealthTone, type SponsorRelayPricingSummary, type SponsorRelayProps, type SponsorRelayRootfsHealth, type SponsorRelayState, type SponsorRelaySubscriber, type SponsorRelayWalletState, UI_PACKAGE_VERSION, buildSshCommand, connectWallet, createDeploymentProgressEmitter, createSponsorRelayController, formatDateTime, formatNumber, formatTierSpecLabel, getEthereumProvider, joinMappedPorts, joinRequiredPortForwards, personalSign, resolveManifestSource, shortHash, toChecksumAddress, watchWallet };
405
+ export { type CompactBootstrapRegistrationRecord, type CompactInstanceDetails, type CompactInstanceRecord, DEFAULT_INSTANCE_NAME, DEFAULT_MANIFEST_URL, DEFAULT_TIER_ID, DEPLOYMENT_PENDING_WARNING_MS, type DeploymentProgressEmitter, type EthereumProviderLike, IDLE_DEPLOYMENT_PROGRESS, INSTANCE_RUNTIME_PENDING_REFRESH_MS, INSTANCE_RUNTIME_READY_REFRESH_MS, INSTANCE_RUNTIME_STALE_REFRESH_MS, MANIFEST_SOURCE_REFRESH_DEBOUNCE_MS, RECENT_INSTANCE_RUNTIME_GRACE_MS, REFRESH_INTERVAL_MS, ROOTFS_MISSING_STATE, STALE_INSTANCE_ALLOCATION_COOLDOWN_MS, SponsorRelayController, type SponsorRelayHealthTone, type SponsorRelayPricingSummary, type SponsorRelayProps, type SponsorRelayRootfsHealth, type SponsorRelayState, type SponsorRelaySubscriber, type SponsorRelayWalletState, UI_PACKAGE_VERSION, buildSshCommand, connectWallet, createDeploymentProgressEmitter, createSponsorRelayController, formatDateTime, formatNumber, formatTierSpecLabel, getEthereumProvider, joinMappedPorts, joinRequiredPortForwards, personalSign, resolveManifestSource, shortHash, toChecksumAddress, watchWallet };
package/shared/index.js CHANGED
@@ -7,16 +7,6 @@ var ROOTFS_MISSING_STATE = {
7
7
  label: "manifest missing",
8
8
  detail: "Provide a manifest URL or paste manifest JSON."
9
9
  };
10
- var RELAY_PING_IDLE_STATE = {
11
- tone: "idle",
12
- sent: false,
13
- received: false,
14
- lastPeerId: null,
15
- lastLatencyMs: null,
16
- lastSentAt: null,
17
- lastReceivedAt: null,
18
- error: null
19
- };
20
10
  var IDLE_DEPLOYMENT_PROGRESS = {
21
11
  stage: "idle",
22
12
  label: "Ready",
@@ -28,7 +18,6 @@ var IDLE_DEPLOYMENT_PROGRESS = {
28
18
  timestamp: Date.now()
29
19
  };
30
20
  var REFRESH_INTERVAL_MS = 3e4;
31
- var RELAY_PING_INTERVAL_MS = 2e4;
32
21
  var MANIFEST_SOURCE_REFRESH_DEBOUNCE_MS = 400;
33
22
  var INSTANCE_RUNTIME_READY_REFRESH_MS = 3e4;
34
23
  var INSTANCE_RUNTIME_PENDING_REFRESH_MS = 45e3;
@@ -38,7 +27,7 @@ var RECENT_INSTANCE_RUNTIME_GRACE_MS = 10 * 60 * 1e3;
38
27
  var DEPLOYMENT_PENDING_WARNING_MS = 3 * 60 * 1e3;
39
28
 
40
29
  // src/shared/package-version.ts
41
- var UI_PACKAGE_VERSION = "0.4.0";
30
+ var UI_PACKAGE_VERSION = "0.4.1";
42
31
 
43
32
  // src/shared/format.ts
44
33
  function shortHash(value, head = 8, tail = 6) {
@@ -2561,7 +2550,7 @@ async function waitForRelayBootstrapRegistration(args) {
2561
2550
  return false;
2562
2551
  });
2563
2552
  if (match?.content) {
2564
- return {
2553
+ const visibleRegistration = {
2565
2554
  itemHash: match.itemHash ?? null,
2566
2555
  hash: match.hash ?? null,
2567
2556
  sender: typeof match.address === "string" ? match.address : null,
@@ -2570,7 +2559,10 @@ async function waitForRelayBootstrapRegistration(args) {
2570
2559
  multiaddrs: Array.isArray(match.content.multiaddrs) ? match.content.multiaddrs : [],
2571
2560
  browserMultiaddrs: Array.isArray(match.content.browserMultiaddrs) ? match.content.browserMultiaddrs : []
2572
2561
  };
2562
+ args.onAttempt?.(visibleRegistration, attempt + 1, attempts);
2563
+ return visibleRegistration;
2573
2564
  }
2565
+ args.onAttempt?.(null, attempt + 1, attempts);
2574
2566
  if (attempt < attempts - 1) {
2575
2567
  await sleep(delayMs);
2576
2568
  }
@@ -2943,7 +2935,6 @@ function defaultState(props = {}) {
2943
2935
  instances: [],
2944
2936
  bootstrapRegistrations: [],
2945
2937
  orphanBootstrapRegistrations: [],
2946
- relayPing: RELAY_PING_IDLE_STATE,
2947
2938
  lastDeploymentHash: null,
2948
2939
  deploymentProgress: IDLE_DEPLOYMENT_PROGRESS
2949
2940
  };
@@ -3170,57 +3161,6 @@ function compatibleCrnsForTier(crns, state) {
3170
3161
  );
3171
3162
  return filterDeployableCrns(crns, { spec });
3172
3163
  }
3173
- async function pingPeer(libp2p) {
3174
- if (!libp2p || typeof libp2p !== "object") {
3175
- return RELAY_PING_IDLE_STATE;
3176
- }
3177
- const candidate = libp2p;
3178
- const peers = candidate.getPeers?.() ?? [];
3179
- const firstPeer = peers[0];
3180
- if (!firstPeer) {
3181
- return {
3182
- ...RELAY_PING_IDLE_STATE,
3183
- tone: "caution",
3184
- error: "No connected relay peers available."
3185
- };
3186
- }
3187
- const sentAt = Date.now();
3188
- try {
3189
- const pingFn = candidate.services?.ping?.ping?.bind(candidate.services.ping) ?? candidate.ping?.bind(candidate);
3190
- if (!pingFn) {
3191
- return {
3192
- ...RELAY_PING_IDLE_STATE,
3193
- tone: "caution",
3194
- sent: true,
3195
- lastPeerId: String(firstPeer),
3196
- lastSentAt: sentAt,
3197
- error: "libp2p ping service not available."
3198
- };
3199
- }
3200
- const latency = await pingFn(firstPeer);
3201
- return {
3202
- tone: "ok",
3203
- sent: true,
3204
- received: true,
3205
- lastPeerId: String(firstPeer),
3206
- lastLatencyMs: Number(latency),
3207
- lastSentAt: sentAt,
3208
- lastReceivedAt: Date.now(),
3209
- error: null
3210
- };
3211
- } catch (error) {
3212
- return {
3213
- tone: "error",
3214
- sent: true,
3215
- received: false,
3216
- lastPeerId: String(firstPeer),
3217
- lastLatencyMs: null,
3218
- lastSentAt: sentAt,
3219
- lastReceivedAt: null,
3220
- error: error instanceof Error ? error.message : String(error)
3221
- };
3222
- }
3223
- }
3224
3164
  var UI_DEPLOY_WAIT_ATTEMPTS = 60;
3225
3165
  var UI_DEPLOY_WAIT_DELAY_MS = 5e3;
3226
3166
  var UI_RUNTIME_WAIT_ATTEMPTS = 40;
@@ -3251,7 +3191,6 @@ var SponsorRelayController = class {
3251
3191
  subscribers = /* @__PURE__ */ new Set();
3252
3192
  client;
3253
3193
  refreshTimer = null;
3254
- pingTimer = null;
3255
3194
  manifestRefreshTimer = null;
3256
3195
  stopWalletWatch = null;
3257
3196
  props;
@@ -3307,8 +3246,7 @@ var SponsorRelayController = class {
3307
3246
  ...patch,
3308
3247
  busy: patch.busy ? { ...this.state.busy, ...patch.busy } : this.state.busy,
3309
3248
  wallet: patch.wallet ? { ...this.state.wallet, ...patch.wallet } : this.state.wallet,
3310
- pricingSummary: patch.pricingSummary ? { ...this.state.pricingSummary, ...patch.pricingSummary } : this.state.pricingSummary,
3311
- relayPing: patch.relayPing ? { ...this.state.relayPing, ...patch.relayPing } : this.state.relayPing
3249
+ pricingSummary: patch.pricingSummary ? { ...this.state.pricingSummary, ...patch.pricingSummary } : this.state.pricingSummary
3312
3250
  };
3313
3251
  this.emit();
3314
3252
  }
@@ -3361,7 +3299,17 @@ var SponsorRelayController = class {
3361
3299
  fetch: (url, init) => fetch(url, init),
3362
3300
  attempts: 15,
3363
3301
  delayMs: 4e3,
3364
- httpTimeoutMs: 1e4
3302
+ httpTimeoutMs: 1e4,
3303
+ onAttempt: (result, attempt, attempts) => {
3304
+ this.emitProgress({
3305
+ stage: "deployment-confirmed",
3306
+ label: "Waiting for guest setup",
3307
+ progress: 93,
3308
+ status: result.ok ? "success" : "info",
3309
+ itemHash: args.itemHash,
3310
+ detail: result.ok ? `Setup endpoint ${attempt}/${attempts}: ready at http://${args.runtime.hostIpv4}:${setupPort}/health.` : `Setup endpoint ${attempt}/${attempts}: waiting for http://${args.runtime.hostIpv4}:${setupPort}/health.`
3311
+ });
3312
+ }
3365
3313
  });
3366
3314
  if (!setupHealth.ok) {
3367
3315
  throw new Error(
@@ -3490,6 +3438,14 @@ var SponsorRelayController = class {
3490
3438
  metadata?.browser_bootstrap_multiaddrs
3491
3439
  ) ? metadata.browser_bootstrap_multiaddrs.length : 0
3492
3440
  });
3441
+ this.emitProgress({
3442
+ stage: "publishing-bootstrap",
3443
+ label: "Reading relay multiaddrs",
3444
+ progress: 95,
3445
+ status: result.ready ? "success" : "info",
3446
+ itemHash: args.itemHash,
3447
+ detail: result.ready ? `Relay metadata ${result.attempt}/${result.attempts}: public multiaddrs available.` : `Relay metadata ${result.attempt}/${result.attempts}: waiting for the relay to report public multiaddrs.`
3448
+ });
3493
3449
  }
3494
3450
  });
3495
3451
  if (!relayMetadata) {
@@ -3515,7 +3471,17 @@ var SponsorRelayController = class {
3515
3471
  fetch: asJsonFetch,
3516
3472
  apiHost: this.client.apiHost,
3517
3473
  attempts: 24,
3518
- delayMs: 2500
3474
+ delayMs: 2500,
3475
+ onAttempt: (match, attempt, attempts) => {
3476
+ this.emitProgress({
3477
+ stage: "publishing-bootstrap",
3478
+ label: "Waiting for guest bootstrap registration",
3479
+ progress: 97,
3480
+ status: match ? "success" : "info",
3481
+ itemHash: args.itemHash,
3482
+ detail: match ? `Guest bootstrap registration ${attempt}/${attempts}: visible on Aleph.` : `Guest bootstrap registration ${attempt}/${attempts}: still waiting for the relay VM registration on Aleph.`
3483
+ });
3484
+ }
3519
3485
  });
3520
3486
  this.trace("deploy:bootstrap-registration-visible", {
3521
3487
  itemHash: args.itemHash,
@@ -3566,7 +3532,17 @@ var SponsorRelayController = class {
3566
3532
  fetch: asJsonFetch,
3567
3533
  apiHost: this.client.apiHost,
3568
3534
  attempts: 24,
3569
- delayMs: 2500
3535
+ delayMs: 2500,
3536
+ onAttempt: (match, attempt, attempts) => {
3537
+ this.emitProgress({
3538
+ stage: "publishing-bootstrap",
3539
+ label: "Waiting for bootstrap fallback",
3540
+ progress: 98,
3541
+ status: match ? "success" : "warning",
3542
+ itemHash: args.itemHash,
3543
+ detail: match ? `Bootstrap fallback ${attempt}/${attempts}: visible on Aleph.` : `Bootstrap fallback ${attempt}/${attempts}: waiting for the browser-published registration on Aleph.`
3544
+ });
3545
+ }
3570
3546
  });
3571
3547
  this.trace("deploy:bootstrap-registration-fallback-visible", {
3572
3548
  itemHash: args.itemHash,
@@ -3742,16 +3718,11 @@ var SponsorRelayController = class {
3742
3718
  this.refreshTimer = setInterval(() => {
3743
3719
  void this.refresh();
3744
3720
  }, REFRESH_INTERVAL_MS);
3745
- this.pingTimer = setInterval(() => {
3746
- void this.refreshRelayPing();
3747
- }, RELAY_PING_INTERVAL_MS);
3748
- await this.refreshRelayPing();
3749
3721
  this.patch({ ready: true });
3750
3722
  this.trace("init:ready");
3751
3723
  }
3752
3724
  destroy() {
3753
3725
  if (this.refreshTimer) clearInterval(this.refreshTimer);
3754
- if (this.pingTimer) clearInterval(this.pingTimer);
3755
3726
  if (this.manifestRefreshTimer) clearTimeout(this.manifestRefreshTimer);
3756
3727
  this.stopWalletWatch?.();
3757
3728
  }
@@ -4046,11 +4017,6 @@ var SponsorRelayController = class {
4046
4017
  });
4047
4018
  }
4048
4019
  }
4049
- async refreshRelayPing() {
4050
- const relayPing = await pingPeer(this.props.libp2p);
4051
- this.patch({ relayPing });
4052
- this.trace("libp2p:relay-ping", relayPing);
4053
- }
4054
4020
  async deploy() {
4055
4021
  this.trace("deploy:start", {
4056
4022
  wallet: this.state.wallet.address,
@@ -4167,7 +4133,7 @@ var SponsorRelayController = class {
4167
4133
  fetch: (url, init) => fetch(url, init),
4168
4134
  attempts: UI_DEPLOY_WAIT_ATTEMPTS,
4169
4135
  delayMs: UI_DEPLOY_WAIT_DELAY_MS,
4170
- onAttempt: (inspectionResult) => {
4136
+ onAttempt: (inspectionResult, attempt, attempts) => {
4171
4137
  if (inspectionResult.status === "processed") {
4172
4138
  this.emitProgress({
4173
4139
  stage: "deployment-confirmed",
@@ -4175,7 +4141,7 @@ var SponsorRelayController = class {
4175
4141
  progress: 82,
4176
4142
  status: "success",
4177
4143
  itemHash: result.itemHash,
4178
- detail: "Aleph processed the instance message."
4144
+ detail: `Aleph processing ${attempt}/${attempts}: instance message processed.`
4179
4145
  });
4180
4146
  return;
4181
4147
  }
@@ -4197,7 +4163,7 @@ var SponsorRelayController = class {
4197
4163
  progress: 76,
4198
4164
  status: "info",
4199
4165
  itemHash: result.itemHash,
4200
- detail: "Deployment submitted. Waiting for Aleph to process the instance message."
4166
+ detail: `Aleph processing ${attempt}/${attempts}: waiting for the instance message to be processed.`
4201
4167
  });
4202
4168
  }
4203
4169
  });
@@ -4263,7 +4229,7 @@ var SponsorRelayController = class {
4263
4229
  crnListUrl: this.props.crnListUrl,
4264
4230
  attempts: UI_RUNTIME_WAIT_ATTEMPTS,
4265
4231
  delayMs: UI_RUNTIME_WAIT_DELAY_MS,
4266
- onAttempt: (runtime2) => {
4232
+ onAttempt: (runtime2, attempt, attempts) => {
4267
4233
  if (runtime2.hostIpv4 && Object.keys(runtime2.mappedPorts ?? {}).length > 0) {
4268
4234
  this.emitProgress({
4269
4235
  stage: "deployment-confirmed",
@@ -4271,7 +4237,7 @@ var SponsorRelayController = class {
4271
4237
  progress: 92,
4272
4238
  status: "info",
4273
4239
  itemHash: result.itemHash,
4274
- detail: "Runtime networking and mapped ports are now available. Finishing relay setup and bootstrap verification."
4240
+ detail: `Runtime ${attempt}/${attempts}: networking and mapped ports are now available.`
4275
4241
  });
4276
4242
  return;
4277
4243
  }
@@ -4281,7 +4247,7 @@ var SponsorRelayController = class {
4281
4247
  progress: 90,
4282
4248
  status: "warning",
4283
4249
  itemHash: result.itemHash,
4284
- detail: runtime2.diagnostics?.reason ?? "Waiting for CRN runtime networking and mapped ports."
4250
+ detail: runtime2.diagnostics?.reason ? `Runtime ${attempt}/${attempts}: ${runtime2.diagnostics.reason}` : `Runtime ${attempt}/${attempts}: waiting for CRN runtime networking and mapped ports.`
4285
4251
  });
4286
4252
  }
4287
4253
  });
@@ -4578,8 +4544,6 @@ export {
4578
4544
  MANIFEST_SOURCE_REFRESH_DEBOUNCE_MS,
4579
4545
  RECENT_INSTANCE_RUNTIME_GRACE_MS,
4580
4546
  REFRESH_INTERVAL_MS,
4581
- RELAY_PING_IDLE_STATE,
4582
- RELAY_PING_INTERVAL_MS,
4583
4547
  ROOTFS_MISSING_STATE,
4584
4548
  STALE_INSTANCE_ALLOCATION_COOLDOWN_MS,
4585
4549
  SponsorRelayController,
@@ -236,7 +236,7 @@ export let apiHost = undefined
236
236
  {#if confirmedRegistration}
237
237
  <span class="chip chip-confirmed">
238
238
  <span class="chip-dot-confirmed"></span>
239
- Registration confirmed
239
+ Aleph bootstrap registered
240
240
  </span>
241
241
  {/if}
242
242
  </div>
@@ -318,7 +318,7 @@ export let apiHost = undefined
318
318
  <div>Browser multiaddrs: {String(entry.content?.browserMultiaddrs?.length ?? 0)}</div>
319
319
  <div>Updated: {formatDateTime(entry.content?.updatedAt ?? entry.time)}</div>
320
320
  <button
321
- class="delete"
321
+ class="warning"
322
322
  type="button"
323
323
  disabled={!registrationHash || state.busy.deletingRegistrationHash === registrationHash}
324
324
  on:click={() => registrationHash && controller.deleteBootstrapRegistration(registrationHash)}
@@ -429,24 +429,41 @@ export let apiHost = undefined
429
429
 
430
430
  .refresh,
431
431
  .primary,
432
- .delete {
433
- border: 1px solid rgba(255, 255, 255, 0.12);
432
+ .delete,
433
+ .warning {
434
434
  border-radius: 0.95rem;
435
435
  padding: 0.7rem 0.9rem;
436
436
  cursor: pointer;
437
+ font-weight: 700;
438
+ line-height: 1.1;
439
+ }
440
+
441
+ .refresh {
442
+ border: 1px solid rgba(255, 255, 255, 0.22);
437
443
  color: var(--relay-text);
438
- background: rgba(255, 255, 255, 0.05);
444
+ background: rgba(255, 255, 255, 0.1);
445
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
446
+ backdrop-filter: blur(8px);
439
447
  }
440
448
 
441
449
  .primary {
442
450
  width: 100%;
443
- background: linear-gradient(135deg, var(--relay-blue), var(--relay-red));
444
- font-weight: 700;
451
+ border: 1px solid rgba(251, 191, 36, 0.42);
452
+ background: linear-gradient(135deg, #f6c453 0%, #f59e0b 100%);
453
+ color: #281606;
454
+ box-shadow: 0 10px 24px rgba(245, 158, 11, 0.24);
445
455
  }
446
456
 
447
457
  .delete {
448
- background: rgba(233, 19, 21, 0.15);
449
- color: #ffd4d4;
458
+ border: 1px solid rgba(248, 113, 113, 0.45);
459
+ background: rgba(239, 68, 68, 0.18);
460
+ color: #ffe2e2;
461
+ }
462
+
463
+ .warning {
464
+ border: 1px solid rgba(251, 146, 60, 0.4);
465
+ background: rgba(251, 146, 60, 0.16);
466
+ color: #fed7aa;
450
467
  }
451
468
 
452
469
  .chip-confirmed {