@timeax/digital-service-engine 0.2.1 → 0.2.2

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.
@@ -1909,6 +1909,9 @@ function toFiniteNumber(v) {
1909
1909
  const n = Number(v);
1910
1910
  return Number.isFinite(n) ? n : NaN;
1911
1911
  }
1912
+ function isValidServiceIdRef(value) {
1913
+ return typeof value === "number" && Number.isFinite(value) || typeof value === "string" && value.trim().length > 0;
1914
+ }
1912
1915
  function constraintFitOk(svcMap, candidate, constraints) {
1913
1916
  const cap = getServiceCapability(svcMap, candidate);
1914
1917
  if (!cap) return false;
@@ -1917,18 +1920,32 @@ function constraintFitOk(svcMap, candidate, constraints) {
1917
1920
  return !(constraints.cancel === true && !cap.cancel);
1918
1921
  }
1919
1922
  function getServiceCapability(svcMap, candidate) {
1920
- if (candidate === void 0 || candidate === null) return void 0;
1921
- const direct = svcMap[candidate];
1922
- if (direct) return direct;
1923
- const byString = svcMap[String(candidate)];
1924
- if (byString) return byString;
1925
- if (typeof candidate === "string") {
1926
- const maybeNumber = Number(candidate);
1927
- if (Number.isFinite(maybeNumber)) {
1928
- return svcMap[maybeNumber];
1929
- }
1923
+ var _a;
1924
+ return (_a = getServiceCapabilityEntry(svcMap, candidate)) == null ? void 0 : _a.capability;
1925
+ }
1926
+ function getServiceCapabilityCanonicalRef(svcMap, candidate) {
1927
+ const entry = getServiceCapabilityEntry(svcMap, candidate);
1928
+ if (!entry) return void 0;
1929
+ return getCanonicalServiceRef(entry.key, entry.capability);
1930
+ }
1931
+ function getServiceCapabilityAliases(svcMap, candidate) {
1932
+ const entry = getServiceCapabilityEntry(svcMap, candidate);
1933
+ if (!entry) return [];
1934
+ return collectServiceRefAliases(entry.key, entry.capability);
1935
+ }
1936
+ function isSameServiceCapabilityRef(svcMap, left, right) {
1937
+ if (!isValidServiceIdRef(left) || !isValidServiceIdRef(right)) return false;
1938
+ const leftAliases = new Set(
1939
+ getServiceCapabilityAliases(svcMap, left).map((value) => String(value))
1940
+ );
1941
+ if (!leftAliases.size) {
1942
+ leftAliases.add(String(left));
1930
1943
  }
1931
- return void 0;
1944
+ const rightAliases = getServiceCapabilityAliases(svcMap, right);
1945
+ if (!rightAliases.length) {
1946
+ return leftAliases.has(String(right));
1947
+ }
1948
+ return rightAliases.some((value) => leftAliases.has(String(value)));
1932
1949
  }
1933
1950
  function normalizeRatePolicy(policy) {
1934
1951
  var _a;
@@ -1964,6 +1981,74 @@ function rateOk(svcMap, candidate, primary, policy) {
1964
1981
  if (!Number.isFinite(cRate) || !Number.isFinite(pRate)) return false;
1965
1982
  return passesRatePolicy(policy.ratePolicy, pRate, cRate);
1966
1983
  }
1984
+ function getServiceCapabilityEntry(svcMap, candidate) {
1985
+ if (candidate === void 0 || candidate === null) return void 0;
1986
+ const direct = svcMap[candidate];
1987
+ if (direct) {
1988
+ return { key: String(candidate), capability: direct };
1989
+ }
1990
+ const byString = svcMap[String(candidate)];
1991
+ if (byString) {
1992
+ return { key: String(candidate), capability: byString };
1993
+ }
1994
+ if (typeof candidate === "string") {
1995
+ const maybeNumber = Number(candidate);
1996
+ if (Number.isFinite(maybeNumber)) {
1997
+ const byNumber = svcMap[maybeNumber];
1998
+ if (byNumber) {
1999
+ return { key: String(maybeNumber), capability: byNumber };
2000
+ }
2001
+ }
2002
+ }
2003
+ const target = String(candidate);
2004
+ for (const [key, capability] of Object.entries(svcMap != null ? svcMap : {})) {
2005
+ if (collectServiceRefAliases(key, capability).some(
2006
+ (alias) => String(alias) === target
2007
+ )) {
2008
+ return { key, capability };
2009
+ }
2010
+ }
2011
+ return void 0;
2012
+ }
2013
+ function collectServiceRefAliases(key, capability) {
2014
+ const out = [];
2015
+ const seen = /* @__PURE__ */ new Set();
2016
+ const push = (value) => {
2017
+ if (!isValidServiceIdRef(value)) return;
2018
+ const normalized = normalizeServiceRef(value);
2019
+ if (!normalized) return;
2020
+ const aliasKey = String(normalized);
2021
+ if (seen.has(aliasKey)) return;
2022
+ seen.add(aliasKey);
2023
+ out.push(normalized);
2024
+ };
2025
+ push(getCanonicalServiceRef(key, capability));
2026
+ push(capability.service);
2027
+ push(capability.key);
2028
+ push(capability.id);
2029
+ return out;
2030
+ }
2031
+ function getCanonicalServiceRef(key, capability) {
2032
+ const explicitRefs = [capability.service, capability.key, capability.id];
2033
+ for (const ref of explicitRefs) {
2034
+ if (!isValidServiceIdRef(ref)) continue;
2035
+ if (String(ref) === key) {
2036
+ return ref;
2037
+ }
2038
+ }
2039
+ return normalizeServiceRef(key);
2040
+ }
2041
+ function normalizeServiceRef(value) {
2042
+ if (!isValidServiceIdRef(value)) return void 0;
2043
+ if (typeof value === "number") return value;
2044
+ const trimmed = value.trim();
2045
+ if (!trimmed) return void 0;
2046
+ const asNumber = Number(trimmed);
2047
+ if (Number.isFinite(asNumber) && String(asNumber) === trimmed) {
2048
+ return asNumber;
2049
+ }
2050
+ return trimmed;
2051
+ }
1967
2052
 
1968
2053
  // src/core/validate/steps/rates.ts
1969
2054
  function validateRates(v) {
@@ -3409,7 +3494,7 @@ function collectFailedFallbacks(props, services, settings) {
3409
3494
  const s = { ...DEFAULT_SETTINGS, ...settings != null ? settings : {} };
3410
3495
  const out = [];
3411
3496
  const fb = (_a = props.fallbacks) != null ? _a : {};
3412
- const primaryRate = (p) => rateOf(services, p);
3497
+ const primaryRate = (primary) => rateOf(services, primary);
3413
3498
  for (const [nodeId, list] of Object.entries((_b = fb.nodes) != null ? _b : {})) {
3414
3499
  const { primary, tagContexts } = primaryForNode(props, nodeId);
3415
3500
  if (!primary) {
@@ -3422,34 +3507,34 @@ function collectFailedFallbacks(props, services, settings) {
3422
3507
  });
3423
3508
  continue;
3424
3509
  }
3425
- for (const cand of list) {
3426
- const cap = getCap(services, cand);
3427
- if (!cap) {
3510
+ for (const candidate of list) {
3511
+ const capability = getCap(services, candidate);
3512
+ if (!capability) {
3428
3513
  out.push({
3429
3514
  scope: "node",
3430
3515
  nodeId,
3431
3516
  primary,
3432
- candidate: cand,
3517
+ candidate,
3433
3518
  reason: "unknown_service"
3434
3519
  });
3435
3520
  continue;
3436
3521
  }
3437
- if (String(cand) === String(primary)) {
3522
+ if (isSameServiceCapabilityRef(services, candidate, primary)) {
3438
3523
  out.push({
3439
3524
  scope: "node",
3440
3525
  nodeId,
3441
3526
  primary,
3442
- candidate: cand,
3527
+ candidate,
3443
3528
  reason: "cycle"
3444
3529
  });
3445
3530
  continue;
3446
3531
  }
3447
- if (!passesRate(s.ratePolicy, primaryRate(primary), cap.rate)) {
3532
+ if (!passesRate(s.ratePolicy, primaryRate(primary), capability.rate)) {
3448
3533
  out.push({
3449
3534
  scope: "node",
3450
3535
  nodeId,
3451
3536
  primary,
3452
- candidate: cand,
3537
+ candidate,
3453
3538
  reason: "rate_violation"
3454
3539
  });
3455
3540
  continue;
@@ -3459,58 +3544,55 @@ function collectFailedFallbacks(props, services, settings) {
3459
3544
  scope: "node",
3460
3545
  nodeId,
3461
3546
  primary,
3462
- candidate: cand,
3547
+ candidate,
3463
3548
  reason: "no_tag_context"
3464
3549
  });
3465
3550
  continue;
3466
3551
  }
3467
- let anyPass = false;
3468
- let anyFail = false;
3469
3552
  for (const tagId of tagContexts) {
3470
- const ok = s.requireConstraintFit ? satisfiesTagConstraints(tagId, { services, props }, cap) : true;
3471
- if (ok) anyPass = true;
3472
- else {
3473
- anyFail = true;
3474
- out.push({
3475
- scope: "node",
3476
- nodeId,
3477
- primary,
3478
- candidate: cand,
3479
- tagContext: tagId,
3480
- reason: "constraint_mismatch"
3481
- });
3482
- }
3553
+ const fitsConstraints = s.requireConstraintFit ? satisfiesTagConstraints(
3554
+ tagId,
3555
+ { services, props },
3556
+ capability
3557
+ ) : true;
3558
+ if (fitsConstraints) continue;
3559
+ out.push({
3560
+ scope: "node",
3561
+ nodeId,
3562
+ primary,
3563
+ candidate,
3564
+ tagContext: tagId,
3565
+ reason: "constraint_mismatch"
3566
+ });
3483
3567
  }
3484
- void anyPass;
3485
- void anyFail;
3486
3568
  }
3487
3569
  }
3488
3570
  for (const [primary, list] of Object.entries((_c = fb.global) != null ? _c : {})) {
3489
- for (const cand of list) {
3490
- const cap = getCap(services, cand);
3491
- if (!cap) {
3571
+ for (const candidate of list) {
3572
+ const capability = getCap(services, candidate);
3573
+ if (!capability) {
3492
3574
  out.push({
3493
3575
  scope: "global",
3494
3576
  primary,
3495
- candidate: cand,
3577
+ candidate,
3496
3578
  reason: "unknown_service"
3497
3579
  });
3498
3580
  continue;
3499
3581
  }
3500
- if (String(cand) === String(primary)) {
3582
+ if (isSameServiceCapabilityRef(services, candidate, primary)) {
3501
3583
  out.push({
3502
3584
  scope: "global",
3503
3585
  primary,
3504
- candidate: cand,
3586
+ candidate,
3505
3587
  reason: "cycle"
3506
3588
  });
3507
3589
  continue;
3508
3590
  }
3509
- if (!passesRate(s.ratePolicy, primaryRate(primary), cap.rate)) {
3591
+ if (!passesRate(s.ratePolicy, primaryRate(primary), capability.rate)) {
3510
3592
  out.push({
3511
3593
  scope: "global",
3512
3594
  primary,
3513
- candidate: cand,
3595
+ candidate,
3514
3596
  reason: "rate_violation"
3515
3597
  });
3516
3598
  }
@@ -3519,52 +3601,61 @@ function collectFailedFallbacks(props, services, settings) {
3519
3601
  return out;
3520
3602
  }
3521
3603
  function rateOf(map, id) {
3522
- var _a;
3604
+ var _a, _b;
3523
3605
  if (id === void 0 || id === null) return void 0;
3524
- const c = getCap(map, id);
3525
- return (_a = c == null ? void 0 : c.rate) != null ? _a : void 0;
3606
+ return (_b = (_a = getCap(map, id)) == null ? void 0 : _a.rate) != null ? _b : void 0;
3526
3607
  }
3527
- function passesRate(policy, primaryRate, candRate) {
3528
- if (typeof candRate !== "number" || !Number.isFinite(candRate))
3608
+ function passesRate(policy, primaryRate, candidateRate) {
3609
+ if (typeof candidateRate !== "number" || !Number.isFinite(candidateRate)) {
3529
3610
  return false;
3530
- if (typeof primaryRate !== "number" || !Number.isFinite(primaryRate))
3611
+ }
3612
+ if (typeof primaryRate !== "number" || !Number.isFinite(primaryRate)) {
3531
3613
  return false;
3532
- return passesRatePolicy(normalizeRatePolicy(policy), primaryRate, candRate);
3614
+ }
3615
+ return passesRatePolicy(
3616
+ normalizeRatePolicy(policy),
3617
+ primaryRate,
3618
+ candidateRate
3619
+ );
3533
3620
  }
3534
3621
  function getCap(map, id) {
3535
3622
  return getServiceCapability(map, id);
3536
3623
  }
3537
- function isCapFlagEnabled(cap, flagId) {
3624
+ function isCapFlagEnabled(capability, flagId) {
3538
3625
  var _a, _b;
3539
- const fromFlags = (_b = (_a = cap.flags) == null ? void 0 : _a[flagId]) == null ? void 0 : _b.enabled;
3626
+ const fromFlags = (_b = (_a = capability.flags) == null ? void 0 : _a[flagId]) == null ? void 0 : _b.enabled;
3540
3627
  if (fromFlags === true) return true;
3541
3628
  if (fromFlags === false) return false;
3542
- const legacy = cap[flagId];
3629
+ const legacy = capability[flagId];
3543
3630
  return legacy === true;
3544
3631
  }
3545
- function satisfiesTagConstraints(tagId, ctx, cap) {
3546
- const tag = ctx.props.filters.find((t) => t.id === tagId);
3547
- const eff = tag == null ? void 0 : tag.constraints;
3548
- if (!eff) return true;
3549
- for (const [key, value] of Object.entries(eff)) {
3550
- if (value === true && !isCapFlagEnabled(cap, key)) {
3632
+ function satisfiesTagConstraints(tagId, ctx, capability) {
3633
+ const tag = ctx.props.filters.find((item) => item.id === tagId);
3634
+ const effectiveConstraints2 = tag == null ? void 0 : tag.constraints;
3635
+ if (!effectiveConstraints2) return true;
3636
+ for (const [key, value] of Object.entries(effectiveConstraints2)) {
3637
+ if (value === true && !isCapFlagEnabled(capability, key)) {
3551
3638
  return false;
3552
3639
  }
3553
3640
  }
3554
3641
  return true;
3555
3642
  }
3556
3643
  function primaryForNode(props, nodeId) {
3557
- const tag = props.filters.find((t) => t.id === nodeId);
3644
+ const tag = props.filters.find((item) => item.id === nodeId);
3558
3645
  if (tag) {
3559
3646
  return { primary: tag.service_id, tagContexts: [tag.id] };
3560
3647
  }
3561
3648
  const field = props.fields.find(
3562
- (f) => Array.isArray(f.options) && f.options.some((o) => o.id === nodeId)
3649
+ (item) => Array.isArray(item.options) && item.options.some((option2) => option2.id === nodeId)
3563
3650
  );
3564
- if (!field) return { tagContexts: [], reasonNoPrimary: "no_parent_field" };
3565
- const opt = field.options.find((o) => o.id === nodeId);
3566
- const contexts = bindIdsToArray(field.bind_id);
3567
- return { primary: opt.service_id, tagContexts: contexts };
3651
+ if (!field) {
3652
+ return { tagContexts: [], reasonNoPrimary: "no_parent_field" };
3653
+ }
3654
+ const option = field.options.find((item) => item.id === nodeId);
3655
+ return {
3656
+ primary: option.service_id,
3657
+ tagContexts: bindIdsToArray(field.bind_id)
3658
+ };
3568
3659
  }
3569
3660
  function bindIdsToArray(bind) {
3570
3661
  if (!bind) return [];
@@ -3574,43 +3665,54 @@ function getEligibleFallbacks(params) {
3574
3665
  var _a, _b, _c, _d, _e, _f;
3575
3666
  const s = { ...DEFAULT_SETTINGS, ...(_a = params.settings) != null ? _a : {} };
3576
3667
  const { primary, nodeId, tagId, services } = params;
3577
- const fb = (_b = params.fallbacks) != null ? _b : {};
3578
- const excludes = new Set(((_c = params.exclude) != null ? _c : []).map(String));
3579
- excludes.add(String(primary));
3580
- const unique = (_d = params.unique) != null ? _d : true;
3581
- const lists = [];
3582
- if (nodeId && ((_e = fb.nodes) == null ? void 0 : _e[nodeId])) lists.push(fb.nodes[nodeId]);
3583
- if ((_f = fb.global) == null ? void 0 : _f[primary]) lists.push(fb.global[primary]);
3584
- if (!lists.length) return [];
3668
+ const excludes = /* @__PURE__ */ new Set();
3669
+ for (const ref of (_b = params.exclude) != null ? _b : []) {
3670
+ addComparableServiceRef(excludes, services, ref);
3671
+ }
3672
+ addComparableServiceRef(excludes, services, primary);
3673
+ const source = (_c = params.source) != null ? _c : "registered";
3674
+ const candidateLists = source === "all_services" ? [listServicePoolCandidates(services)] : listRegisteredFallbackCandidates(
3675
+ (_d = params.fallbacks) != null ? _d : {},
3676
+ primary,
3677
+ nodeId,
3678
+ services
3679
+ );
3680
+ if (!candidateLists.length) return [];
3585
3681
  const primaryRate = rateOf(services, primary);
3586
3682
  const seen = /* @__PURE__ */ new Set();
3587
3683
  const eligible = [];
3588
- for (const list of lists) {
3589
- for (const cand of list) {
3590
- const key = String(cand);
3591
- if (excludes.has(key)) continue;
3592
- if (unique && seen.has(key)) continue;
3593
- seen.add(key);
3594
- const cap = getCap(services, cand);
3595
- if (!cap) continue;
3596
- if (!passesRate(s.ratePolicy, primaryRate, cap.rate)) continue;
3684
+ for (const list of candidateLists) {
3685
+ for (const candidate of list) {
3686
+ if (hasComparableServiceRef(excludes, services, candidate)) continue;
3687
+ const capability = getCap(services, candidate);
3688
+ if (!capability) continue;
3689
+ const candidateId = (_e = getServiceCapabilityCanonicalRef(services, candidate)) != null ? _e : candidate;
3690
+ const candidateIdentity = getComparableServiceRefKey(
3691
+ services,
3692
+ candidateId
3693
+ );
3694
+ if (((_f = params.unique) != null ? _f : true) && seen.has(candidateIdentity)) continue;
3695
+ seen.add(candidateIdentity);
3696
+ if (!passesRate(s.ratePolicy, primaryRate, capability.rate)) {
3697
+ continue;
3698
+ }
3597
3699
  if (s.requireConstraintFit && tagId) {
3598
- const ok = satisfiesTagConstraints(
3700
+ const fitsConstraints = satisfiesTagConstraints(
3599
3701
  tagId,
3600
3702
  { props: params.props, services },
3601
- cap
3703
+ capability
3602
3704
  );
3603
- if (!ok) continue;
3705
+ if (!fitsConstraints) continue;
3604
3706
  }
3605
- eligible.push(cand);
3707
+ eligible.push(candidateId);
3606
3708
  }
3607
3709
  }
3608
3710
  if (s.selectionStrategy === "cheapest") {
3609
- eligible.sort((a, b) => {
3711
+ eligible.sort((left, right) => {
3610
3712
  var _a2, _b2;
3611
- const ra = (_a2 = rateOf(services, a)) != null ? _a2 : Infinity;
3612
- const rb = (_b2 = rateOf(services, b)) != null ? _b2 : Infinity;
3613
- return ra - rb;
3713
+ const leftRate = (_a2 = rateOf(services, left)) != null ? _a2 : Infinity;
3714
+ const rightRate = (_b2 = rateOf(services, right)) != null ? _b2 : Infinity;
3715
+ return leftRate - rightRate;
3614
3716
  });
3615
3717
  }
3616
3718
  if (typeof params.limit === "number" && params.limit >= 0) {
@@ -3618,10 +3720,104 @@ function getEligibleFallbacks(params) {
3618
3720
  }
3619
3721
  return eligible;
3620
3722
  }
3723
+ function getAssignedServiceIds(params) {
3724
+ var _a, _b, _c, _d, _e;
3725
+ const seen = /* @__PURE__ */ new Set();
3726
+ const out = [];
3727
+ const push = (value) => {
3728
+ if (!isValidServiceIdRef(value)) return;
3729
+ const key = String(value);
3730
+ if (seen.has(key)) return;
3731
+ seen.add(key);
3732
+ out.push(value);
3733
+ };
3734
+ const props = params.props;
3735
+ if (props) {
3736
+ for (const tag of (_a = props.filters) != null ? _a : []) {
3737
+ push(tag.service_id);
3738
+ }
3739
+ for (const field of (_b = props.fields) != null ? _b : []) {
3740
+ const fieldService = field.service_id;
3741
+ if (field.button === true) {
3742
+ push(fieldService);
3743
+ }
3744
+ for (const option of (_c = field.options) != null ? _c : []) {
3745
+ if (option.pricing_role === "utility") continue;
3746
+ push(option.service_id);
3747
+ }
3748
+ }
3749
+ }
3750
+ const snapshot = params.snapshot;
3751
+ if (snapshot) {
3752
+ for (const serviceId of (_d = snapshot.services) != null ? _d : []) {
3753
+ push(serviceId);
3754
+ }
3755
+ for (const list of Object.values((_e = snapshot.serviceMap) != null ? _e : {})) {
3756
+ for (const serviceId of list != null ? list : []) {
3757
+ push(serviceId);
3758
+ }
3759
+ }
3760
+ }
3761
+ return out;
3762
+ }
3621
3763
  function getFallbackRegistrationInfo(props, nodeId) {
3622
3764
  const { primary, tagContexts } = primaryForNode(props, nodeId);
3623
3765
  return { primary, tagContexts };
3624
3766
  }
3767
+ function listRegisteredFallbackCandidates(fallbacks, primary, nodeId, services) {
3768
+ var _a, _b;
3769
+ const lists = [];
3770
+ if (nodeId && ((_a = fallbacks.nodes) == null ? void 0 : _a[nodeId])) {
3771
+ lists.push(fallbacks.nodes[nodeId]);
3772
+ }
3773
+ for (const [registeredPrimary, list] of Object.entries((_b = fallbacks.global) != null ? _b : {})) {
3774
+ if (!isMatchingServiceRef(services, registeredPrimary, primary)) continue;
3775
+ lists.push(list);
3776
+ }
3777
+ return lists;
3778
+ }
3779
+ function listServicePoolCandidates(services) {
3780
+ const seen = /* @__PURE__ */ new Set();
3781
+ const out = [];
3782
+ for (const [key, capability] of Object.entries(services != null ? services : {})) {
3783
+ const candidate = getServicePoolCandidateId(key, capability);
3784
+ if (!isValidServiceIdRef(candidate)) continue;
3785
+ const identity = getComparableServiceRefKey(services, candidate);
3786
+ if (seen.has(identity)) continue;
3787
+ seen.add(identity);
3788
+ out.push(candidate);
3789
+ }
3790
+ return out;
3791
+ }
3792
+ function getServicePoolCandidateId(key, capability) {
3793
+ var _a;
3794
+ return (_a = getServiceCapabilityCanonicalRef({ [key]: capability }, key)) != null ? _a : key;
3795
+ }
3796
+ function addComparableServiceRef(target, services, value) {
3797
+ for (const ref of getComparableServiceRefs(services, value)) {
3798
+ target.add(ref);
3799
+ }
3800
+ }
3801
+ function hasComparableServiceRef(target, services, value) {
3802
+ return getComparableServiceRefs(services, value).some((ref) => target.has(ref));
3803
+ }
3804
+ function getComparableServiceRefKey(services, value) {
3805
+ if (!isValidServiceIdRef(value)) return "";
3806
+ const canonical = getServiceCapabilityCanonicalRef(services, value);
3807
+ return String(canonical != null ? canonical : value);
3808
+ }
3809
+ function getComparableServiceRefs(services, value) {
3810
+ if (!isValidServiceIdRef(value)) return [];
3811
+ const aliases = getServiceCapabilityAliases(services, value);
3812
+ if (!aliases.length) {
3813
+ return [String(value)];
3814
+ }
3815
+ return aliases.map((ref) => String(ref));
3816
+ }
3817
+ function isMatchingServiceRef(services, left, right) {
3818
+ if (!services) return String(left) === String(right);
3819
+ return isSameServiceCapabilityRef(services, left, right);
3820
+ }
3625
3821
 
3626
3822
  // src/core/tag-relations.ts
3627
3823
  var toId = (x) => typeof x === "string" ? x : x.id;
@@ -5160,6 +5356,7 @@ function createFallbackEditor(options = {}) {
5160
5356
  const original = cloneFallbacks(options.fallbacks);
5161
5357
  let current = cloneFallbacks(options.fallbacks);
5162
5358
  const props = options.props;
5359
+ const snapshot = options.snapshot;
5163
5360
  const services = (_a = options.services) != null ? _a : {};
5164
5361
  const settings = (_b = options.settings) != null ? _b : {};
5165
5362
  function state() {
@@ -5254,7 +5451,11 @@ function createFallbackEditor(options = {}) {
5254
5451
  const allowed2 = [];
5255
5452
  for (const candidate of normalized) {
5256
5453
  const reasons = [];
5257
- if (String(candidate) === String(context.primary)) {
5454
+ if (isSameServiceCapabilityRef(
5455
+ services,
5456
+ candidate,
5457
+ context.primary
5458
+ )) {
5258
5459
  reasons.push("self_reference");
5259
5460
  }
5260
5461
  if (reasons.length) {
@@ -5342,7 +5543,19 @@ function createFallbackEditor(options = {}) {
5342
5543
  return writeScope(context, []);
5343
5544
  }
5344
5545
  function eligible(context, opt) {
5546
+ var _a2, _b2;
5345
5547
  if (!props) return [];
5548
+ const source = (_a2 = opt == null ? void 0 : opt.source) != null ? _a2 : "all_services";
5549
+ const exclude2 = normalizeCandidateList(
5550
+ [
5551
+ ...(_b2 = opt == null ? void 0 : opt.exclude) != null ? _b2 : [],
5552
+ ...source === "all_services" ? [
5553
+ ...getAssignedServiceIds({ props, snapshot }),
5554
+ ...getScope(context)
5555
+ ] : []
5556
+ ],
5557
+ true
5558
+ );
5346
5559
  if (context.scope === "global") {
5347
5560
  return getEligibleFallbacks({
5348
5561
  primary: context.primary,
@@ -5350,9 +5563,10 @@ function createFallbackEditor(options = {}) {
5350
5563
  fallbacks: current,
5351
5564
  settings,
5352
5565
  props,
5353
- exclude: opt == null ? void 0 : opt.exclude,
5566
+ exclude: exclude2,
5354
5567
  unique: opt == null ? void 0 : opt.unique,
5355
- limit: opt == null ? void 0 : opt.limit
5568
+ limit: opt == null ? void 0 : opt.limit,
5569
+ source
5356
5570
  });
5357
5571
  }
5358
5572
  const info = getFallbackRegistrationInfo(props, context.nodeId);
@@ -5360,14 +5574,19 @@ function createFallbackEditor(options = {}) {
5360
5574
  return getEligibleFallbacks({
5361
5575
  primary: info.primary,
5362
5576
  nodeId: context.nodeId,
5363
- tagId: info.tagContexts[0],
5577
+ tagId: resolveNodeTagContext({
5578
+ nodeId: context.nodeId,
5579
+ snapshot,
5580
+ fallbackTagContexts: info.tagContexts
5581
+ }),
5364
5582
  services,
5365
5583
  fallbacks: current,
5366
5584
  settings,
5367
5585
  props,
5368
- exclude: opt == null ? void 0 : opt.exclude,
5586
+ exclude: exclude2,
5369
5587
  unique: opt == null ? void 0 : opt.unique,
5370
- limit: opt == null ? void 0 : opt.limit
5588
+ limit: opt == null ? void 0 : opt.limit,
5589
+ source
5371
5590
  });
5372
5591
  }
5373
5592
  function writeScope(context, nextList) {
@@ -5425,14 +5644,14 @@ function sameFallbacks(a, b) {
5425
5644
  function normalizeCandidateList(input, preserveOrder) {
5426
5645
  const out = [];
5427
5646
  for (const item of input != null ? input : []) {
5428
- if (!isValidServiceIdRef(item)) continue;
5647
+ if (!isValidServiceIdRef2(item)) continue;
5429
5648
  const exists = out.some((x) => String(x) === String(item));
5430
5649
  if (exists) continue;
5431
5650
  out.push(item);
5432
5651
  }
5433
5652
  return preserveOrder ? out : out;
5434
5653
  }
5435
- function isValidServiceIdRef(value) {
5654
+ function isValidServiceIdRef2(value) {
5436
5655
  return typeof value === "number" && Number.isFinite(value) || typeof value === "string" && value.trim().length > 0;
5437
5656
  }
5438
5657
  function clamp(n, min, max) {
@@ -5441,7 +5660,7 @@ function clamp(n, min, max) {
5441
5660
  function getNodeRegistrationInfo(props, nodeId) {
5442
5661
  const tag = props.filters.find((t) => t.id === nodeId);
5443
5662
  if (tag) {
5444
- if (!isValidServiceIdRef(tag.service_id)) {
5663
+ if (!isValidServiceIdRef2(tag.service_id)) {
5445
5664
  return { ok: false, reasons: ["no_primary"] };
5446
5665
  }
5447
5666
  return {
@@ -5454,7 +5673,7 @@ function getNodeRegistrationInfo(props, nodeId) {
5454
5673
  if (!hit) {
5455
5674
  return { ok: false, reasons: ["node_not_found"] };
5456
5675
  }
5457
- if (!isValidServiceIdRef(hit.option.service_id)) {
5676
+ if (!isValidServiceIdRef2(hit.option.service_id)) {
5458
5677
  return { ok: false, reasons: ["no_primary"] };
5459
5678
  }
5460
5679
  return {
@@ -5476,6 +5695,15 @@ function bindIdsToArray2(v) {
5476
5695
  if (Array.isArray(v)) return v.filter(Boolean);
5477
5696
  return v ? [v] : [];
5478
5697
  }
5698
+ function resolveNodeTagContext(params) {
5699
+ var _a, _b, _c;
5700
+ const nodeContexts = (_c = (_b = (_a = params.snapshot) == null ? void 0 : _a.meta) == null ? void 0 : _b.context) == null ? void 0 : _c.nodeContexts;
5701
+ if (nodeContexts && Object.prototype.hasOwnProperty.call(nodeContexts, params.nodeId)) {
5702
+ const tagId = nodeContexts[params.nodeId];
5703
+ return typeof tagId === "string" && tagId.trim().length > 0 ? tagId : void 0;
5704
+ }
5705
+ return params.fallbackTagContexts[0];
5706
+ }
5479
5707
  function mapDiagReason(reason) {
5480
5708
  switch (String(reason)) {
5481
5709
  case "unknown_service":
@@ -12167,10 +12395,10 @@ function FallbackEditorInner({ className }) {
12167
12395
  "div",
12168
12396
  {
12169
12397
  className: [
12170
- "min-h-screen bg-zinc-100 p-4 text-zinc-900 dark:bg-zinc-950 dark:text-zinc-100",
12398
+ "h-full min-h-0 overflow-hidden bg-zinc-100 p-4 text-zinc-900 dark:bg-zinc-950 dark:text-zinc-100",
12171
12399
  className
12172
12400
  ].filter(Boolean).join(" "),
12173
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-auto flex max-w-7xl flex-col gap-4", children: [
12401
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex h-full min-h-0 flex-col gap-4", children: [
12174
12402
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
12175
12403
  FallbackEditorHeader,
12176
12404
  {
@@ -12182,9 +12410,9 @@ function FallbackEditorInner({ className }) {
12182
12410
  saving: headerSaving
12183
12411
  }
12184
12412
  ),
12185
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "grid gap-4 xl:grid-cols-[300px_minmax(0,1fr)_360px]", children: [
12413
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "grid min-h-0 flex-1 gap-4 overflow-hidden xl:grid-cols-[300px_minmax(0,1fr)_360px]", children: [
12186
12414
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(FallbackServiceSidebar, {}),
12187
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex min-h-0 flex-col gap-4", children: [
12415
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex min-h-0 flex-col gap-4 overflow-y-auto", children: [
12188
12416
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("section", { className: "rounded-2xl border border-zinc-200 bg-white p-4 shadow-sm dark:border-zinc-800 dark:bg-zinc-900", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex flex-wrap items-start justify-between gap-4", children: [
12189
12417
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { children: [
12190
12418
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h2", { className: "text-lg font-semibold text-zinc-900 dark:text-zinc-100", children: activeServiceId !== void 0 ? `Service #${String(activeServiceId)}` : "No service selected" }),
@@ -12328,7 +12556,7 @@ function FallbackDetailsPanel() {
12328
12556
  () => services.find((s) => String(s.id) === String(activeServiceId)),
12329
12557
  [services, activeServiceId]
12330
12558
  );
12331
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("aside", { className: "flex min-h-0 flex-col gap-4", children: [
12559
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("aside", { className: "flex min-h-0 flex-col gap-4 overflow-y-auto", children: [
12332
12560
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("section", { className: "rounded-2xl border border-zinc-200 bg-white p-4 shadow-sm dark:border-zinc-800 dark:bg-zinc-900", children: [
12333
12561
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h3", { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: "Primary service info" }),
12334
12562
  !service ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "mt-3 text-sm text-zinc-500 dark:text-zinc-400", children: "No service selected." }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "mt-3 space-y-2 text-sm", children: [
@@ -12490,7 +12718,7 @@ function FallbackSettingsPanel() {
12490
12718
  kind: "lte_primary",
12491
12719
  pct: 5
12492
12720
  };
12493
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("section", { className: "rounded-2xl border border-zinc-200 bg-white p-4 shadow-sm dark:border-zinc-800 dark:bg-zinc-900", children: [
12721
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("section", { className: "rounded-2xl border border-zinc-200 bg-white p-4 shadow-sm dark:border-zinc-800 dark:bg-zinc-900 overflow-y-auto", children: [
12494
12722
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "mb-4 flex items-start justify-between gap-3", children: [
12495
12723
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
12496
12724
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h3", { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: "Fallback settings" }),
@@ -12689,7 +12917,7 @@ function FallbackServiceSidebar() {
12689
12917
  }
12690
12918
  );
12691
12919
  }, [query, services]);
12692
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("aside", { className: "flex min-h-0 flex-col rounded-2xl border border-zinc-200 bg-white shadow-sm dark:border-zinc-800 dark:bg-zinc-900", children: [
12920
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("aside", { className: "flex h-full min-h-0 flex-col overflow-hidden rounded-2xl border border-zinc-200 bg-white shadow-sm dark:border-zinc-800 dark:bg-zinc-900", children: [
12693
12921
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "border-b border-zinc-200 p-4 dark:border-zinc-800", children: [
12694
12922
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h2", { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: "Primary services" }),
12695
12923
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "mt-1 text-xs text-zinc-500 dark:text-zinc-400", children: "Services currently active in the builder/runtime context." })
@@ -12704,7 +12932,7 @@ function FallbackServiceSidebar() {
12704
12932
  className: "rounded-xl border border-zinc-300 bg-white px-3 py-2 text-sm outline-none focus:border-blue-500 dark:border-zinc-700 dark:bg-zinc-950 dark:text-zinc-100"
12705
12933
  }
12706
12934
  ),
12707
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "mt-3 flex-1 space-y-2 overflow-auto", children: filtered.map((service) => {
12935
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "mt-3 flex-1 space-y-2 overflow-y-auto", children: filtered.map((service) => {
12708
12936
  var _a, _b;
12709
12937
  const active = String(service.id) === String(activeServiceId);
12710
12938
  const count = get(service.id).length;
@@ -12745,11 +12973,172 @@ function FallbackServiceSidebar() {
12745
12973
  }
12746
12974
 
12747
12975
  // src/react/fallback-editor/FallbackRegistrationsPanel.tsx
12748
- var import_react23 = __toESM(require("react"), 1);
12749
-
12750
- // src/react/fallback-editor/FallbackAddCandidatesDialog.tsx
12751
12976
  var import_react21 = __toESM(require("react"), 1);
12752
12977
  var import_jsx_runtime12 = require("react/jsx-runtime");
12978
+ function FallbackRegistrationsPanel() {
12979
+ const { activeServiceId, remove: remove2, clear, check } = useFallbackEditor();
12980
+ const registrations = useActiveFallbackRegistrations();
12981
+ const eligibleServices = useEligibleServiceList();
12982
+ const [candidatePickerOpen, setCandidatePickerOpen] = import_react21.default.useState(false);
12983
+ const [candidateContext, setCandidateContext] = import_react21.default.useState(null);
12984
+ const [candidatePrimaryId, setCandidatePrimaryId] = import_react21.default.useState(void 0);
12985
+ const [registrationDialogOpen, setRegistrationDialogOpen] = import_react21.default.useState(false);
12986
+ const makeContext = import_react21.default.useCallback(
12987
+ (registration) => {
12988
+ if (registration.scope === "global") {
12989
+ return {
12990
+ scope: "global",
12991
+ primary: registration.primary
12992
+ };
12993
+ }
12994
+ return {
12995
+ scope: "node",
12996
+ nodeId: registration.scopeId
12997
+ };
12998
+ },
12999
+ []
13000
+ );
13001
+ const openCandidatePicker = import_react21.default.useCallback(
13002
+ (context, primaryId) => {
13003
+ setCandidateContext(context);
13004
+ setCandidatePrimaryId(primaryId);
13005
+ setCandidatePickerOpen(true);
13006
+ },
13007
+ []
13008
+ );
13009
+ if (activeServiceId === void 0 || activeServiceId === null) {
13010
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("section", { className: "rounded-2xl border border-zinc-200 bg-white p-4 shadow-sm dark:border-zinc-800 dark:bg-zinc-900 ", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "rounded-2xl border border-dashed border-zinc-300 p-6 text-sm text-zinc-500 dark:border-zinc-700 dark:text-zinc-400", children: "Select a primary service to start editing." }) });
13011
+ }
13012
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
13013
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("section", { className: "rounded-2xl border border-zinc-200 bg-white p-4 shadow-sm dark:border-zinc-800 dark:bg-zinc-900 overflow-y-auto", children: [
13014
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "mb-4 flex items-start justify-between gap-3", children: [
13015
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { children: [
13016
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h3", { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: "Registered fallbacks" }),
13017
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "mt-1 text-xs text-zinc-500 dark:text-zinc-400", children: "Use eligible services as fallback candidates for the selected primary." })
13018
+ ] }),
13019
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
13020
+ "button",
13021
+ {
13022
+ type: "button",
13023
+ onClick: () => setRegistrationDialogOpen(true),
13024
+ className: "rounded-xl border border-zinc-300 bg-white px-3 py-2 text-sm font-medium text-zinc-700 hover:bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-950 dark:text-zinc-200 dark:hover:bg-zinc-800",
13025
+ children: "Add registration"
13026
+ }
13027
+ )
13028
+ ] }),
13029
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "space-y-4", children: registrations.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "rounded-2xl border border-dashed border-zinc-300 p-6 text-sm text-zinc-500 dark:border-zinc-700 dark:text-zinc-400", children: "No registrations yet for this primary service." }) : registrations.map((reg, index) => {
13030
+ var _a;
13031
+ const context = makeContext(reg);
13032
+ const candidates = reg.services;
13033
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
13034
+ "div",
13035
+ {
13036
+ className: "rounded-2xl border border-zinc-200 bg-zinc-50 p-4 dark:border-zinc-800 dark:bg-zinc-950",
13037
+ children: [
13038
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex flex-wrap items-start justify-between gap-3", children: [
13039
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { children: [
13040
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: reg.scope === "global" ? "Global registration" : `Node \xB7 ${reg.scopeId}` }),
13041
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "mt-1 text-xs text-zinc-500 dark:text-zinc-400", children: [
13042
+ "Primary #",
13043
+ String(reg.primary)
13044
+ ] })
13045
+ ] }),
13046
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "rounded-full border border-zinc-200 bg-white px-2 py-1 text-[11px] text-zinc-600 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-300", children: [
13047
+ reg.scope,
13048
+ reg.scopeId ? ` \xB7 ${reg.scopeId}` : ""
13049
+ ] })
13050
+ ] }),
13051
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "mt-4 flex flex-wrap gap-2", children: candidates.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "text-xs text-zinc-500 dark:text-zinc-400", children: "No fallback services yet." }) : candidates.map((candidate) => {
13052
+ var _a2;
13053
+ const preview = check(context, [
13054
+ candidate
13055
+ ]);
13056
+ const rejected = preview.rejected[0];
13057
+ const tone = rejected ? "border-red-200 bg-red-50 text-red-700 dark:border-red-900/50 dark:bg-red-950/30 dark:text-red-300" : "border-zinc-200 bg-white text-zinc-700 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-200";
13058
+ const service = eligibleServices.find(
13059
+ (s) => String(s.id) === String(candidate)
13060
+ );
13061
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
13062
+ "div",
13063
+ {
13064
+ className: `inline-flex items-center gap-2 rounded-xl border px-3 py-2 text-xs ${tone}`,
13065
+ children: [
13066
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { children: service ? `#${String(service.id)} \xB7 ${(_a2 = service.name) != null ? _a2 : "Unnamed"}` : `#${String(candidate)}` }),
13067
+ rejected ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "rounded-full border border-current/20 px-2 py-0.5 text-[10px]", children: rejected.reasons.join(
13068
+ ", "
13069
+ ) }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "rounded-full border border-current/20 px-2 py-0.5 text-[10px]", children: "valid" }),
13070
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
13071
+ "button",
13072
+ {
13073
+ type: "button",
13074
+ onClick: () => remove2(
13075
+ context,
13076
+ candidate
13077
+ ),
13078
+ className: "text-current/70 hover:text-current",
13079
+ children: "\xD7"
13080
+ }
13081
+ )
13082
+ ]
13083
+ },
13084
+ String(candidate)
13085
+ );
13086
+ }) }),
13087
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "mt-4 flex gap-2", children: [
13088
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
13089
+ "button",
13090
+ {
13091
+ type: "button",
13092
+ onClick: () => openCandidatePicker(
13093
+ context,
13094
+ reg.primary
13095
+ ),
13096
+ className: "rounded-xl border border-zinc-300 bg-white px-3 py-2 text-sm font-medium text-zinc-700 hover:bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-200 dark:hover:bg-zinc-800",
13097
+ children: "Add fallback"
13098
+ }
13099
+ ),
13100
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
13101
+ "button",
13102
+ {
13103
+ type: "button",
13104
+ onClick: () => clear(context),
13105
+ className: "rounded-xl border border-red-300 bg-white px-3 py-2 text-sm font-medium text-red-600 hover:bg-red-50 dark:border-red-900/50 dark:bg-zinc-900 dark:text-red-300 dark:hover:bg-red-950/20",
13106
+ children: "Clear"
13107
+ }
13108
+ )
13109
+ ] })
13110
+ ]
13111
+ },
13112
+ `${reg.scope}:${String((_a = reg.scopeId) != null ? _a : "global")}:${index}`
13113
+ );
13114
+ }) })
13115
+ ] }),
13116
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
13117
+ FallbackAddRegistrationDialog,
13118
+ {
13119
+ open: registrationDialogOpen,
13120
+ onClose: () => setRegistrationDialogOpen(false),
13121
+ onSelect: (context, primaryId) => {
13122
+ setRegistrationDialogOpen(false);
13123
+ openCandidatePicker(context, primaryId);
13124
+ }
13125
+ }
13126
+ ),
13127
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
13128
+ FallbackAddCandidatesDialog,
13129
+ {
13130
+ open: candidatePickerOpen,
13131
+ onClose: () => setCandidatePickerOpen(false),
13132
+ context: candidateContext,
13133
+ primaryId: candidatePrimaryId
13134
+ }
13135
+ )
13136
+ ] });
13137
+ }
13138
+
13139
+ // src/react/fallback-editor/FallbackAddCandidatesDialog.tsx
13140
+ var import_react25 = __toESM(require("react"), 1);
13141
+ var import_jsx_runtime13 = require("react/jsx-runtime");
12753
13142
  function FallbackAddCandidatesDialog({
12754
13143
  open,
12755
13144
  onClose,
@@ -12758,23 +13147,23 @@ function FallbackAddCandidatesDialog({
12758
13147
  }) {
12759
13148
  const { eligible, addMany } = useFallbackEditor();
12760
13149
  const eligibleServices = useEligibleServiceList();
12761
- const [query, setQuery] = import_react21.default.useState("");
12762
- const [filterEligibleOnly, setFilterEligibleOnly] = import_react21.default.useState(true);
12763
- const [selected, setSelected] = import_react21.default.useState(/* @__PURE__ */ new Set());
12764
- const [submitting, setSubmitting] = import_react21.default.useState(false);
12765
- import_react21.default.useEffect(() => {
13150
+ const [query, setQuery] = import_react25.default.useState("");
13151
+ const [filterEligibleOnly, setFilterEligibleOnly] = import_react25.default.useState(true);
13152
+ const [selected, setSelected] = import_react25.default.useState(/* @__PURE__ */ new Set());
13153
+ const [submitting, setSubmitting] = import_react25.default.useState(false);
13154
+ import_react25.default.useEffect(() => {
12766
13155
  if (!open) {
12767
13156
  setQuery("");
12768
13157
  setFilterEligibleOnly(true);
12769
13158
  setSelected(/* @__PURE__ */ new Set());
12770
13159
  }
12771
13160
  }, [open]);
12772
- const allowedIds = import_react21.default.useMemo(() => {
13161
+ const allowedIds = import_react25.default.useMemo(() => {
12773
13162
  if (!context) return null;
12774
13163
  if (!filterEligibleOnly) return null;
12775
13164
  return new Set(eligible(context).map((id) => String(id)));
12776
13165
  }, [context, filterEligibleOnly, eligible]);
12777
- const items = import_react21.default.useMemo(() => {
13166
+ const items = import_react25.default.useMemo(() => {
12778
13167
  const q = query.trim().toLowerCase();
12779
13168
  return eligibleServices.filter((service) => {
12780
13169
  var _a, _b;
@@ -12809,13 +13198,13 @@ function FallbackAddCandidatesDialog({
12809
13198
  }
12810
13199
  }
12811
13200
  if (!open || !context) return null;
12812
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex max-h-[85vh] w-full max-w-3xl flex-col rounded-2xl border border-zinc-200 bg-white shadow-2xl dark:border-zinc-800 dark:bg-zinc-900", children: [
12813
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "border-b border-zinc-200 p-4 dark:border-zinc-800", children: [
12814
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h3", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "Add fallback services" }),
12815
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "mt-1 text-sm text-zinc-500 dark:text-zinc-400", children: "Search and select one or more eligible fallback candidates." })
13201
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex max-h-[85vh] w-full max-w-3xl flex-col rounded-2xl border border-zinc-200 bg-white shadow-2xl dark:border-zinc-800 dark:bg-zinc-900", children: [
13202
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "border-b border-zinc-200 p-4 dark:border-zinc-800", children: [
13203
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("h3", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "Add fallback services" }),
13204
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { className: "mt-1 text-sm text-zinc-500 dark:text-zinc-400", children: "Search and select one or more eligible fallback candidates." })
12816
13205
  ] }),
12817
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex flex-col gap-3 p-4", children: [
12818
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
13206
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex flex-col gap-3 p-4", children: [
13207
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
12819
13208
  "input",
12820
13209
  {
12821
13210
  value: query,
@@ -12824,8 +13213,8 @@ function FallbackAddCandidatesDialog({
12824
13213
  className: "rounded-xl border border-zinc-300 bg-white px-3 py-2 text-sm outline-none focus:border-blue-500 dark:border-zinc-700 dark:bg-zinc-950 dark:text-zinc-100"
12825
13214
  }
12826
13215
  ),
12827
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("label", { className: "inline-flex items-center gap-2 text-sm text-zinc-700 dark:text-zinc-300", children: [
12828
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
13216
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("label", { className: "inline-flex items-center gap-2 text-sm text-zinc-700 dark:text-zinc-300", children: [
13217
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
12829
13218
  "input",
12830
13219
  {
12831
13220
  type: "checkbox",
@@ -12836,7 +13225,7 @@ function FallbackAddCandidatesDialog({
12836
13225
  ),
12837
13226
  "Filter eligible only"
12838
13227
  ] }),
12839
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
13228
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
12840
13229
  VirtualServiceList,
12841
13230
  {
12842
13231
  items,
@@ -12846,13 +13235,13 @@ function FallbackAddCandidatesDialog({
12846
13235
  }
12847
13236
  )
12848
13237
  ] }),
12849
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center justify-between border-t border-zinc-200 p-4 dark:border-zinc-800", children: [
12850
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "text-sm text-zinc-500 dark:text-zinc-400", children: [
13238
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center justify-between border-t border-zinc-200 p-4 dark:border-zinc-800", children: [
13239
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "text-sm text-zinc-500 dark:text-zinc-400", children: [
12851
13240
  selected.size,
12852
13241
  " selected"
12853
13242
  ] }),
12854
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex gap-2", children: [
12855
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
13243
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex gap-2", children: [
13244
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
12856
13245
  "button",
12857
13246
  {
12858
13247
  type: "button",
@@ -12861,7 +13250,7 @@ function FallbackAddCandidatesDialog({
12861
13250
  children: "Cancel"
12862
13251
  }
12863
13252
  ),
12864
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
13253
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
12865
13254
  "button",
12866
13255
  {
12867
13256
  type: "button",
@@ -12877,8 +13266,8 @@ function FallbackAddCandidatesDialog({
12877
13266
  }
12878
13267
 
12879
13268
  // src/react/fallback-editor/FallbackAddRegistrationDialog.tsx
12880
- var import_react22 = __toESM(require("react"), 1);
12881
- var import_jsx_runtime13 = require("react/jsx-runtime");
13269
+ var import_react26 = __toESM(require("react"), 1);
13270
+ var import_jsx_runtime14 = require("react/jsx-runtime");
12882
13271
  function FallbackAddRegistrationDialog({
12883
13272
  open,
12884
13273
  onClose,
@@ -12886,23 +13275,23 @@ function FallbackAddRegistrationDialog({
12886
13275
  }) {
12887
13276
  const { activeServiceId, serviceProps, snapshot } = useFallbackEditor();
12888
13277
  const registrations = useActiveFallbackRegistrations();
12889
- const [scope, setScope] = import_react22.default.useState("global");
12890
- const [nodeId, setNodeId] = import_react22.default.useState("");
12891
- const mode = import_react22.default.useMemo(() => {
13278
+ const [scope, setScope] = import_react26.default.useState("global");
13279
+ const [nodeId, setNodeId] = import_react26.default.useState("");
13280
+ const mode = import_react26.default.useMemo(() => {
12892
13281
  if (snapshot) return "snapshot";
12893
13282
  if (serviceProps) return "props";
12894
13283
  return "none";
12895
13284
  }, [snapshot, serviceProps]);
12896
- import_react22.default.useEffect(() => {
13285
+ import_react26.default.useEffect(() => {
12897
13286
  if (open) {
12898
13287
  setScope("global");
12899
13288
  setNodeId("");
12900
13289
  }
12901
13290
  }, [open]);
12902
- const hasGlobal = import_react22.default.useMemo(() => {
13291
+ const hasGlobal = import_react26.default.useMemo(() => {
12903
13292
  return registrations.some((r) => r.scope === "global");
12904
13293
  }, [registrations]);
12905
- const nodeTargets = import_react22.default.useMemo(() => {
13294
+ const nodeTargets = import_react26.default.useMemo(() => {
12906
13295
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
12907
13296
  if (activeServiceId === void 0 || activeServiceId === null) {
12908
13297
  return [];
@@ -12992,12 +13381,12 @@ function FallbackAddRegistrationDialog({
12992
13381
  }
12993
13382
  return [];
12994
13383
  }, [mode, snapshot, serviceProps, activeServiceId]);
12995
- import_react22.default.useEffect(() => {
13384
+ import_react26.default.useEffect(() => {
12996
13385
  if (hasGlobal && scope === "global") {
12997
13386
  setScope("node");
12998
13387
  }
12999
13388
  }, [hasGlobal, scope]);
13000
- import_react22.default.useEffect(() => {
13389
+ import_react26.default.useEffect(() => {
13001
13390
  if (scope === "node" && nodeId) {
13002
13391
  const exists = nodeTargets.some((node) => node.id === nodeId);
13003
13392
  if (!exists) setNodeId("");
@@ -13028,17 +13417,17 @@ function FallbackAddRegistrationDialog({
13028
13417
  }
13029
13418
  if (!open) return null;
13030
13419
  const nodeScopeDisabled = nodeTargets.length === 0;
13031
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "w-full max-w-lg rounded-2xl border border-zinc-200 bg-white shadow-2xl dark:border-zinc-800 dark:bg-zinc-900", children: [
13032
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "border-b border-zinc-200 p-4 dark:border-zinc-800", children: [
13033
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("h3", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "Add registration" }),
13034
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { className: "mt-1 text-sm text-zinc-500 dark:text-zinc-400", children: "Choose the registration scope before selecting fallback candidates." })
13420
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "w-full max-w-lg rounded-2xl border border-zinc-200 bg-white shadow-2xl dark:border-zinc-800 dark:bg-zinc-900", children: [
13421
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "border-b border-zinc-200 p-4 dark:border-zinc-800", children: [
13422
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("h3", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "Add registration" }),
13423
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "mt-1 text-sm text-zinc-500 dark:text-zinc-400", children: "Choose the registration scope before selecting fallback candidates." })
13035
13424
  ] }),
13036
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "space-y-4 p-4", children: [
13037
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "space-y-2", children: [
13038
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "text-sm font-medium text-zinc-900 dark:text-zinc-100", children: "Scope" }),
13039
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "grid gap-2", children: [
13040
- !hasGlobal && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("label", { className: "flex cursor-pointer items-start gap-3 rounded-xl border border-zinc-200 p-3 dark:border-zinc-800", children: [
13041
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
13425
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "space-y-4 p-4", children: [
13426
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "space-y-2", children: [
13427
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "text-sm font-medium text-zinc-900 dark:text-zinc-100", children: "Scope" }),
13428
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "grid gap-2", children: [
13429
+ !hasGlobal && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("label", { className: "flex cursor-pointer items-start gap-3 rounded-xl border border-zinc-200 p-3 dark:border-zinc-800", children: [
13430
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
13042
13431
  "input",
13043
13432
  {
13044
13433
  type: "radio",
@@ -13048,12 +13437,12 @@ function FallbackAddRegistrationDialog({
13048
13437
  className: "mt-1 h-4 w-4"
13049
13438
  }
13050
13439
  ),
13051
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
13052
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "text-sm font-medium text-zinc-900 dark:text-zinc-100", children: "Global" }),
13053
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "mt-1 text-xs text-zinc-500 dark:text-zinc-400", children: "Use one global registration for this primary service." })
13440
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { children: [
13441
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "text-sm font-medium text-zinc-900 dark:text-zinc-100", children: "Global" }),
13442
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "mt-1 text-xs text-zinc-500 dark:text-zinc-400", children: "Use one global registration for this primary service." })
13054
13443
  ] })
13055
13444
  ] }),
13056
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
13445
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
13057
13446
  "label",
13058
13447
  {
13059
13448
  className: [
@@ -13061,7 +13450,7 @@ function FallbackAddRegistrationDialog({
13061
13450
  nodeScopeDisabled ? "cursor-not-allowed border-zinc-200 opacity-60 dark:border-zinc-800" : "cursor-pointer border-zinc-200 dark:border-zinc-800"
13062
13451
  ].join(" "),
13063
13452
  children: [
13064
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
13453
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
13065
13454
  "input",
13066
13455
  {
13067
13456
  type: "radio",
@@ -13072,26 +13461,26 @@ function FallbackAddRegistrationDialog({
13072
13461
  className: "mt-1 h-4 w-4"
13073
13462
  }
13074
13463
  ),
13075
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
13076
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "text-sm font-medium text-zinc-900 dark:text-zinc-100", children: "Node" }),
13077
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "mt-1 text-xs text-zinc-500 dark:text-zinc-400", children: mode === "snapshot" ? "Pick a node currently active in the order snapshot for this primary service." : mode === "props" ? "Pick a tag, field, or option from ServiceProps that maps to this primary service." : "Node-scoped registration is unavailable without OrderSnapshot or ServiceProps." })
13464
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { children: [
13465
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "text-sm font-medium text-zinc-900 dark:text-zinc-100", children: "Node" }),
13466
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "mt-1 text-xs text-zinc-500 dark:text-zinc-400", children: mode === "snapshot" ? "Pick a node currently active in the order snapshot for this primary service." : mode === "props" ? "Pick a tag, field, or option from ServiceProps that maps to this primary service." : "Node-scoped registration is unavailable without OrderSnapshot or ServiceProps." })
13078
13467
  ] })
13079
13468
  ]
13080
13469
  }
13081
13470
  )
13082
13471
  ] })
13083
13472
  ] }),
13084
- scope === "node" && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "space-y-2", children: [
13085
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "text-sm font-medium text-zinc-900 dark:text-zinc-100", children: "Node id" }),
13086
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
13473
+ scope === "node" && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "space-y-2", children: [
13474
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "text-sm font-medium text-zinc-900 dark:text-zinc-100", children: "Node id" }),
13475
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
13087
13476
  "select",
13088
13477
  {
13089
13478
  value: nodeId,
13090
13479
  onChange: (e) => setNodeId(e.target.value),
13091
13480
  className: "w-full rounded-xl border border-zinc-300 bg-white px-3 py-2 text-sm outline-none focus:border-blue-500 dark:border-zinc-700 dark:bg-zinc-950 dark:text-zinc-100",
13092
13481
  children: [
13093
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "", children: "Select node\u2026" }),
13094
- nodeTargets.map((node) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("option", { value: node.id, children: [
13482
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("option", { value: "", children: "Select node\u2026" }),
13483
+ nodeTargets.map((node) => /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("option", { value: node.id, children: [
13095
13484
  "[",
13096
13485
  node.kind,
13097
13486
  "] ",
@@ -13102,11 +13491,11 @@ function FallbackAddRegistrationDialog({
13102
13491
  ]
13103
13492
  }
13104
13493
  ),
13105
- nodeScopeDisabled ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "text-xs text-zinc-500 dark:text-zinc-400", children: mode === "snapshot" ? "No active snapshot nodes were found for this primary service." : mode === "props" ? "No ServiceProps nodes were found for this primary service." : "Node-scoped registration requires either OrderSnapshot or ServiceProps." }) : null
13494
+ nodeScopeDisabled ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "text-xs text-zinc-500 dark:text-zinc-400", children: mode === "snapshot" ? "No active snapshot nodes were found for this primary service." : mode === "props" ? "No ServiceProps nodes were found for this primary service." : "Node-scoped registration requires either OrderSnapshot or ServiceProps." }) : null
13106
13495
  ] })
13107
13496
  ] }),
13108
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center justify-end gap-2 border-t border-zinc-200 p-4 dark:border-zinc-800", children: [
13109
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
13497
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex items-center justify-end gap-2 border-t border-zinc-200 p-4 dark:border-zinc-800", children: [
13498
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
13110
13499
  "button",
13111
13500
  {
13112
13501
  type: "button",
@@ -13115,7 +13504,7 @@ function FallbackAddRegistrationDialog({
13115
13504
  children: "Cancel"
13116
13505
  }
13117
13506
  ),
13118
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
13507
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
13119
13508
  "button",
13120
13509
  {
13121
13510
  type: "button",
@@ -13158,169 +13547,6 @@ function resolveNodeMeta(props, nodeId) {
13158
13547
  }
13159
13548
  return { kind: "node", label: nodeId };
13160
13549
  }
13161
-
13162
- // src/react/fallback-editor/FallbackRegistrationsPanel.tsx
13163
- var import_jsx_runtime14 = require("react/jsx-runtime");
13164
- function FallbackRegistrationsPanel() {
13165
- const { activeServiceId, remove: remove2, clear, check } = useFallbackEditor();
13166
- const registrations = useActiveFallbackRegistrations();
13167
- const eligibleServices = useEligibleServiceList();
13168
- const [candidatePickerOpen, setCandidatePickerOpen] = import_react23.default.useState(false);
13169
- const [candidateContext, setCandidateContext] = import_react23.default.useState(null);
13170
- const [candidatePrimaryId, setCandidatePrimaryId] = import_react23.default.useState(void 0);
13171
- const [registrationDialogOpen, setRegistrationDialogOpen] = import_react23.default.useState(false);
13172
- const makeContext = import_react23.default.useCallback(
13173
- (registration) => {
13174
- if (registration.scope === "global") {
13175
- return {
13176
- scope: "global",
13177
- primary: registration.primary
13178
- };
13179
- }
13180
- return {
13181
- scope: "node",
13182
- nodeId: registration.scopeId
13183
- };
13184
- },
13185
- []
13186
- );
13187
- const openCandidatePicker = import_react23.default.useCallback(
13188
- (context, primaryId) => {
13189
- setCandidateContext(context);
13190
- setCandidatePrimaryId(primaryId);
13191
- setCandidatePickerOpen(true);
13192
- },
13193
- []
13194
- );
13195
- if (activeServiceId === void 0 || activeServiceId === null) {
13196
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("section", { className: "rounded-2xl border border-zinc-200 bg-white p-4 shadow-sm dark:border-zinc-800 dark:bg-zinc-900", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "rounded-2xl border border-dashed border-zinc-300 p-6 text-sm text-zinc-500 dark:border-zinc-700 dark:text-zinc-400", children: "Select a primary service to start editing." }) });
13197
- }
13198
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
13199
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("section", { className: "rounded-2xl border border-zinc-200 bg-white p-4 shadow-sm dark:border-zinc-800 dark:bg-zinc-900", children: [
13200
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "mb-4 flex items-start justify-between gap-3", children: [
13201
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { children: [
13202
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("h3", { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: "Registered fallbacks" }),
13203
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "mt-1 text-xs text-zinc-500 dark:text-zinc-400", children: "Use eligible services as fallback candidates for the selected primary." })
13204
- ] }),
13205
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
13206
- "button",
13207
- {
13208
- type: "button",
13209
- onClick: () => setRegistrationDialogOpen(true),
13210
- className: "rounded-xl border border-zinc-300 bg-white px-3 py-2 text-sm font-medium text-zinc-700 hover:bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-950 dark:text-zinc-200 dark:hover:bg-zinc-800",
13211
- children: "Add registration"
13212
- }
13213
- )
13214
- ] }),
13215
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "space-y-4", children: registrations.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "rounded-2xl border border-dashed border-zinc-300 p-6 text-sm text-zinc-500 dark:border-zinc-700 dark:text-zinc-400", children: "No registrations yet for this primary service." }) : registrations.map((reg, index) => {
13216
- var _a;
13217
- const context = makeContext(reg);
13218
- const candidates = reg.services;
13219
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
13220
- "div",
13221
- {
13222
- className: "rounded-2xl border border-zinc-200 bg-zinc-50 p-4 dark:border-zinc-800 dark:bg-zinc-950",
13223
- children: [
13224
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex flex-wrap items-start justify-between gap-3", children: [
13225
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { children: [
13226
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: reg.scope === "global" ? "Global registration" : `Node \xB7 ${reg.scopeId}` }),
13227
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "mt-1 text-xs text-zinc-500 dark:text-zinc-400", children: [
13228
- "Primary #",
13229
- String(reg.primary)
13230
- ] })
13231
- ] }),
13232
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { className: "rounded-full border border-zinc-200 bg-white px-2 py-1 text-[11px] text-zinc-600 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-300", children: [
13233
- reg.scope,
13234
- reg.scopeId ? ` \xB7 ${reg.scopeId}` : ""
13235
- ] })
13236
- ] }),
13237
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "mt-4 flex flex-wrap gap-2", children: candidates.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "text-xs text-zinc-500 dark:text-zinc-400", children: "No fallback services yet." }) : candidates.map((candidate) => {
13238
- var _a2;
13239
- const preview = check(context, [
13240
- candidate
13241
- ]);
13242
- const rejected = preview.rejected[0];
13243
- const tone = rejected ? "border-red-200 bg-red-50 text-red-700 dark:border-red-900/50 dark:bg-red-950/30 dark:text-red-300" : "border-zinc-200 bg-white text-zinc-700 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-200";
13244
- const service = eligibleServices.find(
13245
- (s) => String(s.id) === String(candidate)
13246
- );
13247
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
13248
- "div",
13249
- {
13250
- className: `inline-flex items-center gap-2 rounded-xl border px-3 py-2 text-xs ${tone}`,
13251
- children: [
13252
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { children: service ? `#${String(service.id)} \xB7 ${(_a2 = service.name) != null ? _a2 : "Unnamed"}` : `#${String(candidate)}` }),
13253
- rejected ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "rounded-full border border-current/20 px-2 py-0.5 text-[10px]", children: rejected.reasons.join(
13254
- ", "
13255
- ) }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "rounded-full border border-current/20 px-2 py-0.5 text-[10px]", children: "valid" }),
13256
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
13257
- "button",
13258
- {
13259
- type: "button",
13260
- onClick: () => remove2(
13261
- context,
13262
- candidate
13263
- ),
13264
- className: "text-current/70 hover:text-current",
13265
- children: "\xD7"
13266
- }
13267
- )
13268
- ]
13269
- },
13270
- String(candidate)
13271
- );
13272
- }) }),
13273
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "mt-4 flex gap-2", children: [
13274
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
13275
- "button",
13276
- {
13277
- type: "button",
13278
- onClick: () => openCandidatePicker(
13279
- context,
13280
- reg.primary
13281
- ),
13282
- className: "rounded-xl border border-zinc-300 bg-white px-3 py-2 text-sm font-medium text-zinc-700 hover:bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-200 dark:hover:bg-zinc-800",
13283
- children: "Add fallback"
13284
- }
13285
- ),
13286
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
13287
- "button",
13288
- {
13289
- type: "button",
13290
- onClick: () => clear(context),
13291
- className: "rounded-xl border border-red-300 bg-white px-3 py-2 text-sm font-medium text-red-600 hover:bg-red-50 dark:border-red-900/50 dark:bg-zinc-900 dark:text-red-300 dark:hover:bg-red-950/20",
13292
- children: "Clear"
13293
- }
13294
- )
13295
- ] })
13296
- ]
13297
- },
13298
- `${reg.scope}:${String((_a = reg.scopeId) != null ? _a : "global")}:${index}`
13299
- );
13300
- }) })
13301
- ] }),
13302
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
13303
- FallbackAddRegistrationDialog,
13304
- {
13305
- open: registrationDialogOpen,
13306
- onClose: () => setRegistrationDialogOpen(false),
13307
- onSelect: (context, primaryId) => {
13308
- setRegistrationDialogOpen(false);
13309
- openCandidatePicker(context, primaryId);
13310
- }
13311
- }
13312
- ),
13313
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
13314
- FallbackAddCandidatesDialog,
13315
- {
13316
- open: candidatePickerOpen,
13317
- onClose: () => setCandidatePickerOpen(false),
13318
- context: candidateContext,
13319
- primaryId: candidatePrimaryId
13320
- }
13321
- )
13322
- ] });
13323
- }
13324
13550
  // Annotate the CommonJS export names for ESM import in node:
13325
13551
  0 && (module.exports = {
13326
13552
  CanvasAPI,