@nebulr-group/bridge-svelte 0.9.0-beta.3 → 0.9.0-beta.5
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/dist/client/components/subscription/BridgeBillingNotice.svelte +16 -15
- package/dist/client/components/subscription/BridgePaywall.svelte +1 -1
- package/dist/client/components/subscription/BridgeQuotaBanner.svelte +6 -6
- package/dist/client/components/subscription/BridgeSubscriptionStatus.svelte +12 -12
- package/dist/core/bridge-runtime.js +162 -57
- package/dist/core/snapshot-stores.d.ts +7 -0
- package/dist/core/snapshot-stores.js +1 -0
- package/dist/styles.css +145 -49
- package/package.json +2 -2
|
@@ -284,7 +284,7 @@
|
|
|
284
284
|
|
|
285
285
|
.bbn-cta:hover {
|
|
286
286
|
background: currentColor;
|
|
287
|
-
color:
|
|
287
|
+
color: var(--bridge-bg, #ffffff);
|
|
288
288
|
}
|
|
289
289
|
|
|
290
290
|
/* Chassis variants */
|
|
@@ -300,28 +300,29 @@
|
|
|
300
300
|
|
|
301
301
|
.bbn-chassis-card {
|
|
302
302
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
303
|
-
border: 1px solid
|
|
303
|
+
border: 1px solid var(--bridge-border, #d1d5db);
|
|
304
304
|
}
|
|
305
305
|
|
|
306
306
|
/* Severity tokens */
|
|
307
307
|
.bbn-severity-info {
|
|
308
|
-
background: #
|
|
309
|
-
color: #1e40af;
|
|
308
|
+
background: var(--bridge-alert-info-bg, #eff6ff);
|
|
309
|
+
color: var(--bridge-alert-info-fg, #1e40af);
|
|
310
310
|
}
|
|
311
311
|
|
|
312
312
|
.bbn-severity-warn {
|
|
313
|
-
background: #
|
|
314
|
-
color: #92400e;
|
|
313
|
+
background: var(--bridge-alert-warning-bg, #fffbeb);
|
|
314
|
+
color: var(--bridge-alert-warning-fg, #92400e);
|
|
315
315
|
}
|
|
316
316
|
|
|
317
317
|
.bbn-severity-critical {
|
|
318
|
-
background: #
|
|
319
|
-
color: #991b1b;
|
|
318
|
+
background: var(--bridge-alert-error-bg, #fef2f2);
|
|
319
|
+
color: var(--bridge-alert-error-fg, #991b1b);
|
|
320
320
|
}
|
|
321
321
|
|
|
322
|
+
/* Locked inverts the surface: foreground as background, and vice versa. */
|
|
322
323
|
.bbn-severity-locked {
|
|
323
|
-
background: #
|
|
324
|
-
color: #
|
|
324
|
+
background: var(--bridge-foreground, #111827);
|
|
325
|
+
color: var(--bridge-bg, #ffffff);
|
|
325
326
|
}
|
|
326
327
|
|
|
327
328
|
@media (prefers-reduced-motion: reduce) {
|
|
@@ -339,7 +340,7 @@
|
|
|
339
340
|
align-items: center;
|
|
340
341
|
justify-content: center;
|
|
341
342
|
padding: 1rem;
|
|
342
|
-
background: rgba(15, 23, 42, 0.72);
|
|
343
|
+
background: var(--bridge-paywall-bg, rgba(15, 23, 42, 0.72));
|
|
343
344
|
backdrop-filter: blur(2px);
|
|
344
345
|
}
|
|
345
346
|
|
|
@@ -351,8 +352,8 @@
|
|
|
351
352
|
width: 100%;
|
|
352
353
|
padding: 1.5rem;
|
|
353
354
|
border-radius: 0.75rem;
|
|
354
|
-
background: #
|
|
355
|
-
color: #
|
|
355
|
+
background: var(--bridge-foreground, #111827);
|
|
356
|
+
color: var(--bridge-bg, #ffffff);
|
|
356
357
|
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.35);
|
|
357
358
|
text-align: center;
|
|
358
359
|
}
|
|
@@ -381,7 +382,7 @@
|
|
|
381
382
|
}
|
|
382
383
|
|
|
383
384
|
.bbl-cta:hover {
|
|
384
|
-
background: #
|
|
385
|
-
color: #
|
|
385
|
+
background: var(--bridge-bg, #ffffff);
|
|
386
|
+
color: var(--bridge-foreground, #111827);
|
|
386
387
|
}
|
|
387
388
|
</style>
|
|
@@ -283,7 +283,7 @@
|
|
|
283
283
|
margin-top: 0.25rem;
|
|
284
284
|
width: 100%;
|
|
285
285
|
height: 0.375rem;
|
|
286
|
-
background:
|
|
286
|
+
background: color-mix(in srgb, currentColor 15%, transparent);
|
|
287
287
|
border-radius: 999px;
|
|
288
288
|
overflow: hidden;
|
|
289
289
|
}
|
|
@@ -309,7 +309,7 @@
|
|
|
309
309
|
|
|
310
310
|
.bqb-cta:hover {
|
|
311
311
|
background: currentColor;
|
|
312
|
-
color:
|
|
312
|
+
color: var(--bridge-bg, #ffffff);
|
|
313
313
|
}
|
|
314
314
|
|
|
315
315
|
/* Chassis — rail only for US-11. */
|
|
@@ -320,13 +320,13 @@
|
|
|
320
320
|
|
|
321
321
|
/* Severity tokens — mirror BridgeBillingNotice for visual consistency. */
|
|
322
322
|
.bqb-severity-warn {
|
|
323
|
-
background: #
|
|
324
|
-
color: #92400e;
|
|
323
|
+
background: var(--bridge-alert-warning-bg, #fffbeb);
|
|
324
|
+
color: var(--bridge-alert-warning-fg, #92400e);
|
|
325
325
|
}
|
|
326
326
|
|
|
327
327
|
.bqb-severity-critical {
|
|
328
|
-
background: #
|
|
329
|
-
color: #991b1b;
|
|
328
|
+
background: var(--bridge-alert-error-bg, #fef2f2);
|
|
329
|
+
color: var(--bridge-alert-error-fg, #991b1b);
|
|
330
330
|
}
|
|
331
331
|
|
|
332
332
|
@media (prefers-reduced-motion: reduce) {
|
|
@@ -79,38 +79,38 @@
|
|
|
79
79
|
font-size: 0.75rem;
|
|
80
80
|
font-weight: 500;
|
|
81
81
|
text-transform: lowercase;
|
|
82
|
-
background: #
|
|
83
|
-
color: #
|
|
82
|
+
background: var(--bridge-muted-bg, var(--bridge-bg-muted, #f3f4f6));
|
|
83
|
+
color: var(--bridge-foreground, #111827);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
.bss-badge-active {
|
|
87
|
-
background: #
|
|
88
|
-
color: #
|
|
87
|
+
background: var(--bridge-alert-success-bg, #f0fdf4);
|
|
88
|
+
color: var(--bridge-alert-success-fg, #166534);
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
.bss-badge-trial {
|
|
92
|
-
background: #
|
|
93
|
-
color: #1e40af;
|
|
92
|
+
background: var(--bridge-alert-info-bg, #eff6ff);
|
|
93
|
+
color: var(--bridge-alert-info-fg, #1e40af);
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
.bss-badge-past_due,
|
|
97
97
|
.bss-badge-cancel_at_period_end {
|
|
98
|
-
background: #
|
|
99
|
-
color: #92400e;
|
|
98
|
+
background: var(--bridge-alert-warning-bg, #fffbeb);
|
|
99
|
+
color: var(--bridge-alert-warning-fg, #92400e);
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
.bss-badge-canceled {
|
|
103
|
-
background: #
|
|
104
|
-
color: #991b1b;
|
|
103
|
+
background: var(--bridge-alert-error-bg, #fef2f2);
|
|
104
|
+
color: var(--bridge-alert-error-fg, #991b1b);
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
.bss-loading,
|
|
108
108
|
.bss-empty {
|
|
109
|
-
color: #6b7280;
|
|
109
|
+
color: var(--bridge-muted, #6b7280);
|
|
110
110
|
font-style: italic;
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
.bss-error {
|
|
114
|
-
color: #
|
|
114
|
+
color: var(--bridge-alert-error-fg, #991b1b);
|
|
115
115
|
}
|
|
116
116
|
</style>
|
|
@@ -176,8 +176,8 @@ export function startBridgeRuntime(options = {}) {
|
|
|
176
176
|
// of parking. A signed-out session has nothing to refresh. Loop safety:
|
|
177
177
|
// the refreshed token lands in the tokenStore subscription below, whose
|
|
178
178
|
// reauthorize() is a no-op while that episode is still connecting, and
|
|
179
|
-
// the
|
|
180
|
-
//
|
|
179
|
+
// the reconcile after the reconnect (TBP-700) mints once more and stops
|
|
180
|
+
// there — a token that changes nothing does not replace the socket.
|
|
181
181
|
refreshAuthToken: options.realtime?.refreshAuthToken ??
|
|
182
182
|
(async () => {
|
|
183
183
|
if (!_currentAuthToken)
|
|
@@ -191,11 +191,10 @@ export function startBridgeRuntime(options = {}) {
|
|
|
191
191
|
}
|
|
192
192
|
}),
|
|
193
193
|
});
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
let _reauthInFlight = false;
|
|
194
|
+
// TBP-700 — bookkeeping for reconcileUserState() below.
|
|
195
|
+
let _reconciling = 0;
|
|
196
|
+
let _reconcileSawChange = false;
|
|
197
|
+
let _reconcileSwapsInARow = 0;
|
|
199
198
|
// TBP-660 — catch up after ANY reconnect, including the one our own
|
|
200
199
|
// reauthorize() causes. AppSync Events has no replay, so a push published
|
|
201
200
|
// while the socket is being replaced is gone for good — and a plan change
|
|
@@ -240,11 +239,12 @@ export function startBridgeRuntime(options = {}) {
|
|
|
240
239
|
return;
|
|
241
240
|
const data = (await res.json());
|
|
242
241
|
// Stopped, or the session changed while this was in flight: the answer
|
|
243
|
-
// describes a session we no longer have. A token
|
|
244
|
-
// and
|
|
245
|
-
|
|
242
|
+
// describes a session we no longer have. A new token for the SAME user,
|
|
243
|
+
// workspace and app is not a new session — the reconcile below rotates
|
|
244
|
+
// the token on every open, concurrently with this read (TBP-700).
|
|
245
|
+
if (_realtime !== rt || !sameSession(token, _currentAuthToken))
|
|
246
246
|
return;
|
|
247
|
-
const { planChanged, entitlementsChanged } = applyCatchUpSnapshot(data);
|
|
247
|
+
const { planChanged, entitlementsChanged, initial } = applyCatchUpSnapshot(data);
|
|
248
248
|
if (entitlementsChanged && data?.tenant?.entitlements) {
|
|
249
249
|
// Keep auth-core's copy (what flag targeting reads) in step too.
|
|
250
250
|
try {
|
|
@@ -254,6 +254,14 @@ export function startBridgeRuntime(options = {}) {
|
|
|
254
254
|
}
|
|
255
255
|
// TBP-654 — a recovered change must reach the route guard exactly like
|
|
256
256
|
// the push it replaces would have. Nothing changed → nothing to redo.
|
|
257
|
+
//
|
|
258
|
+
// TBP-700 — and filling empty stores for the first time recovers no
|
|
259
|
+
// change: nothing was decided against a plan this page never knew. It
|
|
260
|
+
// used to count as a plan change, which refreshed the token and swapped
|
|
261
|
+
// the socket right after every first connect — the window a role change
|
|
262
|
+
// was lost in on stage. The access token is reconciled below instead.
|
|
263
|
+
if (initial)
|
|
264
|
+
return;
|
|
257
265
|
if (planChanged)
|
|
258
266
|
authorizationChanged('subscription.plan_changed');
|
|
259
267
|
else if (entitlementsChanged)
|
|
@@ -335,7 +343,7 @@ export function startBridgeRuntime(options = {}) {
|
|
|
335
343
|
const data = (await res.json());
|
|
336
344
|
// Stopped, or the session changed while this was in flight: the
|
|
337
345
|
// answer describes a workspace we no longer have.
|
|
338
|
-
if (_realtime !== rt || _currentAuthToken
|
|
346
|
+
if (_realtime !== rt || !sameSession(token, _currentAuthToken))
|
|
339
347
|
return;
|
|
340
348
|
if (quotas.getAll().get(metric) !== before)
|
|
341
349
|
return; // a push won the race
|
|
@@ -348,6 +356,57 @@ export function startBridgeRuntime(options = {}) {
|
|
|
348
356
|
}
|
|
349
357
|
}));
|
|
350
358
|
};
|
|
359
|
+
// TBP-700 — the same repair for the user's own state: role, privileges,
|
|
360
|
+
// anything that bumps the server's tokenVersion and publishes
|
|
361
|
+
// `user.state_changed`. That push is lost exactly like the others when it
|
|
362
|
+
// is published while the socket is being replaced, and before this nothing
|
|
363
|
+
// recovered it: the session snapshot's `user` is read from the token being
|
|
364
|
+
// presented, and a reconnect our own reauthorize() caused skipped the token
|
|
365
|
+
// refresh (the TBP-644 loop guard). On stage a role change landed in that
|
|
366
|
+
// window in 4 of 4 runs once the first connect started swapping its socket
|
|
367
|
+
// (TBP-686), and the user kept the old role until a reload.
|
|
368
|
+
//
|
|
369
|
+
// The server's current tokenVersion and claims are only available by
|
|
370
|
+
// minting: the refresh endpoint reads them from the database, uncached. So
|
|
371
|
+
// every connect and reconnect ends with ONE refresh, and the token that
|
|
372
|
+
// comes back is compared with the one we had:
|
|
373
|
+
// - same authority (only iat/exp/jti moved): nothing was missed, and the
|
|
374
|
+
// socket that was just subscribed stays — no swap, so no loop;
|
|
375
|
+
// - anything else (tv, role, plan…): that IS the missed change. It is
|
|
376
|
+
// handled like the push would have been — the route guard re-checks and
|
|
377
|
+
// the socket is re-authorized with the new token — and the reconnect
|
|
378
|
+
// reconciles again, which then finds nothing new.
|
|
379
|
+
//
|
|
380
|
+
// `fresh: true` (auth-core, TBP-700) keeps this from joining a
|
|
381
|
+
// refresh that started before the channels were live, whose token could
|
|
382
|
+
// predate the very change we are looking for. An older auth-core ignores
|
|
383
|
+
// the option and joins it — best effort, as before.
|
|
384
|
+
const reconcileUserState = async () => {
|
|
385
|
+
if (_realtime !== rt || !_currentAuthToken)
|
|
386
|
+
return; // signed out: no user state
|
|
387
|
+
let auth;
|
|
388
|
+
try {
|
|
389
|
+
auth = getBridgeAuth();
|
|
390
|
+
}
|
|
391
|
+
catch {
|
|
392
|
+
return; // BridgeAuth not constructed — nothing to refresh with.
|
|
393
|
+
}
|
|
394
|
+
_reconciling += 1;
|
|
395
|
+
_reconcileSawChange = false;
|
|
396
|
+
try {
|
|
397
|
+
const refresh = auth.refreshTokens;
|
|
398
|
+
await refresh.call(auth, { fresh: true });
|
|
399
|
+
}
|
|
400
|
+
catch {
|
|
401
|
+
// Best-effort: the TOKEN_VERSION_STALE retry in bridgeAuthFetch and the
|
|
402
|
+
// next reconnect are the fallbacks.
|
|
403
|
+
}
|
|
404
|
+
finally {
|
|
405
|
+
_reconciling -= 1;
|
|
406
|
+
if (!_reconcileSawChange)
|
|
407
|
+
_reconcileSwapsInARow = 0;
|
|
408
|
+
}
|
|
409
|
+
};
|
|
351
410
|
const requestCatchUp = () => {
|
|
352
411
|
if (!_currentAuthToken)
|
|
353
412
|
return; // signed out: no tenant or user scope to fetch
|
|
@@ -356,9 +415,9 @@ export function startBridgeRuntime(options = {}) {
|
|
|
356
415
|
return;
|
|
357
416
|
}
|
|
358
417
|
_catchUpInFlight = true;
|
|
359
|
-
//
|
|
360
|
-
// costs one round of requests, and
|
|
361
|
-
void Promise.all([catchUpSessionSnapshot(), catchUpQuotaSnapshots()]).catch(() => {
|
|
418
|
+
// Every repair rides the one in-flight/queued gate, so an open storm still
|
|
419
|
+
// costs one round of requests, and none can starve the others.
|
|
420
|
+
void Promise.all([catchUpSessionSnapshot(), catchUpQuotaSnapshots(), reconcileUserState()]).catch(() => {
|
|
362
421
|
// Both halves are already best-effort internally; this is the backstop
|
|
363
422
|
// that keeps a repair failure from surfacing as an unhandled rejection
|
|
364
423
|
// in the consuming app. `.finally` below still drains the queue.
|
|
@@ -370,41 +429,29 @@ export function startBridgeRuntime(options = {}) {
|
|
|
370
429
|
}
|
|
371
430
|
});
|
|
372
431
|
};
|
|
432
|
+
// TBP-700 — when to catch up. Everything above only works if it reads the
|
|
433
|
+
// server AFTER every channel is live: anything published before that is
|
|
434
|
+
// already in the database and the read sees it, anything after arrives on
|
|
435
|
+
// the socket. `open` fires on the FIRST accepted subscription, with the user
|
|
436
|
+
// channel possibly still pending, so an auth-core that can say "all
|
|
437
|
+
// subscribed" is asked; an older one falls back to `open`.
|
|
438
|
+
//
|
|
439
|
+
// One trigger for the initial connect, a genuine reconnect and the one our
|
|
440
|
+
// own reauthorize() causes alike: losing pushes is a property of the socket
|
|
441
|
+
// swap, whoever caused it (TBP-660), and the first connect is where the
|
|
442
|
+
// snapshot push always loses its race (TBP-686). `requestCatchUp` returns
|
|
443
|
+
// early when signed out and coalesces concurrent calls.
|
|
444
|
+
const realtimeWithSubscribed = _realtime;
|
|
445
|
+
const catchUpOnSubscribed = typeof realtimeWithSubscribed.setOnSubscribed === 'function';
|
|
446
|
+
if (catchUpOnSubscribed)
|
|
447
|
+
realtimeWithSubscribed.setOnSubscribed(() => requestCatchUp());
|
|
373
448
|
_realtime.setOnOpen(() => {
|
|
374
449
|
_setRealtimeStatus('open');
|
|
375
|
-
//
|
|
376
|
-
//
|
|
377
|
-
//
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
//
|
|
381
|
-
// EXCEPT when this reconnect was caused by our OWN reauthorize() below
|
|
382
|
-
// (a token-only refresh). In that case the token is already current, so
|
|
383
|
-
// refreshing again would mint yet another JWT, which the tokenStore
|
|
384
|
-
// subscription would see as a change and reauthorize() again → reconnect
|
|
385
|
-
// → setOnOpen → refresh → … an unbounded loop that hammers /auth/token
|
|
386
|
-
// (observed ~32 cycles/sec, jamming the page's main thread and stalling
|
|
387
|
-
// every downstream wait). Only genuine, externally-triggered reconnects
|
|
388
|
-
// (network blips, server restarts) should trigger the catch-up refresh.
|
|
389
|
-
const causedByReauthorize = _reauthInFlight;
|
|
390
|
-
_reauthInFlight = false;
|
|
391
|
-
if (_connectedOnce && !causedByReauthorize) {
|
|
392
|
-
getBridgeAuth().refreshTokens().catch(() => { });
|
|
393
|
-
}
|
|
394
|
-
// TBP-660 — the TOKEN refresh above is skipped for a self-induced
|
|
395
|
-
// reconnect; the STATE catch-up is not. Losing pushes is a property of
|
|
396
|
-
// the socket swap, whoever caused it, and the catch-up cannot loop.
|
|
397
|
-
//
|
|
398
|
-
// TBP-686 — and it runs on the FIRST connect too. It used to be gated on
|
|
399
|
-
// `_connectedOnce`, which read as "only repair a reconnect" — but the
|
|
400
|
-
// first connect is exactly where the snapshot goes missing: the server
|
|
401
|
-
// publishes it fire-and-forget during authorize, before the subscription
|
|
402
|
-
// is live, so it loses the race and there is nothing to replay. The result
|
|
403
|
-
// was a signed-in page with a null workspace id, name and branding for the
|
|
404
|
-
// whole session. `requestCatchUp` already returns early when signed out
|
|
405
|
-
// and coalesces concurrent calls, so this costs one request per session.
|
|
406
|
-
requestCatchUp();
|
|
407
|
-
_connectedOnce = true;
|
|
450
|
+
// No token refresh here any more (TBP-700): the reconcile in
|
|
451
|
+
// requestCatchUp mints on EVERY open, the self-induced ones included, and
|
|
452
|
+
// cannot loop — see reconcileUserState.
|
|
453
|
+
if (!catchUpOnSubscribed)
|
|
454
|
+
requestCatchUp();
|
|
408
455
|
for (const fn of _onOpenSubs) {
|
|
409
456
|
try {
|
|
410
457
|
fn();
|
|
@@ -437,10 +484,6 @@ export function startBridgeRuntime(options = {}) {
|
|
|
437
484
|
// open/close/degraded mirrors above keep `realtimeStatus` working there).
|
|
438
485
|
_realtime.setOnStatusChange?.((status) => {
|
|
439
486
|
_setRealtimeStatusDetail(status);
|
|
440
|
-
// A parked client never opens, so a reauthorize that ended in a refusal
|
|
441
|
-
// must not leave the self-induced flag set for the next genuine reconnect.
|
|
442
|
-
if (status.state === 'unauthorized')
|
|
443
|
-
_reauthInFlight = false;
|
|
444
487
|
for (const fn of _onStatusSubs) {
|
|
445
488
|
try {
|
|
446
489
|
fn(status);
|
|
@@ -560,10 +603,7 @@ export function startBridgeRuntime(options = {}) {
|
|
|
560
603
|
// (none → A) and sign-out (A → none). Keying this on rotation only meant a
|
|
561
604
|
// session that signed in after page load kept the anonymous connection —
|
|
562
605
|
// or stayed parked after a refusal — until something else reconnected it.
|
|
563
|
-
// Flagged self-induced so setOnOpen skips its catch-up refresh (see the
|
|
564
|
-
// loop note there): the token we reconnect with is already current.
|
|
565
606
|
const reauthorizeForTokenChange = () => {
|
|
566
|
-
_reauthInFlight = true;
|
|
567
607
|
void _realtime.reauthorize();
|
|
568
608
|
};
|
|
569
609
|
// `subscribe` emits the current value synchronously, before `start()` below.
|
|
@@ -572,7 +612,30 @@ export function startBridgeRuntime(options = {}) {
|
|
|
572
612
|
_unsubscribeAuth = tokenStore.subscribe((tokens) => {
|
|
573
613
|
const prevAuthToken = _currentAuthToken;
|
|
574
614
|
_currentAuthToken = tokens?.accessToken ?? undefined;
|
|
575
|
-
|
|
615
|
+
let tokenChanged = _tokenSubscriptionLive && prevAuthToken !== _currentAuthToken;
|
|
616
|
+
let reauthorize = tokenChanged;
|
|
617
|
+
// TBP-700 — the token minted by the post-(re)connect reconcile.
|
|
618
|
+
if (tokenChanged && _reconciling > 0) {
|
|
619
|
+
if (_realtime.getState?.() === 'open' && sameAuthorization(prevAuthToken, _currentAuthToken)) {
|
|
620
|
+
// Only its timing moved: nothing was missed, every verdict taken with
|
|
621
|
+
// the old token stands, and the socket that was just subscribed has
|
|
622
|
+
// exactly this authority. Replacing it would open the very window
|
|
623
|
+
// this repair exists for — and loop.
|
|
624
|
+
tokenChanged = false;
|
|
625
|
+
reauthorize = false;
|
|
626
|
+
}
|
|
627
|
+
else {
|
|
628
|
+
// The server moved on while we were not listening: the lost
|
|
629
|
+
// `user.state_changed`, recovered. Re-authorize like the push would
|
|
630
|
+
// have — bounded, so a claim that differs on every mint cannot turn
|
|
631
|
+
// this into a reconnect loop.
|
|
632
|
+
_reconcileSawChange = true;
|
|
633
|
+
if (_reconcileSwapsInARow >= MAX_RECONCILE_SWAPS)
|
|
634
|
+
reauthorize = false;
|
|
635
|
+
else
|
|
636
|
+
_reconcileSwapsInARow += 1;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
576
639
|
// TBP-654 + TBP-653 — a new token (sign-in, refresh after a plan change,
|
|
577
640
|
// sign-out) invalidates every verdict taken with the old one, and the
|
|
578
641
|
// current route is re-evaluated: a signed-out session on a protected page
|
|
@@ -616,7 +679,7 @@ export function startBridgeRuntime(options = {}) {
|
|
|
616
679
|
// setUserId is a no-op when the user is unchanged (token-only refresh),
|
|
617
680
|
// and a setter-driven reconnect waits out a backoff and cannot lift a
|
|
618
681
|
// parked refusal — so reauthorize explicitly on every value change.
|
|
619
|
-
if (
|
|
682
|
+
if (reauthorize)
|
|
620
683
|
reauthorizeForTokenChange();
|
|
621
684
|
});
|
|
622
685
|
_tokenSubscriptionLive = true;
|
|
@@ -739,6 +802,48 @@ export function __resetBridgeRuntime() {
|
|
|
739
802
|
_realtime = undefined;
|
|
740
803
|
}
|
|
741
804
|
// ── helpers ─────────────────────────────────────────────────────────────────
|
|
805
|
+
/**
|
|
806
|
+
* TBP-700 — how many reconcile-driven socket replacements may follow one
|
|
807
|
+
* another before the reconcile stops replacing the socket (it still adopts the
|
|
808
|
+
* token). A genuine missed change costs one; a second is a change published
|
|
809
|
+
* during that swap. More than a few in a row means a claim differs on every
|
|
810
|
+
* mint, and replacing the socket forever would be the TBP-644 loop again.
|
|
811
|
+
*/
|
|
812
|
+
const MAX_RECONCILE_SWAPS = 3;
|
|
813
|
+
/** Claims that differ on every mint without meaning anything changed. */
|
|
814
|
+
const PER_MINT_CLAIMS = new Set(['iat', 'exp', 'nbf', 'jti', 'auth_time']);
|
|
815
|
+
/** Same user, workspace and app — the scope a catch-up read belongs to. */
|
|
816
|
+
function sameSession(a, b) {
|
|
817
|
+
if (!a || !b)
|
|
818
|
+
return false;
|
|
819
|
+
if (a === b)
|
|
820
|
+
return true;
|
|
821
|
+
const ca = decodeJwtPayload(a);
|
|
822
|
+
const cb = decodeJwtPayload(b);
|
|
823
|
+
if (!ca || !cb)
|
|
824
|
+
return false;
|
|
825
|
+
return ca.sub === cb.sub && ca.tid === cb.tid && ca.aid === cb.aid;
|
|
826
|
+
}
|
|
827
|
+
/**
|
|
828
|
+
* TBP-700 — two tokens carry the same authority: every claim is equal except
|
|
829
|
+
* the per-mint ones. Unreadable tokens are never the same.
|
|
830
|
+
*/
|
|
831
|
+
function sameAuthorization(a, b) {
|
|
832
|
+
if (!a || !b)
|
|
833
|
+
return false;
|
|
834
|
+
const ca = decodeJwtPayload(a);
|
|
835
|
+
const cb = decodeJwtPayload(b);
|
|
836
|
+
if (!ca || !cb)
|
|
837
|
+
return false;
|
|
838
|
+
const keys = new Set([...Object.keys(ca), ...Object.keys(cb)]);
|
|
839
|
+
for (const k of keys) {
|
|
840
|
+
if (PER_MINT_CLAIMS.has(k))
|
|
841
|
+
continue;
|
|
842
|
+
if (JSON.stringify(ca[k]) !== JSON.stringify(cb[k]))
|
|
843
|
+
return false;
|
|
844
|
+
}
|
|
845
|
+
return true;
|
|
846
|
+
}
|
|
742
847
|
/** Decode a JWT payload without signature verification (client context only). */
|
|
743
848
|
function decodeJwtPayload(token) {
|
|
744
849
|
try {
|
|
@@ -104,6 +104,13 @@ export declare function applyEntitlementsChanged(msg: {
|
|
|
104
104
|
export declare function applyCatchUpSnapshot(data: SessionSnapshotData): {
|
|
105
105
|
planChanged: boolean;
|
|
106
106
|
entitlementsChanged: boolean;
|
|
107
|
+
/**
|
|
108
|
+
* TBP-700 — the tenant stores were empty: this filled them for the first
|
|
109
|
+
* time rather than changing anything the page had already seen. The
|
|
110
|
+
* `*Changed` flags still say whether the values moved, so a caller can seed
|
|
111
|
+
* other copies; a first fill is not a recovered change.
|
|
112
|
+
*/
|
|
113
|
+
initial: boolean;
|
|
107
114
|
};
|
|
108
115
|
/** Test-only: reset every snapshot store to `null`. Vitest hook. */
|
|
109
116
|
export declare function __resetSnapshotStores(): void;
|
|
@@ -104,6 +104,7 @@ export function applyCatchUpSnapshot(data) {
|
|
|
104
104
|
const subAfter = get(_tenantSubscription);
|
|
105
105
|
const entAfter = get(_tenantEntitlements);
|
|
106
106
|
return {
|
|
107
|
+
initial: subBefore === null && entBefore === null,
|
|
107
108
|
planChanged: (subBefore?.plan?.slug ?? null) !== (subAfter?.plan?.slug ?? null) ||
|
|
108
109
|
(subBefore?.status ?? null) !== (subAfter?.status ?? null),
|
|
109
110
|
entitlementsChanged: !sameFlags(entBefore, entAfter),
|
package/dist/styles.css
CHANGED
|
@@ -1,12 +1,54 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* bridge-svelte default styles
|
|
2
|
+
* bridge-svelte default styles
|
|
3
3
|
*
|
|
4
|
-
* Import this file to get the default appearance for all bridge-svelte components:
|
|
5
4
|
* import '@nebulr-group/bridge-svelte/styles';
|
|
6
5
|
*
|
|
7
|
-
* Without this file, components render as plain unstyled HTML — the headless
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* Without this file, components render as plain unstyled HTML — the headless
|
|
7
|
+
* contract. With it, they get their structure plus a small default look driven
|
|
8
|
+
* entirely by the `--bridge-*` tokens below.
|
|
9
|
+
*
|
|
10
|
+
* ── THE TOKEN CONTRACT ────────────────────────────────────────────────────
|
|
11
|
+
*
|
|
12
|
+
* The `--bridge-*` custom properties are the supported way to restyle every
|
|
13
|
+
* Bridge page and component. Set them in your own stylesheet:
|
|
14
|
+
*
|
|
15
|
+
* :root { --bridge-primary: #0f766e; --bridge-border-radius: 10px; }
|
|
16
|
+
*
|
|
17
|
+
* The defaults are declared on `:where(:root)`, which has zero specificity, so
|
|
18
|
+
* YOUR `:root` wins whatever order the two stylesheets load in. Set a token on
|
|
19
|
+
* a wrapper instead of `:root` to theme one area.
|
|
20
|
+
*
|
|
21
|
+
* Colour
|
|
22
|
+
* --bridge-primary primary buttons, selected tab, active workspace
|
|
23
|
+
* --bridge-primary-hover primary button hover (derived from primary)
|
|
24
|
+
* --bridge-primary-fg text on primary surfaces
|
|
25
|
+
* --bridge-primary-light tint behind the active workspace (derived)
|
|
26
|
+
* --bridge-input-focus input focus ring (defaults to primary)
|
|
27
|
+
* --bridge-bg dialogs, menus, panels
|
|
28
|
+
* --bridge-foreground text on those surfaces, emphasised names
|
|
29
|
+
* --bridge-muted secondary text: hints, table headings
|
|
30
|
+
* --bridge-muted-bg subtle fills: tab tracks, hovers, code boxes
|
|
31
|
+
* --bridge-border input, table, card and secondary button borders
|
|
32
|
+
* --bridge-border-radius corners of inputs, buttons, alerts, cards, dialogs
|
|
33
|
+
* --bridge-overlay backdrop behind dialogs
|
|
34
|
+
* --bridge-alert-error-{bg,fg,border} errors, critical billing notices
|
|
35
|
+
* --bridge-alert-success-{bg,fg,border} confirmations, active plan badge
|
|
36
|
+
* --bridge-alert-info-{bg,fg,border} info notices, trial badge
|
|
37
|
+
* --bridge-alert-warning-{bg,fg,border} warnings, quota nearing its limit
|
|
38
|
+
*
|
|
39
|
+
* Layout
|
|
40
|
+
* --bridge-auth-page-padding default container of the sign-in pages
|
|
41
|
+
* --bridge-billing-page-width subscription pages, <BridgePaywallPage>
|
|
42
|
+
* --bridge-billing-page-padding subscription pages, <BridgePaywallPage>
|
|
43
|
+
* --bridge-paywall-bg backdrop of <BridgePaywall> / the lockscreen
|
|
44
|
+
* --bridge-paywall-panel-bg panel of <BridgePaywall>
|
|
45
|
+
*
|
|
46
|
+
* Deprecated aliases — still honoured when set on :root, removed in a later
|
|
47
|
+
* major: --bridge-primary-foreground → --bridge-primary-fg
|
|
48
|
+
* --bridge-bg-muted → --bridge-muted-bg
|
|
49
|
+
*
|
|
50
|
+
* Fonts and body text colour are not tokens: Bridge pages render inside your
|
|
51
|
+
* layout and inherit them.
|
|
10
52
|
*
|
|
11
53
|
* State selectors:
|
|
12
54
|
* [data-active="true"] — active tab
|
|
@@ -19,21 +61,37 @@
|
|
|
19
61
|
* [data-variant="danger"] — danger variant
|
|
20
62
|
*/
|
|
21
63
|
|
|
22
|
-
/* ───
|
|
64
|
+
/* ─── Token defaults — zero specificity, so the app's :root always wins ──── */
|
|
23
65
|
|
|
24
|
-
:root {
|
|
66
|
+
:where(:root) {
|
|
67
|
+
/* Derived tokens (primary-hover, primary-fg, primary-light, input-focus,
|
|
68
|
+
muted-bg, paywall-panel-bg) have no value here on purpose: each falls back
|
|
69
|
+
to its source where it is used, so setting --bridge-primary on a wrapper
|
|
70
|
+
re-derives them for that wrapper too. */
|
|
25
71
|
--bridge-primary: #4f46e5;
|
|
26
|
-
--bridge-
|
|
27
|
-
--bridge-
|
|
72
|
+
--bridge-bg: #ffffff;
|
|
73
|
+
--bridge-foreground: #111827;
|
|
74
|
+
--bridge-muted: #6b7280;
|
|
28
75
|
--bridge-border: #d1d5db;
|
|
29
76
|
--bridge-border-radius: 6px;
|
|
30
|
-
--bridge-
|
|
77
|
+
--bridge-overlay: rgba(15, 23, 42, 0.45);
|
|
31
78
|
--bridge-alert-error-bg: #fef2f2;
|
|
32
79
|
--bridge-alert-error-fg: #991b1b;
|
|
33
80
|
--bridge-alert-error-border: #fca5a5;
|
|
34
81
|
--bridge-alert-success-bg: #f0fdf4;
|
|
35
82
|
--bridge-alert-success-fg: #166534;
|
|
36
83
|
--bridge-alert-success-border: #86efac;
|
|
84
|
+
--bridge-alert-info-bg: #eff6ff;
|
|
85
|
+
--bridge-alert-info-fg: #1e40af;
|
|
86
|
+
--bridge-alert-info-border: #bfdbfe;
|
|
87
|
+
--bridge-alert-warning-bg: #fffbeb;
|
|
88
|
+
--bridge-alert-warning-fg: #92400e;
|
|
89
|
+
--bridge-alert-warning-border: #fcd34d;
|
|
90
|
+
|
|
91
|
+
--bridge-auth-page-padding: 3rem 1rem;
|
|
92
|
+
--bridge-billing-page-width: 60rem;
|
|
93
|
+
--bridge-billing-page-padding: 3rem 1rem;
|
|
94
|
+
--bridge-paywall-bg: rgba(15, 23, 42, 0.72);
|
|
37
95
|
}
|
|
38
96
|
|
|
39
97
|
/* ─── Input visual defaults ──────────────────────────────────────────────── */
|
|
@@ -47,8 +105,8 @@
|
|
|
47
105
|
:where(input[type="text"]:focus, input[type="email"]:focus,
|
|
48
106
|
input[type="password"]:focus, input[type="tel"]:focus,
|
|
49
107
|
.bridge-input:focus) {
|
|
50
|
-
border-color: var(--bridge-input-focus);
|
|
51
|
-
box-shadow: 0 0 0 2px color-mix(in srgb, var(--bridge-input-focus) 20%, transparent);
|
|
108
|
+
border-color: var(--bridge-input-focus, var(--bridge-primary));
|
|
109
|
+
box-shadow: 0 0 0 2px color-mix(in srgb, var(--bridge-input-focus, var(--bridge-primary)) 20%, transparent);
|
|
52
110
|
}
|
|
53
111
|
|
|
54
112
|
/* ─── Buttons ────────────────────────────────────────────────────────────── */
|
|
@@ -92,13 +150,13 @@
|
|
|
92
150
|
|
|
93
151
|
:where(.bridge-btn-primary) {
|
|
94
152
|
background-color: var(--bridge-primary);
|
|
95
|
-
color: var(--bridge-primary-fg);
|
|
153
|
+
color: var(--bridge-primary-fg, var(--bridge-primary-foreground, #ffffff));
|
|
96
154
|
border: 1px solid var(--bridge-primary);
|
|
97
155
|
border-radius: var(--bridge-border-radius);
|
|
98
156
|
}
|
|
99
157
|
:where(.bridge-btn-primary:hover:not(:disabled)) {
|
|
100
|
-
background-color: var(--bridge-primary-hover);
|
|
101
|
-
border-color: var(--bridge-primary-hover);
|
|
158
|
+
background-color: var(--bridge-primary-hover, color-mix(in srgb, var(--bridge-primary) 85%, #000000));
|
|
159
|
+
border-color: var(--bridge-primary-hover, color-mix(in srgb, var(--bridge-primary) 85%, #000000));
|
|
102
160
|
}
|
|
103
161
|
:where(.bridge-btn-secondary, .bridge-btn-outline) {
|
|
104
162
|
background-color: transparent;
|
|
@@ -275,12 +333,12 @@
|
|
|
275
333
|
border: none;
|
|
276
334
|
cursor: pointer;
|
|
277
335
|
padding: 0.25rem;
|
|
278
|
-
color: var(--bridge-muted
|
|
336
|
+
color: var(--bridge-muted);
|
|
279
337
|
line-height: 1;
|
|
280
338
|
}
|
|
281
339
|
|
|
282
340
|
.bridge-password-toggle:hover {
|
|
283
|
-
color: var(--bridge-foreground
|
|
341
|
+
color: var(--bridge-foreground);
|
|
284
342
|
}
|
|
285
343
|
|
|
286
344
|
/* ─── Auth form helpers ───────────────────────────────────────────────────── */
|
|
@@ -318,13 +376,13 @@
|
|
|
318
376
|
/* Roughly the height of the credentials form it replaces, so the card does
|
|
319
377
|
not collapse and then re-expand under the cursor. */
|
|
320
378
|
min-height: 8rem;
|
|
321
|
-
color: var(--bridge-muted
|
|
379
|
+
color: var(--bridge-muted);
|
|
322
380
|
font-size: 0.875rem;
|
|
323
381
|
}
|
|
324
382
|
|
|
325
383
|
.bridge-mfa-help {
|
|
326
384
|
font-size: 0.8125rem;
|
|
327
|
-
color: var(--bridge-muted
|
|
385
|
+
color: var(--bridge-muted);
|
|
328
386
|
margin: 0.75rem 0 0.25rem;
|
|
329
387
|
text-align: center;
|
|
330
388
|
}
|
|
@@ -358,7 +416,7 @@
|
|
|
358
416
|
gap: 0.75rem;
|
|
359
417
|
margin-bottom: 1rem;
|
|
360
418
|
padding: 0.75rem;
|
|
361
|
-
background: var(--bridge-bg-muted, #
|
|
419
|
+
background: var(--bridge-muted-bg, var(--bridge-bg-muted, #f3f4f6));
|
|
362
420
|
border-radius: 0.5rem;
|
|
363
421
|
}
|
|
364
422
|
|
|
@@ -446,6 +504,19 @@
|
|
|
446
504
|
font-family: inherit;
|
|
447
505
|
}
|
|
448
506
|
|
|
507
|
+
:where(.bridge-team-tabs) {
|
|
508
|
+
border-bottom: 1px solid var(--bridge-border);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
:where(.bridge-team-tab) {
|
|
512
|
+
color: var(--bridge-muted);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
:where(.bridge-team-tab[data-active="true"]) {
|
|
516
|
+
color: var(--bridge-primary);
|
|
517
|
+
box-shadow: inset 0 -2px 0 var(--bridge-primary);
|
|
518
|
+
}
|
|
519
|
+
|
|
449
520
|
/* ─── TeamUserList ───────────────────────────────────────────────────────── */
|
|
450
521
|
|
|
451
522
|
.bridge-team-users-header {
|
|
@@ -633,6 +704,22 @@
|
|
|
633
704
|
min-width: 160px;
|
|
634
705
|
}
|
|
635
706
|
|
|
707
|
+
:where(.bridge-team-actions-menu) {
|
|
708
|
+
background: var(--bridge-bg);
|
|
709
|
+
color: var(--bridge-foreground);
|
|
710
|
+
border: 1px solid var(--bridge-border);
|
|
711
|
+
border-radius: var(--bridge-border-radius);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
:where(.bridge-team-table th) {
|
|
715
|
+
color: var(--bridge-muted);
|
|
716
|
+
border-bottom: 1px solid var(--bridge-border);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
:where(.bridge-team-table td) {
|
|
720
|
+
border-bottom: 1px solid var(--bridge-border);
|
|
721
|
+
}
|
|
722
|
+
|
|
636
723
|
.bridge-team-actions-item {
|
|
637
724
|
display: block;
|
|
638
725
|
width: 100%;
|
|
@@ -647,15 +734,24 @@
|
|
|
647
734
|
|
|
648
735
|
/* ─── Team Dialogs ───────────────────────────────────────────────────────── */
|
|
649
736
|
|
|
650
|
-
|
|
737
|
+
/* The team dialogs carry [data-bridge-team-dialog]; the upgrade dialog the class. */
|
|
738
|
+
.bridge-team-dialog,
|
|
739
|
+
[data-bridge-team-dialog] {
|
|
651
740
|
border: none;
|
|
652
741
|
padding: 0;
|
|
653
742
|
max-width: 400px;
|
|
654
743
|
width: 90%;
|
|
655
744
|
}
|
|
656
745
|
|
|
657
|
-
.bridge-team-dialog
|
|
658
|
-
background:
|
|
746
|
+
:where(.bridge-team-dialog, [data-bridge-team-dialog]) {
|
|
747
|
+
background: var(--bridge-bg);
|
|
748
|
+
color: var(--bridge-foreground);
|
|
749
|
+
border-radius: var(--bridge-border-radius);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
.bridge-team-dialog::backdrop,
|
|
753
|
+
[data-bridge-team-dialog]::backdrop {
|
|
754
|
+
background: var(--bridge-overlay);
|
|
659
755
|
}
|
|
660
756
|
|
|
661
757
|
.bridge-team-dialog-content {
|
|
@@ -764,25 +860,25 @@
|
|
|
764
860
|
letter-spacing: 0.05em;
|
|
765
861
|
font-weight: 500;
|
|
766
862
|
text-align: left;
|
|
767
|
-
border-bottom: 1px solid var(--bridge-border
|
|
768
|
-
color: var(--bridge-muted
|
|
863
|
+
border-bottom: 1px solid var(--bridge-border);
|
|
864
|
+
color: var(--bridge-muted);
|
|
769
865
|
}
|
|
770
866
|
|
|
771
867
|
[data-bridge-api-tokens] .bridge-api-td,
|
|
772
868
|
[data-bridge-api-tokens] .bridge-api-td-name,
|
|
773
869
|
[data-bridge-api-tokens] .bridge-api-td-muted {
|
|
774
870
|
padding: 0.75rem 1rem;
|
|
775
|
-
border-bottom: 1px solid var(--bridge-border
|
|
871
|
+
border-bottom: 1px solid var(--bridge-border);
|
|
776
872
|
}
|
|
777
873
|
|
|
778
874
|
[data-bridge-api-tokens] .bridge-api-td-muted {
|
|
779
|
-
color: var(--bridge-muted
|
|
875
|
+
color: var(--bridge-muted);
|
|
780
876
|
}
|
|
781
877
|
|
|
782
878
|
[data-bridge-api-tokens] .bridge-api-td-actions {
|
|
783
879
|
padding: 0.75rem 1rem;
|
|
784
880
|
text-align: right;
|
|
785
|
-
border-bottom: 1px solid var(--bridge-border
|
|
881
|
+
border-bottom: 1px solid var(--bridge-border);
|
|
786
882
|
}
|
|
787
883
|
|
|
788
884
|
[data-bridge-api-tokens] .bridge-api-privileges {
|
|
@@ -792,7 +888,7 @@
|
|
|
792
888
|
}
|
|
793
889
|
|
|
794
890
|
[data-bridge-api-tokens] .bridge-api-inline-form {
|
|
795
|
-
border: 1px solid var(--bridge-border
|
|
891
|
+
border: 1px solid var(--bridge-border);
|
|
796
892
|
border-radius: 0.5rem;
|
|
797
893
|
padding: 1rem 1.25rem;
|
|
798
894
|
}
|
|
@@ -957,9 +1053,9 @@
|
|
|
957
1053
|
gap: 0.25rem;
|
|
958
1054
|
margin-bottom: 1.25rem;
|
|
959
1055
|
padding: 0.25rem;
|
|
960
|
-
border: 1px solid var(--bridge-border
|
|
961
|
-
border-radius: var(--bridge-border-radius
|
|
962
|
-
background: var(--bridge-muted-bg, var(--bridge-bg-muted, #
|
|
1056
|
+
border: 1px solid var(--bridge-border);
|
|
1057
|
+
border-radius: var(--bridge-border-radius);
|
|
1058
|
+
background: var(--bridge-muted-bg, var(--bridge-bg-muted, #f3f4f6));
|
|
963
1059
|
}
|
|
964
1060
|
|
|
965
1061
|
.bridge-plan-interval-tab {
|
|
@@ -970,19 +1066,19 @@
|
|
|
970
1066
|
font: inherit;
|
|
971
1067
|
font-size: 0.8125rem;
|
|
972
1068
|
padding: 0.375rem 0.875rem;
|
|
973
|
-
border-radius: calc(var(--bridge-border-radius
|
|
974
|
-
color: var(--bridge-muted
|
|
1069
|
+
border-radius: calc(var(--bridge-border-radius) - 0.2rem);
|
|
1070
|
+
color: var(--bridge-muted);
|
|
975
1071
|
}
|
|
976
1072
|
|
|
977
1073
|
.bridge-plan-interval-tab[data-active="true"] {
|
|
978
|
-
background: var(--bridge-primary
|
|
979
|
-
color: var(--bridge-primary-fg, var(--bridge-primary-foreground, #
|
|
1074
|
+
background: var(--bridge-primary);
|
|
1075
|
+
color: var(--bridge-primary-fg, var(--bridge-primary-foreground, #ffffff));
|
|
980
1076
|
}
|
|
981
1077
|
|
|
982
1078
|
.bridge-plan-unavailable {
|
|
983
1079
|
margin: 0;
|
|
984
1080
|
font-size: 0.8125rem;
|
|
985
|
-
color: var(--bridge-muted
|
|
1081
|
+
color: var(--bridge-muted);
|
|
986
1082
|
}
|
|
987
1083
|
|
|
988
1084
|
/* Plan-change confirmation dialog + success notice (TBP-33) */
|
|
@@ -993,7 +1089,7 @@
|
|
|
993
1089
|
.bridge-plan-confirm-backdrop {
|
|
994
1090
|
position: fixed;
|
|
995
1091
|
inset: 0;
|
|
996
|
-
background:
|
|
1092
|
+
background: var(--bridge-overlay);
|
|
997
1093
|
display: grid;
|
|
998
1094
|
place-items: center;
|
|
999
1095
|
z-index: 1000;
|
|
@@ -1001,9 +1097,9 @@
|
|
|
1001
1097
|
}
|
|
1002
1098
|
|
|
1003
1099
|
.bridge-plan-confirm {
|
|
1004
|
-
background: var(--bridge-bg
|
|
1005
|
-
border: 1px solid var(--bridge-border
|
|
1006
|
-
border-radius: var(--bridge-border-radius
|
|
1100
|
+
background: var(--bridge-bg);
|
|
1101
|
+
border: 1px solid var(--bridge-border);
|
|
1102
|
+
border-radius: var(--bridge-border-radius);
|
|
1007
1103
|
box-shadow: 0 24px 60px -12px rgba(0, 0, 0, 0.35);
|
|
1008
1104
|
max-width: 26rem;
|
|
1009
1105
|
width: 100%;
|
|
@@ -1028,7 +1124,7 @@
|
|
|
1028
1124
|
.bridge-plan-confirm-note {
|
|
1029
1125
|
margin: 0;
|
|
1030
1126
|
font-size: 0.8125rem;
|
|
1031
|
-
color: var(--bridge-muted
|
|
1127
|
+
color: var(--bridge-muted);
|
|
1032
1128
|
line-height: 1.5;
|
|
1033
1129
|
}
|
|
1034
1130
|
|
|
@@ -1129,12 +1225,12 @@
|
|
|
1129
1225
|
}
|
|
1130
1226
|
|
|
1131
1227
|
[data-bridge-workspace-item]:hover:not(:disabled) {
|
|
1132
|
-
background: var(--bridge-muted-bg, #f3f4f6);
|
|
1228
|
+
background: var(--bridge-muted-bg, var(--bridge-bg-muted, #f3f4f6));
|
|
1133
1229
|
}
|
|
1134
1230
|
|
|
1135
1231
|
[data-bridge-workspace-item][data-active="true"] {
|
|
1136
|
-
background: var(--bridge-primary-light, #
|
|
1137
|
-
border-color: var(--bridge-primary
|
|
1232
|
+
background: var(--bridge-primary-light, color-mix(in srgb, var(--bridge-primary) 10%, #ffffff));
|
|
1233
|
+
border-color: var(--bridge-primary);
|
|
1138
1234
|
}
|
|
1139
1235
|
|
|
1140
1236
|
[data-bridge-workspace-item][data-loading="true"] {
|
|
@@ -1153,8 +1249,8 @@
|
|
|
1153
1249
|
width: 2rem;
|
|
1154
1250
|
height: 2rem;
|
|
1155
1251
|
border-radius: 0.375rem;
|
|
1156
|
-
background: var(--bridge-primary
|
|
1157
|
-
color: var(--bridge-primary-foreground, #ffffff);
|
|
1252
|
+
background: var(--bridge-primary);
|
|
1253
|
+
color: var(--bridge-primary-fg, var(--bridge-primary-foreground, #ffffff));
|
|
1158
1254
|
font-size: 0.875rem;
|
|
1159
1255
|
font-weight: 600;
|
|
1160
1256
|
flex-shrink: 0;
|
|
@@ -1178,7 +1274,7 @@
|
|
|
1178
1274
|
[data-bridge-workspace-name] {
|
|
1179
1275
|
font-size: 0.875rem;
|
|
1180
1276
|
font-weight: 500;
|
|
1181
|
-
color: var(--bridge-foreground
|
|
1277
|
+
color: var(--bridge-foreground);
|
|
1182
1278
|
white-space: nowrap;
|
|
1183
1279
|
overflow: hidden;
|
|
1184
1280
|
text-overflow: ellipsis;
|
|
@@ -1186,7 +1282,7 @@
|
|
|
1186
1282
|
|
|
1187
1283
|
[data-bridge-workspace-user] {
|
|
1188
1284
|
font-size: 0.75rem;
|
|
1189
|
-
color: var(--bridge-muted
|
|
1285
|
+
color: var(--bridge-muted);
|
|
1190
1286
|
white-space: nowrap;
|
|
1191
1287
|
overflow: hidden;
|
|
1192
1288
|
text-overflow: ellipsis;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nebulr-group/bridge-svelte",
|
|
3
|
-
"version": "0.9.0-beta.
|
|
3
|
+
"version": "0.9.0-beta.5",
|
|
4
4
|
"description": "Bridge Svelte library, This library helps you to add bridge authentication and feature flags, and payments to your svelte application.",
|
|
5
5
|
"author": "Iman Pouya",
|
|
6
6
|
"license": "MIT",
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
"typescript": "^6.0.0",
|
|
80
80
|
"vite": "^6.2.6",
|
|
81
81
|
"vitest": "^4.1.4",
|
|
82
|
-
"@nebulr-group/bridge-auth-core": "0.8.0-beta.
|
|
82
|
+
"@nebulr-group/bridge-auth-core": "0.8.0-beta.2"
|
|
83
83
|
},
|
|
84
84
|
"keywords": [
|
|
85
85
|
"svelte",
|