@timeax/digital-service-engine 0.2.1 → 0.2.3

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" }),
@@ -12238,6 +12466,7 @@ function TabButton({
12238
12466
 
12239
12467
  // src/react/fallback-editor/VirtualServiceList.tsx
12240
12468
  var import_react17 = __toESM(require("react"), 1);
12469
+ var import_lucide_react = require("lucide-react");
12241
12470
  var import_jsx_runtime7 = require("react/jsx-runtime");
12242
12471
  function VirtualServiceList({
12243
12472
  items,
@@ -12280,7 +12509,7 @@ function VirtualServiceList({
12280
12509
  {
12281
12510
  type: "button",
12282
12511
  onClick: () => onToggle(item.id),
12283
- className: "absolute left-0 right-0 flex items-center justify-between border-b border-zinc-100 bg-white px-3 text-left hover:bg-zinc-50 dark:border-zinc-800 dark:bg-zinc-900 dark:hover:bg-zinc-800",
12512
+ className: "absolute left-0 right-0 flex items-center justify-between border-b border-zinc-100 bg-white px-3 text-left transition hover:bg-zinc-50 dark:border-zinc-800 dark:bg-zinc-900 dark:hover:bg-zinc-800",
12284
12513
  style: {
12285
12514
  top: index * rowHeight,
12286
12515
  height: rowHeight
@@ -12300,12 +12529,13 @@ function VirtualServiceList({
12300
12529
  ] })
12301
12530
  ] }),
12302
12531
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
12303
- "input",
12532
+ "span",
12304
12533
  {
12305
- type: "checkbox",
12306
- checked,
12307
- readOnly: true,
12308
- className: "h-4 w-4 rounded border-zinc-300"
12534
+ className: [
12535
+ "inline-flex h-4 w-4 items-center justify-center rounded border transition",
12536
+ checked ? "border-blue-600 bg-blue-600 text-white dark:border-blue-500 dark:bg-blue-500" : "border-zinc-300 bg-white text-transparent dark:border-zinc-700 dark:bg-zinc-800"
12537
+ ].join(" "),
12538
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react.Check, { className: "h-3 w-3" })
12309
12539
  }
12310
12540
  )
12311
12541
  ]
@@ -12328,7 +12558,7 @@ function FallbackDetailsPanel() {
12328
12558
  () => services.find((s) => String(s.id) === String(activeServiceId)),
12329
12559
  [services, activeServiceId]
12330
12560
  );
12331
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("aside", { className: "flex min-h-0 flex-col gap-4", children: [
12561
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("aside", { className: "flex min-h-0 flex-col gap-4 overflow-y-auto", children: [
12332
12562
  /* @__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
12563
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h3", { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: "Primary service info" }),
12334
12564
  !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: [
@@ -12455,6 +12685,7 @@ function FallbackEditorHeader({
12455
12685
 
12456
12686
  // src/react/fallback-editor/FallbackSettingsPanel.tsx
12457
12687
  var import_react19 = __toESM(require("react"), 1);
12688
+ var import_form_palette23 = require("@timeax/form-palette");
12458
12689
  var import_jsx_runtime10 = require("react/jsx-runtime");
12459
12690
  function FallbackSettingsPanel() {
12460
12691
  var _a, _b, _c;
@@ -12490,7 +12721,7 @@ function FallbackSettingsPanel() {
12490
12721
  kind: "lte_primary",
12491
12722
  pct: 5
12492
12723
  };
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: [
12724
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("section", { className: "overflow-y-auto rounded-2xl border border-zinc-200 bg-white p-4 shadow-sm dark:border-zinc-800 dark:bg-zinc-900", children: [
12494
12725
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "mb-4 flex items-start justify-between gap-3", children: [
12495
12726
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
12496
12727
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h3", { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: "Fallback settings" }),
@@ -12516,30 +12747,17 @@ function FallbackSettingsPanel() {
12516
12747
  {
12517
12748
  title: "Require constraint fit",
12518
12749
  hint: "Reject or warn when a candidate does not match effective tag constraints.",
12519
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
12520
- "button",
12750
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
12751
+ import_form_palette23.InputField,
12521
12752
  {
12522
- type: "button",
12523
- onClick: () => setDraft((prev) => ({
12753
+ variant: "toggle",
12754
+ value: Boolean(draft.requireConstraintFit),
12755
+ onChange: ({ value }) => setDraft((prev) => ({
12524
12756
  ...prev,
12525
- requireConstraintFit: !(prev == null ? void 0 : prev.requireConstraintFit)
12757
+ requireConstraintFit: Boolean(value)
12526
12758
  })),
12527
- className: [
12528
- "inline-flex items-center gap-2 rounded-full border px-3 py-2 text-sm",
12529
- draft.requireConstraintFit ? "border-green-300 bg-green-50 text-green-700 dark:border-green-900/50 dark:bg-green-950/30 dark:text-green-300" : "border-zinc-300 bg-white text-zinc-600 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-300"
12530
- ].join(" "),
12531
- children: [
12532
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: draft.requireConstraintFit ? "Enabled" : "Disabled" }),
12533
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
12534
- "span",
12535
- {
12536
- className: [
12537
- "h-2.5 w-2.5 rounded-full",
12538
- draft.requireConstraintFit ? "bg-green-500" : "bg-zinc-400"
12539
- ].join(" ")
12540
- }
12541
- )
12542
- ]
12759
+ onText: "Enabled",
12760
+ offText: "Disabled"
12543
12761
  }
12544
12762
  )
12545
12763
  }
@@ -12550,12 +12768,13 @@ function FallbackSettingsPanel() {
12550
12768
  title: "Rate policy",
12551
12769
  hint: "Controls how fallback service rates are compared against the primary service.",
12552
12770
  children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-col gap-2 md:items-end", children: [
12553
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
12554
- "select",
12771
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
12772
+ import_form_palette23.InputField,
12555
12773
  {
12774
+ variant: "select",
12556
12775
  value: ratePolicy.kind,
12557
- onChange: (e) => {
12558
- const kind = e.target.value;
12776
+ onChange: ({ value }) => {
12777
+ const kind = value;
12559
12778
  if (kind === "eq_primary") {
12560
12779
  setRatePolicy({ kind: "eq_primary" });
12561
12780
  return;
@@ -12580,30 +12799,39 @@ function FallbackSettingsPanel() {
12580
12799
  pct: currentPct
12581
12800
  });
12582
12801
  },
12583
- className: "w-56 rounded-xl border border-zinc-300 bg-white px-3 py-2 text-sm dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-100",
12584
- children: [
12585
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: "eq_primary", children: "eq_primary" }),
12586
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: "lte_primary", children: "lte_primary" }),
12587
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: "within_pct", children: "within_pct" }),
12588
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: "at_least_pct_lower", children: "at_least_pct_lower" })
12589
- ]
12802
+ options: [
12803
+ { value: "eq_primary", label: "eq_primary" },
12804
+ {
12805
+ value: "lte_primary",
12806
+ label: "lte_primary"
12807
+ },
12808
+ { value: "within_pct", label: "within_pct" },
12809
+ {
12810
+ value: "at_least_pct_lower",
12811
+ label: "at_least_pct_lower"
12812
+ }
12813
+ ],
12814
+ clearable: false
12590
12815
  }
12591
12816
  ),
12592
12817
  ratePolicy.kind !== "eq_primary" && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center gap-2", children: [
12593
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
12594
- "input",
12818
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "w-32", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
12819
+ import_form_palette23.InputField,
12595
12820
  {
12596
- type: "number",
12597
- min: 0,
12598
- step: "0.01",
12821
+ variant: "number",
12599
12822
  value: ratePolicy.pct,
12600
- onChange: (e) => {
12601
- const pct = Number(e.target.value || 0);
12602
- setRatePolicy({ ...ratePolicy, pct });
12823
+ onChange: ({ value }) => {
12824
+ const pct = typeof value === "number" ? value : Number(value != null ? value : 0);
12825
+ setRatePolicy({
12826
+ ...ratePolicy,
12827
+ pct
12828
+ });
12603
12829
  },
12604
- className: "w-32 rounded-xl border border-zinc-300 bg-white px-3 py-2 text-sm dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-100"
12830
+ min: 0,
12831
+ step: 0.01,
12832
+ fullWidth: true
12605
12833
  }
12606
- ),
12834
+ ) }),
12607
12835
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-sm text-zinc-500 dark:text-zinc-400", children: "%" })
12608
12836
  ] })
12609
12837
  ] })
@@ -12614,19 +12842,20 @@ function FallbackSettingsPanel() {
12614
12842
  {
12615
12843
  title: "Selection strategy",
12616
12844
  hint: "How valid fallback candidates are ordered in previews.",
12617
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
12618
- "select",
12845
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
12846
+ import_form_palette23.InputField,
12619
12847
  {
12848
+ variant: "select",
12620
12849
  value: (_b = draft.selectionStrategy) != null ? _b : "priority",
12621
- onChange: (e) => setDraft((prev) => ({
12850
+ onChange: ({ value }) => setDraft((prev) => ({
12622
12851
  ...prev,
12623
- selectionStrategy: e.target.value
12852
+ selectionStrategy: value
12624
12853
  })),
12625
- className: "w-48 rounded-xl border border-zinc-300 bg-white px-3 py-2 text-sm dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-100",
12626
- children: [
12627
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: "priority", children: "priority" }),
12628
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: "cheapest", children: "cheapest" })
12629
- ]
12854
+ options: [
12855
+ { value: "priority", label: "priority" },
12856
+ { value: "cheapest", label: "cheapest" }
12857
+ ],
12858
+ clearable: false
12630
12859
  }
12631
12860
  )
12632
12861
  }
@@ -12636,19 +12865,20 @@ function FallbackSettingsPanel() {
12636
12865
  {
12637
12866
  title: "Mode",
12638
12867
  hint: "Use strict for enforced filtering, dev for advisory feedback.",
12639
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
12640
- "select",
12868
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
12869
+ import_form_palette23.InputField,
12641
12870
  {
12871
+ variant: "select",
12642
12872
  value: (_c = draft.mode) != null ? _c : "strict",
12643
- onChange: (e) => setDraft((prev) => ({
12873
+ onChange: ({ value }) => setDraft((prev) => ({
12644
12874
  ...prev,
12645
- mode: e.target.value
12875
+ mode: value
12646
12876
  })),
12647
- className: "w-48 rounded-xl border border-zinc-300 bg-white px-3 py-2 text-sm dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-100",
12648
- children: [
12649
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: "strict", children: "strict" }),
12650
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: "dev", children: "dev" })
12651
- ]
12877
+ options: [
12878
+ { value: "strict", label: "strict" },
12879
+ { value: "dev", label: "dev" }
12880
+ ],
12881
+ clearable: false
12652
12882
  }
12653
12883
  )
12654
12884
  }
@@ -12674,6 +12904,8 @@ function SettingRow({
12674
12904
 
12675
12905
  // src/react/fallback-editor/FallbackServiceSidebar.tsx
12676
12906
  var import_react20 = require("react");
12907
+ var import_form_palette24 = require("@timeax/form-palette");
12908
+ var import_lucide_react2 = require("lucide-react");
12677
12909
  var import_jsx_runtime11 = require("react/jsx-runtime");
12678
12910
  function FallbackServiceSidebar() {
12679
12911
  const { activeServiceId, setActiveServiceId, get } = useFallbackEditor();
@@ -12689,22 +12921,26 @@ function FallbackServiceSidebar() {
12689
12921
  }
12690
12922
  );
12691
12923
  }, [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: [
12924
+ 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
12925
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "border-b border-zinc-200 p-4 dark:border-zinc-800", children: [
12694
12926
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h2", { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: "Primary services" }),
12695
12927
  /* @__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." })
12696
12928
  ] }),
12697
12929
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex min-h-0 flex-1 flex-col p-4", children: [
12698
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
12699
- "input",
12930
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "rounded-xl border border-zinc-200 bg-zinc-50/80 p-2 dark:border-zinc-800 dark:bg-zinc-950/80", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
12931
+ import_form_palette24.InputField,
12700
12932
  {
12933
+ variant: "text",
12701
12934
  value: query,
12702
- onChange: (e) => setQuery(e.target.value),
12935
+ onChange: ({ value }) => setQuery(String(value != null ? value : "")),
12703
12936
  placeholder: "Search primary service...",
12704
- 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"
12937
+ leadingControl: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react2.Search, { className: "h-4 w-4 text-zinc-400" }),
12938
+ joinControls: true,
12939
+ extendBoxToControls: true,
12940
+ fullWidth: true
12705
12941
  }
12706
- ),
12707
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "mt-3 flex-1 space-y-2 overflow-auto", children: filtered.map((service) => {
12942
+ ) }),
12943
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "mt-3 flex-1 space-y-2 overflow-y-auto", children: filtered.map((service) => {
12708
12944
  var _a, _b;
12709
12945
  const active = String(service.id) === String(activeServiceId);
12710
12946
  const count = get(service.id).length;
@@ -12715,7 +12951,7 @@ function FallbackServiceSidebar() {
12715
12951
  onClick: () => setActiveServiceId(service.id),
12716
12952
  className: [
12717
12953
  "w-full rounded-2xl border p-3 text-left transition",
12718
- active ? "border-blue-500 bg-blue-50 dark:bg-blue-950/30" : "border-zinc-200 bg-zinc-50 hover:border-zinc-300 dark:border-zinc-800 dark:bg-zinc-950 dark:hover:border-zinc-700"
12954
+ active ? "border-blue-500 bg-blue-50 dark:border-blue-500/70 dark:bg-blue-950/30" : "border-zinc-200 bg-zinc-50 hover:border-zinc-300 dark:border-zinc-800 dark:bg-zinc-950 dark:hover:border-zinc-700 dark:hover:bg-zinc-900"
12719
12955
  ].join(" "),
12720
12956
  children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-start justify-between gap-3", children: [
12721
12957
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "min-w-0", children: [
@@ -12745,11 +12981,174 @@ function FallbackServiceSidebar() {
12745
12981
  }
12746
12982
 
12747
12983
  // src/react/fallback-editor/FallbackRegistrationsPanel.tsx
12748
- var import_react23 = __toESM(require("react"), 1);
12749
-
12750
- // src/react/fallback-editor/FallbackAddCandidatesDialog.tsx
12751
12984
  var import_react21 = __toESM(require("react"), 1);
12752
12985
  var import_jsx_runtime12 = require("react/jsx-runtime");
12986
+ function FallbackRegistrationsPanel() {
12987
+ const { activeServiceId, remove: remove2, clear, check } = useFallbackEditor();
12988
+ const registrations = useActiveFallbackRegistrations();
12989
+ const eligibleServices = useEligibleServiceList();
12990
+ const [candidatePickerOpen, setCandidatePickerOpen] = import_react21.default.useState(false);
12991
+ const [candidateContext, setCandidateContext] = import_react21.default.useState(null);
12992
+ const [candidatePrimaryId, setCandidatePrimaryId] = import_react21.default.useState(void 0);
12993
+ const [registrationDialogOpen, setRegistrationDialogOpen] = import_react21.default.useState(false);
12994
+ const makeContext = import_react21.default.useCallback(
12995
+ (registration) => {
12996
+ if (registration.scope === "global") {
12997
+ return {
12998
+ scope: "global",
12999
+ primary: registration.primary
13000
+ };
13001
+ }
13002
+ return {
13003
+ scope: "node",
13004
+ nodeId: registration.scopeId
13005
+ };
13006
+ },
13007
+ []
13008
+ );
13009
+ const openCandidatePicker = import_react21.default.useCallback(
13010
+ (context, primaryId) => {
13011
+ setCandidateContext(context);
13012
+ setCandidatePrimaryId(primaryId);
13013
+ setCandidatePickerOpen(true);
13014
+ },
13015
+ []
13016
+ );
13017
+ if (activeServiceId === void 0 || activeServiceId === null) {
13018
+ 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." }) });
13019
+ }
13020
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
13021
+ /* @__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: [
13022
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "mb-4 flex items-start justify-between gap-3", children: [
13023
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { children: [
13024
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h3", { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: "Registered fallbacks" }),
13025
+ /* @__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." })
13026
+ ] }),
13027
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
13028
+ "button",
13029
+ {
13030
+ type: "button",
13031
+ onClick: () => setRegistrationDialogOpen(true),
13032
+ 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",
13033
+ children: "Add registration"
13034
+ }
13035
+ )
13036
+ ] }),
13037
+ /* @__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) => {
13038
+ var _a;
13039
+ const context = makeContext(reg);
13040
+ const candidates = reg.services;
13041
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
13042
+ "div",
13043
+ {
13044
+ className: "rounded-2xl border border-zinc-200 bg-zinc-50 p-4 dark:border-zinc-800 dark:bg-zinc-950",
13045
+ children: [
13046
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex flex-wrap items-start justify-between gap-3", children: [
13047
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { children: [
13048
+ /* @__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}` }),
13049
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "mt-1 text-xs text-zinc-500 dark:text-zinc-400", children: [
13050
+ "Primary #",
13051
+ String(reg.primary)
13052
+ ] })
13053
+ ] }),
13054
+ /* @__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: [
13055
+ reg.scope,
13056
+ reg.scopeId ? ` \xB7 ${reg.scopeId}` : ""
13057
+ ] })
13058
+ ] }),
13059
+ /* @__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) => {
13060
+ var _a2;
13061
+ const preview = check(context, [
13062
+ candidate
13063
+ ]);
13064
+ const rejected = preview.rejected[0];
13065
+ 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";
13066
+ const service = eligibleServices.find(
13067
+ (s) => String(s.id) === String(candidate)
13068
+ );
13069
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
13070
+ "div",
13071
+ {
13072
+ className: `inline-flex items-center gap-2 rounded-xl border px-3 py-2 text-xs ${tone}`,
13073
+ children: [
13074
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { children: service ? `#${String(service.id)} \xB7 ${(_a2 = service.name) != null ? _a2 : "Unnamed"}` : `#${String(candidate)}` }),
13075
+ 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(
13076
+ ", "
13077
+ ) }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "rounded-full border border-current/20 px-2 py-0.5 text-[10px]", children: "valid" }),
13078
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
13079
+ "button",
13080
+ {
13081
+ type: "button",
13082
+ onClick: () => remove2(
13083
+ context,
13084
+ candidate
13085
+ ),
13086
+ className: "text-current/70 hover:text-current",
13087
+ children: "\xD7"
13088
+ }
13089
+ )
13090
+ ]
13091
+ },
13092
+ String(candidate)
13093
+ );
13094
+ }) }),
13095
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "mt-4 flex gap-2", children: [
13096
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
13097
+ "button",
13098
+ {
13099
+ type: "button",
13100
+ onClick: () => openCandidatePicker(
13101
+ context,
13102
+ reg.primary
13103
+ ),
13104
+ 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",
13105
+ children: "Add fallback"
13106
+ }
13107
+ ),
13108
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
13109
+ "button",
13110
+ {
13111
+ type: "button",
13112
+ onClick: () => clear(context),
13113
+ 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",
13114
+ children: "Clear"
13115
+ }
13116
+ )
13117
+ ] })
13118
+ ]
13119
+ },
13120
+ `${reg.scope}:${String((_a = reg.scopeId) != null ? _a : "global")}:${index}`
13121
+ );
13122
+ }) })
13123
+ ] }),
13124
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
13125
+ FallbackAddRegistrationDialog,
13126
+ {
13127
+ open: registrationDialogOpen,
13128
+ onClose: () => setRegistrationDialogOpen(false),
13129
+ onSelect: (context, primaryId) => {
13130
+ setRegistrationDialogOpen(false);
13131
+ openCandidatePicker(context, primaryId);
13132
+ }
13133
+ }
13134
+ ),
13135
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
13136
+ FallbackAddCandidatesDialog,
13137
+ {
13138
+ open: candidatePickerOpen,
13139
+ onClose: () => setCandidatePickerOpen(false),
13140
+ context: candidateContext,
13141
+ primaryId: candidatePrimaryId
13142
+ }
13143
+ )
13144
+ ] });
13145
+ }
13146
+
13147
+ // src/react/fallback-editor/FallbackAddCandidatesDialog.tsx
13148
+ var import_react25 = __toESM(require("react"), 1);
13149
+ var import_form_palette25 = require("@timeax/form-palette");
13150
+ var import_lucide_react3 = require("lucide-react");
13151
+ var import_jsx_runtime13 = require("react/jsx-runtime");
12753
13152
  function FallbackAddCandidatesDialog({
12754
13153
  open,
12755
13154
  onClose,
@@ -12758,23 +13157,23 @@ function FallbackAddCandidatesDialog({
12758
13157
  }) {
12759
13158
  const { eligible, addMany } = useFallbackEditor();
12760
13159
  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(() => {
13160
+ const [query, setQuery] = import_react25.default.useState("");
13161
+ const [filterEligibleOnly, setFilterEligibleOnly] = import_react25.default.useState(true);
13162
+ const [selected, setSelected] = import_react25.default.useState(/* @__PURE__ */ new Set());
13163
+ const [submitting, setSubmitting] = import_react25.default.useState(false);
13164
+ import_react25.default.useEffect(() => {
12766
13165
  if (!open) {
12767
13166
  setQuery("");
12768
13167
  setFilterEligibleOnly(true);
12769
13168
  setSelected(/* @__PURE__ */ new Set());
12770
13169
  }
12771
13170
  }, [open]);
12772
- const allowedIds = import_react21.default.useMemo(() => {
13171
+ const allowedIds = import_react25.default.useMemo(() => {
12773
13172
  if (!context) return null;
12774
13173
  if (!filterEligibleOnly) return null;
12775
13174
  return new Set(eligible(context).map((id) => String(id)));
12776
13175
  }, [context, filterEligibleOnly, eligible]);
12777
- const items = import_react21.default.useMemo(() => {
13176
+ const items = import_react25.default.useMemo(() => {
12778
13177
  const q = query.trim().toLowerCase();
12779
13178
  return eligibleServices.filter((service) => {
12780
13179
  var _a, _b;
@@ -12809,34 +13208,37 @@ function FallbackAddCandidatesDialog({
12809
13208
  }
12810
13209
  }
12811
13210
  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." })
13211
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/60 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: [
13212
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "border-b border-zinc-200 p-4 dark:border-zinc-800", children: [
13213
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("h3", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "Add fallback services" }),
13214
+ /* @__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
13215
  ] }),
12817
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex flex-col gap-3 p-4", children: [
12818
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
12819
- "input",
13216
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex flex-col gap-3 p-4", children: [
13217
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
13218
+ import_form_palette25.InputField,
12820
13219
  {
13220
+ variant: "text",
12821
13221
  value: query,
12822
- onChange: (e) => setQuery(e.target.value),
13222
+ onChange: ({ value }) => setQuery(String(value != null ? value : "")),
12823
13223
  placeholder: "Search eligible services...",
12824
- 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"
13224
+ leadingControl: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react3.Search, { className: "h-4 w-4 text-zinc-400" }),
13225
+ joinControls: true,
13226
+ extendBoxToControls: true,
13227
+ fullWidth: true
12825
13228
  }
12826
13229
  ),
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)(
12829
- "input",
12830
- {
12831
- type: "checkbox",
12832
- checked: filterEligibleOnly,
12833
- onChange: (e) => setFilterEligibleOnly(e.target.checked),
12834
- className: "h-4 w-4 rounded border-zinc-300"
12835
- }
12836
- ),
12837
- "Filter eligible only"
12838
- ] }),
12839
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
13230
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
13231
+ import_form_palette25.InputField,
13232
+ {
13233
+ variant: "toggle",
13234
+ value: filterEligibleOnly,
13235
+ onChange: ({ value }) => setFilterEligibleOnly(Boolean(value)),
13236
+ label: "Filter eligible only",
13237
+ onText: "On",
13238
+ offText: "Off"
13239
+ }
13240
+ ),
13241
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
12840
13242
  VirtualServiceList,
12841
13243
  {
12842
13244
  items,
@@ -12846,28 +13248,28 @@ function FallbackAddCandidatesDialog({
12846
13248
  }
12847
13249
  )
12848
13250
  ] }),
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: [
13251
+ /* @__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: [
13252
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "text-sm text-zinc-500 dark:text-zinc-400", children: [
12851
13253
  selected.size,
12852
13254
  " selected"
12853
13255
  ] }),
12854
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex gap-2", children: [
12855
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
13256
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex gap-2", children: [
13257
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
12856
13258
  "button",
12857
13259
  {
12858
13260
  type: "button",
12859
13261
  onClick: onClose,
12860
- 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",
13262
+ className: "rounded-xl border border-zinc-300 bg-white px-3 py-2 text-sm font-medium text-zinc-700 transition hover:bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-950 dark:text-zinc-200 dark:hover:bg-zinc-800",
12861
13263
  children: "Cancel"
12862
13264
  }
12863
13265
  ),
12864
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
13266
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
12865
13267
  "button",
12866
13268
  {
12867
13269
  type: "button",
12868
13270
  onClick: handleAdd,
12869
13271
  disabled: selected.size === 0 || submitting,
12870
- className: "rounded-xl bg-blue-600 px-3 py-2 text-sm font-medium text-white hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-60",
13272
+ className: "rounded-xl bg-blue-600 px-3 py-2 text-sm font-medium text-white transition hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-60",
12871
13273
  children: submitting ? "Adding..." : "Add selected"
12872
13274
  }
12873
13275
  )
@@ -12877,8 +13279,9 @@ function FallbackAddCandidatesDialog({
12877
13279
  }
12878
13280
 
12879
13281
  // src/react/fallback-editor/FallbackAddRegistrationDialog.tsx
12880
- var import_react22 = __toESM(require("react"), 1);
12881
- var import_jsx_runtime13 = require("react/jsx-runtime");
13282
+ var import_react26 = __toESM(require("react"), 1);
13283
+ var import_form_palette26 = require("@timeax/form-palette");
13284
+ var import_jsx_runtime14 = require("react/jsx-runtime");
12882
13285
  function FallbackAddRegistrationDialog({
12883
13286
  open,
12884
13287
  onClose,
@@ -12886,23 +13289,23 @@ function FallbackAddRegistrationDialog({
12886
13289
  }) {
12887
13290
  const { activeServiceId, serviceProps, snapshot } = useFallbackEditor();
12888
13291
  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(() => {
13292
+ const [scope, setScope] = import_react26.default.useState("global");
13293
+ const [nodeId, setNodeId] = import_react26.default.useState("");
13294
+ const mode = import_react26.default.useMemo(() => {
12892
13295
  if (snapshot) return "snapshot";
12893
13296
  if (serviceProps) return "props";
12894
13297
  return "none";
12895
13298
  }, [snapshot, serviceProps]);
12896
- import_react22.default.useEffect(() => {
13299
+ import_react26.default.useEffect(() => {
12897
13300
  if (open) {
12898
13301
  setScope("global");
12899
13302
  setNodeId("");
12900
13303
  }
12901
13304
  }, [open]);
12902
- const hasGlobal = import_react22.default.useMemo(() => {
13305
+ const hasGlobal = import_react26.default.useMemo(() => {
12903
13306
  return registrations.some((r) => r.scope === "global");
12904
13307
  }, [registrations]);
12905
- const nodeTargets = import_react22.default.useMemo(() => {
13308
+ const nodeTargets = import_react26.default.useMemo(() => {
12906
13309
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
12907
13310
  if (activeServiceId === void 0 || activeServiceId === null) {
12908
13311
  return [];
@@ -12992,12 +13395,12 @@ function FallbackAddRegistrationDialog({
12992
13395
  }
12993
13396
  return [];
12994
13397
  }, [mode, snapshot, serviceProps, activeServiceId]);
12995
- import_react22.default.useEffect(() => {
13398
+ import_react26.default.useEffect(() => {
12996
13399
  if (hasGlobal && scope === "global") {
12997
13400
  setScope("node");
12998
13401
  }
12999
13402
  }, [hasGlobal, scope]);
13000
- import_react22.default.useEffect(() => {
13403
+ import_react26.default.useEffect(() => {
13001
13404
  if (scope === "node" && nodeId) {
13002
13405
  const exists = nodeTargets.some((node) => node.id === nodeId);
13003
13406
  if (!exists) setNodeId("");
@@ -13028,100 +13431,74 @@ function FallbackAddRegistrationDialog({
13028
13431
  }
13029
13432
  if (!open) return null;
13030
13433
  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." })
13434
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/60 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: [
13435
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "border-b border-zinc-200 p-4 dark:border-zinc-800", children: [
13436
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("h3", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "Add registration" }),
13437
+ /* @__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
13438
  ] }),
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)(
13042
- "input",
13439
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "space-y-4 p-4", children: [
13440
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "space-y-2", children: [
13441
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "text-sm font-medium text-zinc-900 dark:text-zinc-100", children: "Scope" }),
13442
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
13443
+ import_form_palette26.InputField,
13444
+ {
13445
+ variant: "radio",
13446
+ value: scope,
13447
+ onChange: ({ value }) => setScope(value),
13448
+ options: [
13449
+ ...!hasGlobal ? [
13450
+ {
13451
+ value: "global",
13452
+ label: "Global"
13453
+ }
13454
+ ] : [],
13043
13455
  {
13044
- type: "radio",
13045
- name: "scope",
13046
- checked: scope === "global",
13047
- onChange: () => setScope("global"),
13048
- className: "mt-1 h-4 w-4"
13456
+ value: "node",
13457
+ label: nodeScopeDisabled ? "Node (Unavailable)" : "Node"
13049
13458
  }
13050
- ),
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." })
13054
- ] })
13055
- ] }),
13056
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
13057
- "label",
13058
- {
13059
- className: [
13060
- "flex items-start gap-3 rounded-xl border p-3",
13061
- nodeScopeDisabled ? "cursor-not-allowed border-zinc-200 opacity-60 dark:border-zinc-800" : "cursor-pointer border-zinc-200 dark:border-zinc-800"
13062
- ].join(" "),
13063
- children: [
13064
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
13065
- "input",
13066
- {
13067
- type: "radio",
13068
- name: "scope",
13069
- checked: scope === "node",
13070
- onChange: () => setScope("node"),
13071
- disabled: nodeScopeDisabled,
13072
- className: "mt-1 h-4 w-4"
13073
- }
13074
- ),
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." })
13078
- ] })
13079
- ]
13080
- }
13081
- )
13082
- ] })
13459
+ ]
13460
+ }
13461
+ ),
13462
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "text-xs text-zinc-500 dark:text-zinc-400", children: scope === "global" ? "Use one global registration for this primary service." : 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." })
13083
13463
  ] }),
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)(
13087
- "select",
13464
+ scope === "node" && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "space-y-2", children: [
13465
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "text-sm font-medium text-zinc-900 dark:text-zinc-100", children: "Node id" }),
13466
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
13467
+ import_form_palette26.InputField,
13088
13468
  {
13089
- value: nodeId,
13090
- onChange: (e) => setNodeId(e.target.value),
13091
- 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
- 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: [
13095
- "[",
13096
- node.kind,
13097
- "] ",
13098
- node.label,
13099
- " \xB7 #",
13100
- String(node.serviceId)
13101
- ] }, node.id))
13102
- ]
13469
+ variant: "select",
13470
+ value: nodeId || void 0,
13471
+ onChange: ({ value }) => setNodeId(String(value != null ? value : "")),
13472
+ options: nodeTargets.map((node) => ({
13473
+ value: node.id,
13474
+ label: `[${node.kind}] ${node.label} \xB7 #${String(node.serviceId)}`
13475
+ })),
13476
+ placeholder: "Select node...",
13477
+ searchable: true,
13478
+ clearable: false,
13479
+ fullWidth: true
13103
13480
  }
13104
13481
  ),
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
13482
+ 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
13483
  ] })
13107
13484
  ] }),
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)(
13485
+ /* @__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: [
13486
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
13110
13487
  "button",
13111
13488
  {
13112
13489
  type: "button",
13113
13490
  onClick: onClose,
13114
- 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",
13491
+ className: "rounded-xl border border-zinc-300 bg-white px-3 py-2 text-sm font-medium text-zinc-700 transition hover:bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-950 dark:text-zinc-200 dark:hover:bg-zinc-800",
13115
13492
  children: "Cancel"
13116
13493
  }
13117
13494
  ),
13118
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
13495
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
13119
13496
  "button",
13120
13497
  {
13121
13498
  type: "button",
13122
13499
  onClick: handleContinue,
13123
13500
  disabled: activeServiceId === void 0 || scope === "node" && !nodeId,
13124
- className: "rounded-xl bg-blue-600 px-3 py-2 text-sm font-medium text-white hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-60",
13501
+ className: "rounded-xl bg-blue-600 px-3 py-2 text-sm font-medium text-white transition hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-60",
13125
13502
  children: "Continue"
13126
13503
  }
13127
13504
  )
@@ -13158,169 +13535,6 @@ function resolveNodeMeta(props, nodeId) {
13158
13535
  }
13159
13536
  return { kind: "node", label: nodeId };
13160
13537
  }
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
13538
  // Annotate the CommonJS export names for ESM import in node:
13325
13539
  0 && (module.exports = {
13326
13540
  CanvasAPI,