@timeax/digital-service-engine 0.0.4 → 0.0.6

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.
@@ -32,18 +32,33 @@ var react_exports = {};
32
32
  __export(react_exports, {
33
33
  CanvasAPI: () => CanvasAPI,
34
34
  EventBus: () => EventBus,
35
+ FallbackAddCandidatesDialog: () => FallbackAddCandidatesDialog,
36
+ FallbackAddRegistrationDialog: () => FallbackAddRegistrationDialog,
37
+ FallbackDetailsPanel: () => FallbackDetailsPanel,
38
+ FallbackEditor: () => FallbackEditor,
39
+ FallbackEditorHeader: () => FallbackEditorHeader,
40
+ FallbackEditorProvider: () => FallbackEditorProvider,
41
+ FallbackRegistrationsPanel: () => FallbackRegistrationsPanel,
42
+ FallbackServiceSidebar: () => FallbackServiceSidebar,
43
+ FallbackSettingsPanel: () => FallbackSettingsPanel,
35
44
  FormProvider: () => FormProvider,
36
45
  OrderFlowProvider: () => OrderFlowProvider,
37
46
  Provider: () => Provider,
47
+ VirtualServiceList: () => VirtualServiceList,
38
48
  Wrapper: () => Wrapper,
39
49
  createInputRegistry: () => createInputRegistry,
40
50
  registerEntries: () => registerEntries,
41
51
  resolveInputDescriptor: () => resolveInputDescriptor,
52
+ useActiveFallbackRegistrations: () => useActiveFallbackRegistrations,
53
+ useEligibleServiceList: () => useEligibleServiceList,
54
+ useFallbackEditor: () => useFallbackEditor,
55
+ useFallbackEditorContext: () => useFallbackEditorContext,
42
56
  useFormApi: () => useFormApi,
43
57
  useInputs: () => useInputs,
44
58
  useOptionalFormApi: () => useOptionalFormApi,
45
59
  useOrderFlow: () => useOrderFlow,
46
- useOrderFlowContext: () => useOrderFlowContext
60
+ useOrderFlowContext: () => useOrderFlowContext,
61
+ usePrimaryServiceList: () => usePrimaryServiceList
47
62
  });
48
63
  module.exports = __toCommonJS(react_exports);
49
64
 
@@ -3173,6 +3188,58 @@ function bindIdsToArray(bind) {
3173
3188
  if (!bind) return [];
3174
3189
  return Array.isArray(bind) ? bind.slice() : [bind];
3175
3190
  }
3191
+ function getEligibleFallbacks(params) {
3192
+ var _a, _b, _c, _d, _e, _f;
3193
+ const s = { ...DEFAULT_SETTINGS, ...(_a = params.settings) != null ? _a : {} };
3194
+ const { primary, nodeId, tagId, services } = params;
3195
+ const fb = (_b = params.fallbacks) != null ? _b : {};
3196
+ const excludes = new Set(((_c = params.exclude) != null ? _c : []).map(String));
3197
+ excludes.add(String(primary));
3198
+ const unique = (_d = params.unique) != null ? _d : true;
3199
+ const lists = [];
3200
+ if (nodeId && ((_e = fb.nodes) == null ? void 0 : _e[nodeId])) lists.push(fb.nodes[nodeId]);
3201
+ if ((_f = fb.global) == null ? void 0 : _f[primary]) lists.push(fb.global[primary]);
3202
+ if (!lists.length) return [];
3203
+ const primaryRate = rateOf(services, primary);
3204
+ const seen = /* @__PURE__ */ new Set();
3205
+ const eligible = [];
3206
+ for (const list of lists) {
3207
+ for (const cand of list) {
3208
+ const key = String(cand);
3209
+ if (excludes.has(key)) continue;
3210
+ if (unique && seen.has(key)) continue;
3211
+ seen.add(key);
3212
+ const cap = getCap(services, cand);
3213
+ if (!cap) continue;
3214
+ if (!passesRate(s.ratePolicy, primaryRate, cap.rate)) continue;
3215
+ if (s.requireConstraintFit && tagId) {
3216
+ const ok = satisfiesTagConstraints(
3217
+ tagId,
3218
+ { props: params.props, services },
3219
+ cap
3220
+ );
3221
+ if (!ok) continue;
3222
+ }
3223
+ eligible.push(cand);
3224
+ }
3225
+ }
3226
+ if (s.selectionStrategy === "cheapest") {
3227
+ eligible.sort((a, b) => {
3228
+ var _a2, _b2;
3229
+ const ra = (_a2 = rateOf(services, a)) != null ? _a2 : Infinity;
3230
+ const rb = (_b2 = rateOf(services, b)) != null ? _b2 : Infinity;
3231
+ return ra - rb;
3232
+ });
3233
+ }
3234
+ if (typeof params.limit === "number" && params.limit >= 0) {
3235
+ return eligible.slice(0, params.limit);
3236
+ }
3237
+ return eligible;
3238
+ }
3239
+ function getFallbackRegistrationInfo(props, nodeId) {
3240
+ const { primary, tagContexts } = primaryForNode(props, nodeId);
3241
+ return { primary, tagContexts };
3242
+ }
3176
3243
 
3177
3244
  // src/utils/prune-fallbacks.ts
3178
3245
  function pruneInvalidNodeFallbacks(props, services, settings) {
@@ -3861,6 +3928,347 @@ function resolveMinMax(servicesList, services) {
3861
3928
  return { min: min != null ? min : 1, ...max !== void 0 ? { max } : {} };
3862
3929
  }
3863
3930
 
3931
+ // src/core/fallback-editor.ts
3932
+ function createFallbackEditor(options = {}) {
3933
+ var _a, _b;
3934
+ const original = cloneFallbacks(options.fallbacks);
3935
+ let current = cloneFallbacks(options.fallbacks);
3936
+ const props = options.props;
3937
+ const services = (_a = options.services) != null ? _a : {};
3938
+ const settings = (_b = options.settings) != null ? _b : {};
3939
+ function state() {
3940
+ return {
3941
+ original: cloneFallbacks(original),
3942
+ current: cloneFallbacks(current),
3943
+ changed: !sameFallbacks(original, current)
3944
+ };
3945
+ }
3946
+ function value() {
3947
+ return cloneFallbacks(current);
3948
+ }
3949
+ function reset() {
3950
+ current = cloneFallbacks(original);
3951
+ return state();
3952
+ }
3953
+ function get(serviceId) {
3954
+ var _a2, _b2;
3955
+ const out = [];
3956
+ for (const [primary, list] of Object.entries((_a2 = current.global) != null ? _a2 : {})) {
3957
+ if (String(primary) !== String(serviceId)) continue;
3958
+ out.push({
3959
+ scope: "global",
3960
+ primary,
3961
+ services: [...list != null ? list : []]
3962
+ });
3963
+ }
3964
+ if (!props) return out;
3965
+ for (const [nodeId, list] of Object.entries((_b2 = current.nodes) != null ? _b2 : {})) {
3966
+ const info = getFallbackRegistrationInfo(props, nodeId);
3967
+ if (String(info.primary) !== String(serviceId)) continue;
3968
+ out.push({
3969
+ scope: "node",
3970
+ scopeId: nodeId,
3971
+ primary: info.primary,
3972
+ services: [...list != null ? list : []]
3973
+ });
3974
+ }
3975
+ return out;
3976
+ }
3977
+ function getScope(context) {
3978
+ var _a2, _b2, _c, _d;
3979
+ if (context.scope === "global") {
3980
+ return [...(_b2 = (_a2 = current.global) == null ? void 0 : _a2[context.primary]) != null ? _b2 : []];
3981
+ }
3982
+ return [...(_d = (_c = current.nodes) == null ? void 0 : _c[context.nodeId]) != null ? _d : []];
3983
+ }
3984
+ function check(context, candidates) {
3985
+ var _a2, _b2;
3986
+ const normalized = normalizeCandidateList(
3987
+ candidates != null ? candidates : getScope(context),
3988
+ true
3989
+ );
3990
+ if (context.scope === "node" && !props) {
3991
+ return {
3992
+ context,
3993
+ allowed: [],
3994
+ rejected: normalized.map((candidate) => ({
3995
+ candidate,
3996
+ ok: false,
3997
+ reasons: ["missing_service_props"]
3998
+ })),
3999
+ warnings: ["missing_service_props"]
4000
+ };
4001
+ }
4002
+ const tempFallbacks = cloneFallbacks(current);
4003
+ if (context.scope === "global") {
4004
+ (_a2 = tempFallbacks.global) != null ? _a2 : tempFallbacks.global = {};
4005
+ if (normalized.length)
4006
+ tempFallbacks.global[context.primary] = normalized;
4007
+ else delete tempFallbacks.global[context.primary];
4008
+ } else {
4009
+ (_b2 = tempFallbacks.nodes) != null ? _b2 : tempFallbacks.nodes = {};
4010
+ if (normalized.length)
4011
+ tempFallbacks.nodes[context.nodeId] = normalized;
4012
+ else delete tempFallbacks.nodes[context.nodeId];
4013
+ }
4014
+ if (!props) {
4015
+ if (context.scope !== "global") {
4016
+ return {
4017
+ context,
4018
+ allowed: [],
4019
+ rejected: normalized.map((candidate) => ({
4020
+ candidate,
4021
+ ok: false,
4022
+ reasons: ["missing_service_props"]
4023
+ })),
4024
+ warnings: ["missing_service_props"]
4025
+ };
4026
+ }
4027
+ const rejected2 = [];
4028
+ const allowed2 = [];
4029
+ for (const candidate of normalized) {
4030
+ const reasons = [];
4031
+ if (String(candidate) === String(context.primary)) {
4032
+ reasons.push("self_reference");
4033
+ }
4034
+ if (reasons.length) {
4035
+ rejected2.push({ candidate, ok: false, reasons });
4036
+ } else {
4037
+ allowed2.push(candidate);
4038
+ }
4039
+ }
4040
+ return {
4041
+ context,
4042
+ primary: context.primary,
4043
+ allowed: allowed2,
4044
+ rejected: rejected2,
4045
+ warnings: []
4046
+ };
4047
+ }
4048
+ const fakeProps = {
4049
+ ...props,
4050
+ fallbacks: tempFallbacks
4051
+ };
4052
+ const diags = collectFailedFallbacks(fakeProps, services, {
4053
+ ...settings,
4054
+ mode: "dev"
4055
+ });
4056
+ const scoped = diags.filter((d) => {
4057
+ if (context.scope === "global") {
4058
+ return d.scope === "global" && String(d.primary) === String(context.primary);
4059
+ }
4060
+ return d.scope === "node" && String(d.nodeId) === String(context.nodeId);
4061
+ });
4062
+ const rejected = normalized.map((candidate) => {
4063
+ const reasons = scoped.filter((d) => String(d.candidate) === String(candidate)).map((d) => mapDiagReason(d.reason));
4064
+ return {
4065
+ candidate,
4066
+ ok: reasons.length === 0,
4067
+ reasons
4068
+ };
4069
+ }).filter((row) => !row.ok);
4070
+ const allowed = normalized.filter(
4071
+ (candidate) => !rejected.some(
4072
+ (r) => String(r.candidate) === String(candidate)
4073
+ )
4074
+ );
4075
+ const info = context.scope === "global" ? { ok: true, primary: context.primary } : getNodeRegistrationInfo(props, context.nodeId);
4076
+ const primary = (info == null ? void 0 : info.ok) ? info.primary : void 0;
4077
+ return {
4078
+ context,
4079
+ primary,
4080
+ allowed,
4081
+ rejected,
4082
+ warnings: []
4083
+ };
4084
+ }
4085
+ function add(context, candidate, options2) {
4086
+ return addMany(context, [candidate], options2);
4087
+ }
4088
+ function addMany(context, candidates, options2) {
4089
+ const existing = getScope(context);
4090
+ const merged = [...existing];
4091
+ const insertAt = typeof (options2 == null ? void 0 : options2.index) === "number" ? clamp(options2.index, 0, merged.length) : void 0;
4092
+ const incoming = normalizeCandidateList(candidates, true).filter(
4093
+ (id) => !merged.some((x) => String(x) === String(id))
4094
+ );
4095
+ if (insertAt === void 0) {
4096
+ merged.push(...incoming);
4097
+ } else {
4098
+ merged.splice(insertAt, 0, ...incoming);
4099
+ }
4100
+ return replace(context, merged, options2);
4101
+ }
4102
+ function remove(context, candidate) {
4103
+ const next = getScope(context).filter(
4104
+ (id) => String(id) !== String(candidate)
4105
+ );
4106
+ return writeScope(context, next);
4107
+ }
4108
+ function replace(context, candidates, options2) {
4109
+ const strict = !!(options2 == null ? void 0 : options2.strict);
4110
+ const normalized = normalizeCandidateList(candidates, true);
4111
+ const checked = check(context, normalized);
4112
+ const next = strict ? checked.allowed : normalized;
4113
+ return writeScope(context, next);
4114
+ }
4115
+ function clear(context) {
4116
+ return writeScope(context, []);
4117
+ }
4118
+ function eligible(context, opt) {
4119
+ if (!props) return [];
4120
+ if (context.scope === "global") {
4121
+ return getEligibleFallbacks({
4122
+ primary: context.primary,
4123
+ services,
4124
+ fallbacks: current,
4125
+ settings,
4126
+ props,
4127
+ exclude: opt == null ? void 0 : opt.exclude,
4128
+ unique: opt == null ? void 0 : opt.unique,
4129
+ limit: opt == null ? void 0 : opt.limit
4130
+ });
4131
+ }
4132
+ const info = getFallbackRegistrationInfo(props, context.nodeId);
4133
+ if (!info.primary) return [];
4134
+ return getEligibleFallbacks({
4135
+ primary: info.primary,
4136
+ nodeId: context.nodeId,
4137
+ tagId: info.tagContexts[0],
4138
+ services,
4139
+ fallbacks: current,
4140
+ settings,
4141
+ props,
4142
+ exclude: opt == null ? void 0 : opt.exclude,
4143
+ unique: opt == null ? void 0 : opt.unique,
4144
+ limit: opt == null ? void 0 : opt.limit
4145
+ });
4146
+ }
4147
+ function writeScope(context, nextList) {
4148
+ var _a2, _b2;
4149
+ const next = cloneFallbacks(current);
4150
+ if (context.scope === "global") {
4151
+ (_a2 = next.global) != null ? _a2 : next.global = {};
4152
+ if (nextList.length) {
4153
+ next.global[context.primary] = [...nextList];
4154
+ } else {
4155
+ delete next.global[context.primary];
4156
+ if (!Object.keys(next.global).length) delete next.global;
4157
+ }
4158
+ } else {
4159
+ (_b2 = next.nodes) != null ? _b2 : next.nodes = {};
4160
+ if (nextList.length) {
4161
+ next.nodes[context.nodeId] = [...nextList];
4162
+ } else {
4163
+ delete next.nodes[context.nodeId];
4164
+ if (!Object.keys(next.nodes).length) delete next.nodes;
4165
+ }
4166
+ }
4167
+ current = next;
4168
+ return state();
4169
+ }
4170
+ return {
4171
+ state,
4172
+ value,
4173
+ reset,
4174
+ get,
4175
+ getScope,
4176
+ check,
4177
+ add,
4178
+ addMany,
4179
+ remove,
4180
+ replace,
4181
+ clear,
4182
+ eligible
4183
+ };
4184
+ }
4185
+ function cloneFallbacks(input) {
4186
+ return {
4187
+ ...(input == null ? void 0 : input.nodes) ? { nodes: cloneRecordArray(input.nodes) } : {},
4188
+ ...(input == null ? void 0 : input.global) ? { global: cloneRecordArray(input.global) } : {}
4189
+ };
4190
+ }
4191
+ function cloneRecordArray(input) {
4192
+ const out = {};
4193
+ for (const [k, v] of Object.entries(input)) out[k] = [...v != null ? v : []];
4194
+ return out;
4195
+ }
4196
+ function sameFallbacks(a, b) {
4197
+ return JSON.stringify(a != null ? a : {}) === JSON.stringify(b != null ? b : {});
4198
+ }
4199
+ function normalizeCandidateList(input, preserveOrder) {
4200
+ const out = [];
4201
+ for (const item of input != null ? input : []) {
4202
+ if (!isValidServiceIdRef(item)) continue;
4203
+ const exists = out.some((x) => String(x) === String(item));
4204
+ if (exists) continue;
4205
+ out.push(item);
4206
+ }
4207
+ return preserveOrder ? out : out;
4208
+ }
4209
+ function isValidServiceIdRef(value) {
4210
+ return typeof value === "number" && Number.isFinite(value) || typeof value === "string" && value.trim().length > 0;
4211
+ }
4212
+ function clamp(n, min, max) {
4213
+ return Math.max(min, Math.min(max, n));
4214
+ }
4215
+ function getNodeRegistrationInfo(props, nodeId) {
4216
+ const tag = props.filters.find((t) => t.id === nodeId);
4217
+ if (tag) {
4218
+ if (!isValidServiceIdRef(tag.service_id)) {
4219
+ return { ok: false, reasons: ["no_primary"] };
4220
+ }
4221
+ return {
4222
+ ok: true,
4223
+ primary: tag.service_id,
4224
+ tagContexts: [tag.id]
4225
+ };
4226
+ }
4227
+ const hit = findOptionOwner(props.fields, nodeId);
4228
+ if (!hit) {
4229
+ return { ok: false, reasons: ["node_not_found"] };
4230
+ }
4231
+ if (!isValidServiceIdRef(hit.option.service_id)) {
4232
+ return { ok: false, reasons: ["no_primary"] };
4233
+ }
4234
+ return {
4235
+ ok: true,
4236
+ primary: hit.option.service_id,
4237
+ tagContexts: bindIdsToArray2(hit.field.bind_id)
4238
+ };
4239
+ }
4240
+ function findOptionOwner(fields, optionId) {
4241
+ var _a;
4242
+ for (const field of fields) {
4243
+ for (const option of (_a = field.options) != null ? _a : []) {
4244
+ if (option.id === optionId) return { field, option };
4245
+ }
4246
+ }
4247
+ return null;
4248
+ }
4249
+ function bindIdsToArray2(v) {
4250
+ if (Array.isArray(v)) return v.filter(Boolean);
4251
+ return v ? [v] : [];
4252
+ }
4253
+ function mapDiagReason(reason) {
4254
+ switch (String(reason)) {
4255
+ case "unknown_service":
4256
+ return "unknown_service";
4257
+ case "no_primary":
4258
+ return "no_primary";
4259
+ case "rate_violation":
4260
+ return "rate_violation";
4261
+ case "constraint_mismatch":
4262
+ return "constraint_mismatch";
4263
+ case "cycle":
4264
+ return "cycle";
4265
+ case "no_tag_context":
4266
+ return "no_tag_context";
4267
+ default:
4268
+ return "node_not_found";
4269
+ }
4270
+ }
4271
+
3864
4272
  // src/core/policy.ts
3865
4273
  var ALLOWED_SCOPES = /* @__PURE__ */ new Set([
3866
4274
  "global",
@@ -9855,21 +10263,1525 @@ function registerEntries(registry) {
9855
10263
  })
9856
10264
  );
9857
10265
  }
10266
+
10267
+ // src/react/fallback-editor/useFallbackEditor.ts
10268
+ var import_react10 = __toESM(require("react"), 1);
10269
+
10270
+ // src/react/fallback-editor/FallbackEditorProvider.tsx
10271
+ var import_react9 = __toESM(require("react"), 1);
10272
+ var import_jsx_runtime5 = require("react/jsx-runtime");
10273
+ var FallbackEditorContext = import_react9.default.createContext(null);
10274
+ function FallbackEditorProvider({
10275
+ children,
10276
+ fallbacks,
10277
+ props,
10278
+ snapshot,
10279
+ primaryServices,
10280
+ eligibleServices,
10281
+ settings: initialSettings,
10282
+ initialServiceId,
10283
+ initialTab = "registrations",
10284
+ onSettingsChange,
10285
+ onSave,
10286
+ onValidate,
10287
+ onReset
10288
+ }) {
10289
+ const [settings, setSettings] = import_react9.default.useState(
10290
+ initialSettings != null ? initialSettings : {}
10291
+ );
10292
+ const [settingsSaving, setSettingsSaving] = import_react9.default.useState(false);
10293
+ const [headerSaving, setHeaderSaving] = import_react9.default.useState(false);
10294
+ const [headerValidating, setHeaderValidating] = import_react9.default.useState(false);
10295
+ const [headerResetting, setHeaderResetting] = import_react9.default.useState(false);
10296
+ const [version, setVersion] = import_react9.default.useState(0);
10297
+ const [activeServiceId, setActiveServiceId] = import_react9.default.useState(initialServiceId);
10298
+ const [activeTab, setActiveTab] = import_react9.default.useState(initialTab);
10299
+ import_react9.default.useEffect(() => {
10300
+ setSettings(initialSettings != null ? initialSettings : {});
10301
+ }, [initialSettings]);
10302
+ const resolvedPrimaryServices = import_react9.default.useMemo(
10303
+ () => primaryServices != null ? primaryServices : {},
10304
+ [primaryServices]
10305
+ );
10306
+ const resolvedEligibleServices = import_react9.default.useMemo(
10307
+ () => {
10308
+ var _a;
10309
+ return (_a = eligibleServices != null ? eligibleServices : primaryServices) != null ? _a : {};
10310
+ },
10311
+ [eligibleServices, primaryServices]
10312
+ );
10313
+ const editorRef = import_react9.default.useRef(null);
10314
+ const buildEditor = import_react9.default.useCallback(
10315
+ (next) => {
10316
+ var _a, _b, _c, _d, _e, _f, _g, _h;
10317
+ const currentValue = (_a = editorRef.current) == null ? void 0 : _a.value();
10318
+ editorRef.current = createFallbackEditor({
10319
+ fallbacks: (_d = (_c = (_b = next == null ? void 0 : next.fallbacks) != null ? _b : currentValue) != null ? _c : fallbacks) != null ? _d : {},
10320
+ props: (_e = next == null ? void 0 : next.props) != null ? _e : props,
10321
+ snapshot: (_f = next == null ? void 0 : next.snapshot) != null ? _f : snapshot,
10322
+ services: (_g = next == null ? void 0 : next.services) != null ? _g : resolvedEligibleServices,
10323
+ settings: (_h = next == null ? void 0 : next.settings) != null ? _h : settings
10324
+ });
10325
+ setVersion((v) => v + 1);
10326
+ },
10327
+ [fallbacks, props, snapshot, resolvedEligibleServices, settings]
10328
+ );
10329
+ if (!editorRef.current) {
10330
+ editorRef.current = createFallbackEditor({
10331
+ fallbacks: fallbacks != null ? fallbacks : {},
10332
+ props,
10333
+ snapshot,
10334
+ services: resolvedEligibleServices,
10335
+ settings
10336
+ });
10337
+ }
10338
+ import_react9.default.useEffect(() => {
10339
+ buildEditor({
10340
+ fallbacks: fallbacks != null ? fallbacks : {},
10341
+ props,
10342
+ snapshot,
10343
+ services: resolvedEligibleServices,
10344
+ settings
10345
+ });
10346
+ }, [fallbacks, props, snapshot, resolvedEligibleServices, buildEditor]);
10347
+ const editor = editorRef.current;
10348
+ const bump = import_react9.default.useCallback(() => {
10349
+ setVersion((v) => v + 1);
10350
+ }, []);
10351
+ const syncAfterMutation = import_react9.default.useCallback(() => {
10352
+ bump();
10353
+ }, [bump]);
10354
+ const reset = import_react9.default.useCallback(() => {
10355
+ editor.reset();
10356
+ syncAfterMutation();
10357
+ }, [editor, syncAfterMutation]);
10358
+ const add = import_react9.default.useCallback(
10359
+ (context, candidate, options) => {
10360
+ const next = editor.add(context, candidate, options);
10361
+ syncAfterMutation();
10362
+ return next;
10363
+ },
10364
+ [editor, syncAfterMutation]
10365
+ );
10366
+ const addMany = import_react9.default.useCallback(
10367
+ (context, candidates, options) => {
10368
+ const next = editor.addMany(context, candidates, options);
10369
+ syncAfterMutation();
10370
+ return next;
10371
+ },
10372
+ [editor, syncAfterMutation]
10373
+ );
10374
+ const remove = import_react9.default.useCallback(
10375
+ (context, candidate) => {
10376
+ const next = editor.remove(context, candidate);
10377
+ syncAfterMutation();
10378
+ return next;
10379
+ },
10380
+ [editor, syncAfterMutation]
10381
+ );
10382
+ const replace = import_react9.default.useCallback(
10383
+ (context, candidates, options) => {
10384
+ const next = editor.replace(context, candidates, options);
10385
+ syncAfterMutation();
10386
+ return next;
10387
+ },
10388
+ [editor, syncAfterMutation]
10389
+ );
10390
+ const clear = import_react9.default.useCallback(
10391
+ (context) => {
10392
+ const next = editor.clear(context);
10393
+ syncAfterMutation();
10394
+ return next;
10395
+ },
10396
+ [editor, syncAfterMutation]
10397
+ );
10398
+ const saveSettings = import_react9.default.useCallback(
10399
+ async (next) => {
10400
+ setSettingsSaving(true);
10401
+ try {
10402
+ const resolved = onSettingsChange ? await onSettingsChange(next) : next;
10403
+ const finalSettings = resolved != null ? resolved : next;
10404
+ setSettings(finalSettings);
10405
+ buildEditor({
10406
+ settings: finalSettings,
10407
+ fallbacks: editor.value()
10408
+ });
10409
+ return finalSettings;
10410
+ } finally {
10411
+ setSettingsSaving(false);
10412
+ }
10413
+ },
10414
+ [onSettingsChange, buildEditor, editor]
10415
+ );
10416
+ const saveFallbacks = import_react9.default.useCallback(async () => {
10417
+ const next = editor.value();
10418
+ setHeaderSaving(true);
10419
+ try {
10420
+ const resolved = onSave ? await onSave(next) : next;
10421
+ if (resolved) {
10422
+ buildEditor({ fallbacks: resolved });
10423
+ }
10424
+ return resolved;
10425
+ } finally {
10426
+ setHeaderSaving(false);
10427
+ }
10428
+ }, [editor, onSave, buildEditor]);
10429
+ const validateFallbacks2 = import_react9.default.useCallback(async () => {
10430
+ const next = editor.value();
10431
+ setHeaderValidating(true);
10432
+ try {
10433
+ if (onValidate) {
10434
+ await onValidate(next);
10435
+ }
10436
+ } finally {
10437
+ setHeaderValidating(false);
10438
+ }
10439
+ }, [editor, onValidate]);
10440
+ const resetEditor = import_react9.default.useCallback(async () => {
10441
+ setHeaderResetting(true);
10442
+ try {
10443
+ editor.reset();
10444
+ syncAfterMutation();
10445
+ if (onReset) {
10446
+ await onReset();
10447
+ }
10448
+ } finally {
10449
+ setHeaderResetting(false);
10450
+ }
10451
+ }, [editor, syncAfterMutation, onReset]);
10452
+ const value = import_react9.default.useMemo(() => editor.value(), [editor, version]);
10453
+ const state = import_react9.default.useMemo(() => editor.state(), [editor, version]);
10454
+ const ctx = import_react9.default.useMemo(
10455
+ () => ({
10456
+ editor,
10457
+ version,
10458
+ serviceProps: props,
10459
+ snapshot,
10460
+ primaryServices: resolvedPrimaryServices,
10461
+ eligibleServices: resolvedEligibleServices,
10462
+ activeServiceId,
10463
+ setActiveServiceId,
10464
+ activeTab,
10465
+ setActiveTab,
10466
+ state,
10467
+ value,
10468
+ settings,
10469
+ settingsSaving,
10470
+ headerSaving,
10471
+ headerValidating,
10472
+ headerResetting,
10473
+ saveSettings,
10474
+ saveFallbacks,
10475
+ validateFallbacks: validateFallbacks2,
10476
+ resetEditor,
10477
+ reset,
10478
+ get: editor.get,
10479
+ getScope: editor.getScope,
10480
+ check: editor.check,
10481
+ eligible: editor.eligible,
10482
+ add,
10483
+ addMany,
10484
+ remove,
10485
+ replace,
10486
+ clear
10487
+ }),
10488
+ [
10489
+ editor,
10490
+ version,
10491
+ props,
10492
+ snapshot,
10493
+ resolvedPrimaryServices,
10494
+ resolvedEligibleServices,
10495
+ activeServiceId,
10496
+ activeTab,
10497
+ state,
10498
+ value,
10499
+ settings,
10500
+ settingsSaving,
10501
+ headerSaving,
10502
+ headerValidating,
10503
+ headerResetting,
10504
+ saveSettings,
10505
+ saveFallbacks,
10506
+ validateFallbacks2,
10507
+ resetEditor,
10508
+ reset,
10509
+ add,
10510
+ addMany,
10511
+ remove,
10512
+ replace,
10513
+ clear
10514
+ ]
10515
+ );
10516
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(FallbackEditorContext.Provider, { value: ctx, children });
10517
+ }
10518
+ function useFallbackEditorContext() {
10519
+ const ctx = import_react9.default.useContext(FallbackEditorContext);
10520
+ if (!ctx) {
10521
+ throw new Error(
10522
+ "useFallbackEditorContext must be used inside FallbackEditorProvider"
10523
+ );
10524
+ }
10525
+ return ctx;
10526
+ }
10527
+
10528
+ // src/react/fallback-editor/useFallbackEditor.ts
10529
+ function useFallbackEditor() {
10530
+ return useFallbackEditorContext();
10531
+ }
10532
+ function useActiveFallbackRegistrations() {
10533
+ const { activeServiceId, get, version } = useFallbackEditorContext();
10534
+ return import_react10.default.useMemo(() => {
10535
+ if (activeServiceId === void 0 || activeServiceId === null)
10536
+ return [];
10537
+ return get(activeServiceId);
10538
+ }, [activeServiceId, get, version]);
10539
+ }
10540
+ function usePrimaryServiceList() {
10541
+ const { primaryServices, version } = useFallbackEditorContext();
10542
+ return import_react10.default.useMemo(() => {
10543
+ return Object.values(primaryServices != null ? primaryServices : {});
10544
+ }, [primaryServices, version]);
10545
+ }
10546
+ function useEligibleServiceList() {
10547
+ const { eligibleServices, version } = useFallbackEditorContext();
10548
+ return import_react10.default.useMemo(() => {
10549
+ return Object.values(eligibleServices != null ? eligibleServices : {});
10550
+ }, [eligibleServices, version]);
10551
+ }
10552
+
10553
+ // src/react/fallback-editor/FallbackEditor.tsx
10554
+ var import_jsx_runtime6 = require("react/jsx-runtime");
10555
+ function FallbackEditor({
10556
+ className,
10557
+ fallbacks,
10558
+ props,
10559
+ snapshot,
10560
+ primaryServices,
10561
+ eligibleServices,
10562
+ settings,
10563
+ initialServiceId,
10564
+ onSettingsChange,
10565
+ onSave,
10566
+ onValidate,
10567
+ onReset
10568
+ }) {
10569
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
10570
+ FallbackEditorProvider,
10571
+ {
10572
+ fallbacks,
10573
+ props,
10574
+ snapshot,
10575
+ primaryServices,
10576
+ eligibleServices,
10577
+ settings,
10578
+ initialServiceId,
10579
+ onSettingsChange,
10580
+ onSave,
10581
+ onValidate,
10582
+ onReset,
10583
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(FallbackEditorInner, { className })
10584
+ }
10585
+ );
10586
+ }
10587
+ function FallbackEditorInner({ className }) {
10588
+ const {
10589
+ activeTab,
10590
+ setActiveTab,
10591
+ activeServiceId,
10592
+ saveFallbacks,
10593
+ validateFallbacks: validateFallbacks2,
10594
+ resetEditor,
10595
+ headerSaving,
10596
+ headerValidating,
10597
+ headerResetting
10598
+ } = useFallbackEditor();
10599
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
10600
+ "div",
10601
+ {
10602
+ className: [
10603
+ "min-h-screen bg-zinc-100 p-4 text-zinc-900 dark:bg-zinc-950 dark:text-zinc-100",
10604
+ className
10605
+ ].filter(Boolean).join(" "),
10606
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-auto flex max-w-7xl flex-col gap-4", children: [
10607
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
10608
+ FallbackEditorHeader,
10609
+ {
10610
+ onReset: resetEditor,
10611
+ onValidate: validateFallbacks2,
10612
+ onSave: saveFallbacks,
10613
+ resetting: headerResetting,
10614
+ validating: headerValidating,
10615
+ saving: headerSaving
10616
+ }
10617
+ ),
10618
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "grid gap-4 xl:grid-cols-[300px_minmax(0,1fr)_360px]", children: [
10619
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(FallbackServiceSidebar, {}),
10620
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex min-h-0 flex-col gap-4", children: [
10621
+ /* @__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: [
10622
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { children: [
10623
+ /* @__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" }),
10624
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "mt-1 text-sm text-zinc-500 dark:text-zinc-400", children: "Edit fallback registrations and inspect validation." })
10625
+ ] }),
10626
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex gap-2", children: [
10627
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
10628
+ TabButton,
10629
+ {
10630
+ active: activeTab === "registrations",
10631
+ onClick: () => setActiveTab("registrations"),
10632
+ children: "Registrations"
10633
+ }
10634
+ ),
10635
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
10636
+ TabButton,
10637
+ {
10638
+ active: activeTab === "settings",
10639
+ onClick: () => setActiveTab("settings"),
10640
+ children: "Settings"
10641
+ }
10642
+ )
10643
+ ] })
10644
+ ] }) }),
10645
+ activeTab === "registrations" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(FallbackRegistrationsPanel, {}) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(FallbackSettingsPanel, {})
10646
+ ] }),
10647
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(FallbackDetailsPanel, {})
10648
+ ] })
10649
+ ] })
10650
+ }
10651
+ );
10652
+ }
10653
+ function TabButton({
10654
+ active,
10655
+ onClick,
10656
+ children
10657
+ }) {
10658
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
10659
+ "button",
10660
+ {
10661
+ type: "button",
10662
+ onClick,
10663
+ className: [
10664
+ "rounded-xl px-3 py-2 text-sm font-medium transition",
10665
+ active ? "bg-blue-600 text-white" : "border border-zinc-300 bg-white text-zinc-700 hover:bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-950 dark:text-zinc-200 dark:hover:bg-zinc-800"
10666
+ ].join(" "),
10667
+ children
10668
+ }
10669
+ );
10670
+ }
10671
+
10672
+ // src/react/fallback-editor/VirtualServiceList.tsx
10673
+ var import_react17 = __toESM(require("react"), 1);
10674
+ var import_jsx_runtime7 = require("react/jsx-runtime");
10675
+ function VirtualServiceList({
10676
+ items,
10677
+ selected,
10678
+ onToggle,
10679
+ height = 420,
10680
+ rowHeight = 52,
10681
+ emptyText = "No services found."
10682
+ }) {
10683
+ const [scrollTop, setScrollTop] = import_react17.default.useState(0);
10684
+ const total = items.length;
10685
+ const visibleCount = Math.ceil(height / rowHeight);
10686
+ const overscan = 8;
10687
+ const start = Math.max(0, Math.floor(scrollTop / rowHeight) - overscan);
10688
+ const end = Math.min(total, start + visibleCount + overscan * 2);
10689
+ const visible = items.slice(start, end);
10690
+ if (total === 0) {
10691
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
10692
+ "div",
10693
+ {
10694
+ className: "flex items-center justify-center rounded-xl border border-zinc-200 bg-zinc-50 text-sm text-zinc-500 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-400",
10695
+ style: { height },
10696
+ children: emptyText
10697
+ }
10698
+ );
10699
+ }
10700
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
10701
+ "div",
10702
+ {
10703
+ className: "overflow-auto rounded-xl border border-zinc-200 dark:border-zinc-800",
10704
+ style: { height },
10705
+ onScroll: (e) => setScrollTop(e.currentTarget.scrollTop),
10706
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "relative", style: { height: total * rowHeight }, children: visible.map((item, i) => {
10707
+ var _a, _b;
10708
+ const index = start + i;
10709
+ const key = String(item.id);
10710
+ const checked = selected.has(key);
10711
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
10712
+ "button",
10713
+ {
10714
+ type: "button",
10715
+ onClick: () => onToggle(item.id),
10716
+ 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",
10717
+ style: {
10718
+ top: index * rowHeight,
10719
+ height: rowHeight
10720
+ },
10721
+ children: [
10722
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "min-w-0", children: [
10723
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "truncate text-sm font-medium text-zinc-900 dark:text-zinc-100", children: [
10724
+ "#",
10725
+ String(item.id),
10726
+ " \xB7",
10727
+ " ",
10728
+ (_a = item.name) != null ? _a : "Unnamed"
10729
+ ] }),
10730
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "mt-0.5 text-xs text-zinc-500 dark:text-zinc-400", children: [
10731
+ (_b = item.platform) != null ? _b : "Unknown",
10732
+ typeof item.rate === "number" ? ` \xB7 rate ${item.rate}` : ""
10733
+ ] })
10734
+ ] }),
10735
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
10736
+ "input",
10737
+ {
10738
+ type: "checkbox",
10739
+ checked,
10740
+ readOnly: true,
10741
+ className: "h-4 w-4 rounded border-zinc-300"
10742
+ }
10743
+ )
10744
+ ]
10745
+ },
10746
+ key
10747
+ );
10748
+ }) })
10749
+ }
10750
+ );
10751
+ }
10752
+
10753
+ // src/react/fallback-editor/FallbackDetailsPanel.tsx
10754
+ var import_react18 = __toESM(require("react"), 1);
10755
+ var import_jsx_runtime8 = require("react/jsx-runtime");
10756
+ function FallbackDetailsPanel() {
10757
+ var _a, _b, _c, _d, _e, _f;
10758
+ const { activeServiceId, state, settings } = useFallbackEditor();
10759
+ const services = usePrimaryServiceList();
10760
+ const service = import_react18.default.useMemo(
10761
+ () => services.find((s) => String(s.id) === String(activeServiceId)),
10762
+ [services, activeServiceId]
10763
+ );
10764
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("aside", { className: "flex min-h-0 flex-col gap-4", children: [
10765
+ /* @__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: [
10766
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h3", { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: "Primary service info" }),
10767
+ !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: [
10768
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Detail, { label: "ID", value: String(service.id) }),
10769
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
10770
+ Detail,
10771
+ {
10772
+ label: "Name",
10773
+ value: (_a = service.name) != null ? _a : "Unnamed"
10774
+ }
10775
+ ),
10776
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
10777
+ Detail,
10778
+ {
10779
+ label: "Platform",
10780
+ value: (_b = service.platform) != null ? _b : "\u2014"
10781
+ }
10782
+ ),
10783
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
10784
+ Detail,
10785
+ {
10786
+ label: "Rate",
10787
+ value: typeof service.rate === "number" ? String(service.rate) : "\u2014"
10788
+ }
10789
+ ),
10790
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
10791
+ Detail,
10792
+ {
10793
+ label: "Changed",
10794
+ value: state.changed ? "yes" : "no"
10795
+ }
10796
+ )
10797
+ ] })
10798
+ ] }),
10799
+ /* @__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: [
10800
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h3", { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: "Active policy" }),
10801
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "mt-3 space-y-2 text-sm", children: [
10802
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
10803
+ Detail,
10804
+ {
10805
+ label: "Constraint fit",
10806
+ value: settings.requireConstraintFit ? "enabled" : "disabled"
10807
+ }
10808
+ ),
10809
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
10810
+ Detail,
10811
+ {
10812
+ label: "Rate policy",
10813
+ value: ((_c = settings.ratePolicy) == null ? void 0 : _c.kind) === "within_pct" ? `within_pct (${settings.ratePolicy.pct}%)` : ((_d = settings.ratePolicy) == null ? void 0 : _d.kind) === "at_least_pct_lower" ? `at_least_pct_lower (${settings.ratePolicy.pct}%)` : "lte_primary"
10814
+ }
10815
+ ),
10816
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
10817
+ Detail,
10818
+ {
10819
+ label: "Strategy",
10820
+ value: (_e = settings.selectionStrategy) != null ? _e : "priority"
10821
+ }
10822
+ ),
10823
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Detail, { label: "Mode", value: (_f = settings.mode) != null ? _f : "strict" })
10824
+ ] })
10825
+ ] }),
10826
+ /* @__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: [
10827
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h3", { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: "Current payload" }),
10828
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("pre", { className: "mt-3 overflow-auto rounded-xl bg-zinc-950 p-3 text-xs text-zinc-100", children: JSON.stringify(state.current, null, 2) })
10829
+ ] })
10830
+ ] });
10831
+ }
10832
+ function Detail({ label, value }) {
10833
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-start justify-between gap-4", children: [
10834
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "text-zinc-500 dark:text-zinc-400", children: label }),
10835
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "text-right text-zinc-900 dark:text-zinc-100", children: value })
10836
+ ] });
10837
+ }
10838
+
10839
+ // src/react/fallback-editor/FallbackEditorHeader.tsx
10840
+ var import_jsx_runtime9 = require("react/jsx-runtime");
10841
+ function FallbackEditorHeader({
10842
+ onReset,
10843
+ onValidate,
10844
+ onSave,
10845
+ resetting = false,
10846
+ validating = false,
10847
+ saving = false
10848
+ }) {
10849
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col gap-3 rounded-2xl border border-zinc-200 bg-white p-4 shadow-sm dark:border-zinc-800 dark:bg-zinc-900 md:flex-row md:items-center md:justify-between", children: [
10850
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { children: [
10851
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h1", { className: "text-lg font-semibold text-zinc-900 dark:text-zinc-100", children: "Fallback Editor" }),
10852
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "mt-1 text-sm text-zinc-500 dark:text-zinc-400", children: "Manage global and node-scoped fallback registrations with live validation hints." })
10853
+ ] }),
10854
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-wrap gap-2", children: [
10855
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
10856
+ "button",
10857
+ {
10858
+ type: "button",
10859
+ onClick: onReset,
10860
+ disabled: resetting,
10861
+ className: "rounded-xl border border-zinc-300 bg-white px-3 py-2 text-sm font-medium text-zinc-700 hover:bg-zinc-50 disabled:cursor-not-allowed disabled:opacity-60 dark:border-zinc-700 dark:bg-zinc-950 dark:text-zinc-200 dark:hover:bg-zinc-800",
10862
+ children: resetting ? "Resetting..." : "Reset"
10863
+ }
10864
+ ),
10865
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
10866
+ "button",
10867
+ {
10868
+ type: "button",
10869
+ onClick: onValidate,
10870
+ disabled: validating,
10871
+ className: "rounded-xl border border-zinc-300 bg-white px-3 py-2 text-sm font-medium text-zinc-700 hover:bg-zinc-50 disabled:cursor-not-allowed disabled:opacity-60 dark:border-zinc-700 dark:bg-zinc-950 dark:text-zinc-200 dark:hover:bg-zinc-800",
10872
+ children: validating ? "Validating..." : "Validate"
10873
+ }
10874
+ ),
10875
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
10876
+ "button",
10877
+ {
10878
+ type: "button",
10879
+ onClick: onSave,
10880
+ disabled: saving,
10881
+ 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",
10882
+ children: saving ? "Saving..." : "Save"
10883
+ }
10884
+ )
10885
+ ] })
10886
+ ] });
10887
+ }
10888
+
10889
+ // src/react/fallback-editor/FallbackSettingsPanel.tsx
10890
+ var import_react19 = __toESM(require("react"), 1);
10891
+ var import_jsx_runtime10 = require("react/jsx-runtime");
10892
+ function FallbackSettingsPanel() {
10893
+ var _a, _b, _c;
10894
+ const { settings, saveSettings, settingsSaving } = useFallbackEditorContext();
10895
+ const [draft, setDraft] = import_react19.default.useState(settings);
10896
+ const [error, setError] = import_react19.default.useState(null);
10897
+ const [saved, setSaved] = import_react19.default.useState(false);
10898
+ import_react19.default.useEffect(() => {
10899
+ setDraft(settings);
10900
+ setSaved(false);
10901
+ setError(null);
10902
+ }, [settings]);
10903
+ const changed = JSON.stringify(draft != null ? draft : {}) !== JSON.stringify(settings != null ? settings : {});
10904
+ async function handleSave() {
10905
+ setError(null);
10906
+ setSaved(false);
10907
+ try {
10908
+ await saveSettings(draft);
10909
+ setSaved(true);
10910
+ } catch (err) {
10911
+ setError(
10912
+ err instanceof Error ? err.message : "Failed to save fallback settings."
10913
+ );
10914
+ }
10915
+ }
10916
+ function setRatePolicy(next) {
10917
+ setDraft((prev) => ({
10918
+ ...prev,
10919
+ ratePolicy: next
10920
+ }));
10921
+ }
10922
+ const ratePolicy = (_a = draft.ratePolicy) != null ? _a : { kind: "lte_primary" };
10923
+ 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: [
10924
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "mb-4 flex items-start justify-between gap-3", children: [
10925
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
10926
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h3", { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: "Fallback settings" }),
10927
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "mt-1 text-xs text-zinc-500 dark:text-zinc-400", children: "These settings can be persisted by the host and returned into the editor." })
10928
+ ] }),
10929
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
10930
+ "button",
10931
+ {
10932
+ type: "button",
10933
+ onClick: handleSave,
10934
+ disabled: !changed || settingsSaving,
10935
+ className: [
10936
+ "rounded-xl px-3 py-2 text-sm font-medium transition",
10937
+ !changed || settingsSaving ? "cursor-not-allowed bg-zinc-200 text-zinc-500 dark:bg-zinc-800 dark:text-zinc-500" : "bg-blue-600 text-white hover:bg-blue-700"
10938
+ ].join(" "),
10939
+ children: settingsSaving ? "Saving..." : "Save settings"
10940
+ }
10941
+ )
10942
+ ] }),
10943
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "space-y-4", children: [
10944
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
10945
+ SettingRow,
10946
+ {
10947
+ title: "Require constraint fit",
10948
+ hint: "Reject or warn when a candidate does not match effective tag constraints.",
10949
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
10950
+ "button",
10951
+ {
10952
+ type: "button",
10953
+ onClick: () => setDraft((prev) => ({
10954
+ ...prev,
10955
+ requireConstraintFit: !(prev == null ? void 0 : prev.requireConstraintFit)
10956
+ })),
10957
+ className: [
10958
+ "inline-flex items-center gap-2 rounded-full border px-3 py-2 text-sm",
10959
+ 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"
10960
+ ].join(" "),
10961
+ children: [
10962
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: draft.requireConstraintFit ? "Enabled" : "Disabled" }),
10963
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
10964
+ "span",
10965
+ {
10966
+ className: [
10967
+ "h-2.5 w-2.5 rounded-full",
10968
+ draft.requireConstraintFit ? "bg-green-500" : "bg-zinc-400"
10969
+ ].join(" ")
10970
+ }
10971
+ )
10972
+ ]
10973
+ }
10974
+ )
10975
+ }
10976
+ ),
10977
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
10978
+ SettingRow,
10979
+ {
10980
+ title: "Rate policy",
10981
+ hint: "Controls how fallback service rates are compared against the primary service.",
10982
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-col gap-2 md:items-end", children: [
10983
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
10984
+ "select",
10985
+ {
10986
+ value: ratePolicy.kind,
10987
+ onChange: (e) => {
10988
+ const kind = e.target.value;
10989
+ if (kind === "lte_primary") {
10990
+ setRatePolicy({ kind: "lte_primary" });
10991
+ return;
10992
+ }
10993
+ if (kind === "within_pct") {
10994
+ setRatePolicy({
10995
+ kind: "within_pct",
10996
+ pct: ratePolicy.kind === "within_pct" || ratePolicy.kind === "at_least_pct_lower" ? ratePolicy.pct : 10
10997
+ });
10998
+ return;
10999
+ }
11000
+ setRatePolicy({
11001
+ kind: "at_least_pct_lower",
11002
+ pct: ratePolicy.kind === "within_pct" || ratePolicy.kind === "at_least_pct_lower" ? ratePolicy.pct : 10
11003
+ });
11004
+ },
11005
+ 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",
11006
+ children: [
11007
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: "lte_primary", children: "lte_primary" }),
11008
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: "within_pct", children: "within_pct" }),
11009
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: "at_least_pct_lower", children: "at_least_pct_lower" })
11010
+ ]
11011
+ }
11012
+ ),
11013
+ (ratePolicy.kind === "within_pct" || ratePolicy.kind === "at_least_pct_lower") && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center gap-2", children: [
11014
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
11015
+ "input",
11016
+ {
11017
+ type: "number",
11018
+ min: 0,
11019
+ step: "0.01",
11020
+ value: ratePolicy.pct,
11021
+ onChange: (e) => {
11022
+ const pct = Number(e.target.value || 0);
11023
+ if (ratePolicy.kind === "within_pct") {
11024
+ setRatePolicy({
11025
+ kind: "within_pct",
11026
+ pct
11027
+ });
11028
+ return;
11029
+ }
11030
+ setRatePolicy({
11031
+ kind: "at_least_pct_lower",
11032
+ pct
11033
+ });
11034
+ },
11035
+ 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"
11036
+ }
11037
+ ),
11038
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-sm text-zinc-500 dark:text-zinc-400", children: "%" })
11039
+ ] })
11040
+ ] })
11041
+ }
11042
+ ),
11043
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
11044
+ SettingRow,
11045
+ {
11046
+ title: "Selection strategy",
11047
+ hint: "How valid fallback candidates are ordered in previews.",
11048
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
11049
+ "select",
11050
+ {
11051
+ value: (_b = draft.selectionStrategy) != null ? _b : "priority",
11052
+ onChange: (e) => setDraft((prev) => ({
11053
+ ...prev,
11054
+ selectionStrategy: e.target.value
11055
+ })),
11056
+ 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",
11057
+ children: [
11058
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: "priority", children: "priority" }),
11059
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: "cheapest", children: "cheapest" })
11060
+ ]
11061
+ }
11062
+ )
11063
+ }
11064
+ ),
11065
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
11066
+ SettingRow,
11067
+ {
11068
+ title: "Mode",
11069
+ hint: "Use strict for enforced filtering, dev for advisory feedback.",
11070
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
11071
+ "select",
11072
+ {
11073
+ value: (_c = draft.mode) != null ? _c : "strict",
11074
+ onChange: (e) => setDraft((prev) => ({
11075
+ ...prev,
11076
+ mode: e.target.value
11077
+ })),
11078
+ 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",
11079
+ children: [
11080
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: "strict", children: "strict" }),
11081
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: "dev", children: "dev" })
11082
+ ]
11083
+ }
11084
+ )
11085
+ }
11086
+ )
11087
+ ] }),
11088
+ saved && !error ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "mt-4 rounded-xl border border-green-200 bg-green-50 px-3 py-2 text-sm text-green-700 dark:border-green-900/50 dark:bg-green-950/20 dark:text-green-300", children: "Settings saved." }) : null,
11089
+ error ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "mt-4 rounded-xl border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700 dark:border-red-900/50 dark:bg-red-950/20 dark:text-red-300", children: error }) : null
11090
+ ] });
11091
+ }
11092
+ function SettingRow({
11093
+ title,
11094
+ hint,
11095
+ children
11096
+ }) {
11097
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-col gap-3 border-b border-dashed border-zinc-200 pb-4 last:border-b-0 last:pb-0 dark:border-zinc-800 md:flex-row md:items-center md:justify-between", children: [
11098
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "max-w-xl", children: [
11099
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "text-sm font-medium text-zinc-900 dark:text-zinc-100", children: title }),
11100
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "mt-1 text-xs text-zinc-500 dark:text-zinc-400", children: hint })
11101
+ ] }),
11102
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { children })
11103
+ ] });
11104
+ }
11105
+
11106
+ // src/react/fallback-editor/FallbackServiceSidebar.tsx
11107
+ var import_react20 = require("react");
11108
+ var import_jsx_runtime11 = require("react/jsx-runtime");
11109
+ function FallbackServiceSidebar() {
11110
+ const { activeServiceId, setActiveServiceId, get } = useFallbackEditor();
11111
+ const services = usePrimaryServiceList();
11112
+ const [query, setQuery] = (0, import_react20.useState)("");
11113
+ const filtered = (0, import_react20.useMemo)(() => {
11114
+ const q = query.trim().toLowerCase();
11115
+ if (!q) return services;
11116
+ return services.filter(
11117
+ (service) => {
11118
+ var _a, _b;
11119
+ return String(service.id).includes(q) || String((_a = service.name) != null ? _a : "").toLowerCase().includes(q) || String((_b = service.platform) != null ? _b : "").toLowerCase().includes(q);
11120
+ }
11121
+ );
11122
+ }, [query, services]);
11123
+ 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: [
11124
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "border-b border-zinc-200 p-4 dark:border-zinc-800", children: [
11125
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h2", { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: "Primary services" }),
11126
+ /* @__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." })
11127
+ ] }),
11128
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex min-h-0 flex-1 flex-col p-4", children: [
11129
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
11130
+ "input",
11131
+ {
11132
+ value: query,
11133
+ onChange: (e) => setQuery(e.target.value),
11134
+ placeholder: "Search primary service...",
11135
+ 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"
11136
+ }
11137
+ ),
11138
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "mt-3 flex-1 space-y-2 overflow-auto", children: filtered.map((service) => {
11139
+ var _a, _b;
11140
+ const active = String(service.id) === String(activeServiceId);
11141
+ const count = get(service.id).length;
11142
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
11143
+ "button",
11144
+ {
11145
+ type: "button",
11146
+ onClick: () => setActiveServiceId(service.id),
11147
+ className: [
11148
+ "w-full rounded-2xl border p-3 text-left transition",
11149
+ 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"
11150
+ ].join(" "),
11151
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-start justify-between gap-3", children: [
11152
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "min-w-0", children: [
11153
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "truncate text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: [
11154
+ "#",
11155
+ String(service.id),
11156
+ " \xB7",
11157
+ " ",
11158
+ (_a = service.name) != null ? _a : "Unnamed"
11159
+ ] }),
11160
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "mt-1 text-xs text-zinc-500 dark:text-zinc-400", children: [
11161
+ (_b = service.platform) != null ? _b : "Unknown",
11162
+ typeof service.rate === "number" ? ` \xB7 rate ${service.rate}` : ""
11163
+ ] })
11164
+ ] }),
11165
+ /* @__PURE__ */ (0, import_jsx_runtime11.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: [
11166
+ count,
11167
+ " reg"
11168
+ ] })
11169
+ ] })
11170
+ },
11171
+ String(service.id)
11172
+ );
11173
+ }) })
11174
+ ] })
11175
+ ] });
11176
+ }
11177
+
11178
+ // src/react/fallback-editor/FallbackRegistrationsPanel.tsx
11179
+ var import_react23 = __toESM(require("react"), 1);
11180
+
11181
+ // src/react/fallback-editor/FallbackAddCandidatesDialog.tsx
11182
+ var import_react21 = __toESM(require("react"), 1);
11183
+ var import_jsx_runtime12 = require("react/jsx-runtime");
11184
+ function FallbackAddCandidatesDialog({
11185
+ open,
11186
+ onClose,
11187
+ context,
11188
+ primaryId
11189
+ }) {
11190
+ const { eligible, addMany } = useFallbackEditor();
11191
+ const eligibleServices = useEligibleServiceList();
11192
+ const [query, setQuery] = import_react21.default.useState("");
11193
+ const [filterEligibleOnly, setFilterEligibleOnly] = import_react21.default.useState(true);
11194
+ const [selected, setSelected] = import_react21.default.useState(/* @__PURE__ */ new Set());
11195
+ const [submitting, setSubmitting] = import_react21.default.useState(false);
11196
+ import_react21.default.useEffect(() => {
11197
+ if (!open) {
11198
+ setQuery("");
11199
+ setFilterEligibleOnly(true);
11200
+ setSelected(/* @__PURE__ */ new Set());
11201
+ }
11202
+ }, [open]);
11203
+ const allowedIds = import_react21.default.useMemo(() => {
11204
+ if (!context) return null;
11205
+ if (!filterEligibleOnly) return null;
11206
+ return new Set(eligible(context).map((id) => String(id)));
11207
+ }, [context, filterEligibleOnly, eligible]);
11208
+ const items = import_react21.default.useMemo(() => {
11209
+ const q = query.trim().toLowerCase();
11210
+ return eligibleServices.filter((service) => {
11211
+ var _a, _b;
11212
+ if (primaryId !== void 0 && String(service.id) === String(primaryId)) {
11213
+ return false;
11214
+ }
11215
+ if (allowedIds && !allowedIds.has(String(service.id))) {
11216
+ return false;
11217
+ }
11218
+ if (!q) return true;
11219
+ return String(service.id).includes(q) || String((_a = service.name) != null ? _a : "").toLowerCase().includes(q) || String((_b = service.platform) != null ? _b : "").toLowerCase().includes(q);
11220
+ });
11221
+ }, [eligibleServices, allowedIds, query, primaryId]);
11222
+ function toggle(id) {
11223
+ setSelected((prev) => {
11224
+ const next = new Set(prev);
11225
+ const key = String(id);
11226
+ if (next.has(key)) next.delete(key);
11227
+ else next.add(key);
11228
+ return next;
11229
+ });
11230
+ }
11231
+ async function handleAdd() {
11232
+ if (!context || selected.size === 0) return;
11233
+ setSubmitting(true);
11234
+ try {
11235
+ const ids = items.filter((item) => selected.has(String(item.id))).map((item) => item.id);
11236
+ addMany(context, ids);
11237
+ onClose();
11238
+ } finally {
11239
+ setSubmitting(false);
11240
+ }
11241
+ }
11242
+ if (!open || !context) return null;
11243
+ 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: [
11244
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "border-b border-zinc-200 p-4 dark:border-zinc-800", children: [
11245
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h3", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "Add fallback services" }),
11246
+ /* @__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." })
11247
+ ] }),
11248
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex flex-col gap-3 p-4", children: [
11249
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
11250
+ "input",
11251
+ {
11252
+ value: query,
11253
+ onChange: (e) => setQuery(e.target.value),
11254
+ placeholder: "Search eligible services...",
11255
+ 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"
11256
+ }
11257
+ ),
11258
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("label", { className: "inline-flex items-center gap-2 text-sm text-zinc-700 dark:text-zinc-300", children: [
11259
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
11260
+ "input",
11261
+ {
11262
+ type: "checkbox",
11263
+ checked: filterEligibleOnly,
11264
+ onChange: (e) => setFilterEligibleOnly(e.target.checked),
11265
+ className: "h-4 w-4 rounded border-zinc-300"
11266
+ }
11267
+ ),
11268
+ "Filter eligible only"
11269
+ ] }),
11270
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
11271
+ VirtualServiceList,
11272
+ {
11273
+ items,
11274
+ selected,
11275
+ onToggle: toggle,
11276
+ emptyText: "No eligible services found."
11277
+ }
11278
+ )
11279
+ ] }),
11280
+ /* @__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: [
11281
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "text-sm text-zinc-500 dark:text-zinc-400", children: [
11282
+ selected.size,
11283
+ " selected"
11284
+ ] }),
11285
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex gap-2", children: [
11286
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
11287
+ "button",
11288
+ {
11289
+ type: "button",
11290
+ onClick: onClose,
11291
+ 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",
11292
+ children: "Cancel"
11293
+ }
11294
+ ),
11295
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
11296
+ "button",
11297
+ {
11298
+ type: "button",
11299
+ onClick: handleAdd,
11300
+ disabled: selected.size === 0 || submitting,
11301
+ 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",
11302
+ children: submitting ? "Adding..." : "Add selected"
11303
+ }
11304
+ )
11305
+ ] })
11306
+ ] })
11307
+ ] }) });
11308
+ }
11309
+
11310
+ // src/react/fallback-editor/FallbackAddRegistrationDialog.tsx
11311
+ var import_react22 = __toESM(require("react"), 1);
11312
+ var import_jsx_runtime13 = require("react/jsx-runtime");
11313
+ function FallbackAddRegistrationDialog({
11314
+ open,
11315
+ onClose,
11316
+ onSelect
11317
+ }) {
11318
+ const { activeServiceId, serviceProps, snapshot } = useFallbackEditor();
11319
+ const registrations = useActiveFallbackRegistrations();
11320
+ const [scope, setScope] = import_react22.default.useState("global");
11321
+ const [nodeId, setNodeId] = import_react22.default.useState("");
11322
+ const mode = import_react22.default.useMemo(() => {
11323
+ if (snapshot) return "snapshot";
11324
+ if (serviceProps) return "props";
11325
+ return "none";
11326
+ }, [snapshot, serviceProps]);
11327
+ import_react22.default.useEffect(() => {
11328
+ if (open) {
11329
+ setScope("global");
11330
+ setNodeId("");
11331
+ }
11332
+ }, [open]);
11333
+ const hasGlobal = import_react22.default.useMemo(() => {
11334
+ return registrations.some((r) => r.scope === "global");
11335
+ }, [registrations]);
11336
+ const nodeTargets = import_react22.default.useMemo(() => {
11337
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
11338
+ if (activeServiceId === void 0 || activeServiceId === null) {
11339
+ return [];
11340
+ }
11341
+ if (mode === "snapshot" && (snapshot == null ? void 0 : snapshot.serviceMap)) {
11342
+ const out = [];
11343
+ for (const [id, primaryIds] of Object.entries(
11344
+ snapshot.serviceMap
11345
+ )) {
11346
+ const matchesPrimary = (primaryIds != null ? primaryIds : []).some(
11347
+ (serviceId) => String(serviceId) === String(activeServiceId)
11348
+ );
11349
+ if (!matchesPrimary) continue;
11350
+ const meta = resolveNodeMeta(serviceProps, id);
11351
+ out.push({
11352
+ id,
11353
+ kind: meta.kind,
11354
+ label: meta.label,
11355
+ serviceId: activeServiceId
11356
+ });
11357
+ }
11358
+ const activeTagId = (_a = snapshot.selection) == null ? void 0 : _a.tag;
11359
+ out.sort((a, b) => {
11360
+ if (activeTagId && a.id === activeTagId && b.id !== activeTagId) {
11361
+ return -1;
11362
+ }
11363
+ if (activeTagId && b.id === activeTagId && a.id !== activeTagId) {
11364
+ return 1;
11365
+ }
11366
+ return a.label.localeCompare(b.label);
11367
+ });
11368
+ const seen = /* @__PURE__ */ new Set();
11369
+ return out.filter((item) => {
11370
+ if (seen.has(item.id)) return false;
11371
+ seen.add(item.id);
11372
+ return true;
11373
+ });
11374
+ }
11375
+ if (mode === "props" && serviceProps) {
11376
+ const out = [];
11377
+ for (const tag of (_b = serviceProps.filters) != null ? _b : []) {
11378
+ if ((tag == null ? void 0 : tag.service_id) === void 0 || (tag == null ? void 0 : tag.service_id) === null) {
11379
+ continue;
11380
+ }
11381
+ if (String(tag.service_id) !== String(activeServiceId)) {
11382
+ continue;
11383
+ }
11384
+ out.push({
11385
+ id: tag.id,
11386
+ kind: "tag",
11387
+ label: (_d = (_c = tag.label) != null ? _c : tag.title) != null ? _d : tag.id,
11388
+ serviceId: tag.service_id
11389
+ });
11390
+ }
11391
+ for (const field of (_e = serviceProps.fields) != null ? _e : []) {
11392
+ if ((field == null ? void 0 : field.service_id) !== void 0 && (field == null ? void 0 : field.service_id) !== null && String(field.service_id) === String(activeServiceId)) {
11393
+ out.push({
11394
+ id: field.id,
11395
+ kind: "field",
11396
+ label: (_g = (_f = field.label) != null ? _f : field.title) != null ? _g : field.id,
11397
+ serviceId: field.service_id
11398
+ });
11399
+ }
11400
+ for (const option of (_h = field.options) != null ? _h : []) {
11401
+ if ((option == null ? void 0 : option.service_id) === void 0 || (option == null ? void 0 : option.service_id) === null) {
11402
+ continue;
11403
+ }
11404
+ if (String(option.service_id) !== String(activeServiceId)) {
11405
+ continue;
11406
+ }
11407
+ out.push({
11408
+ id: option.id,
11409
+ kind: "option",
11410
+ label: (_k = (_i = option.label) != null ? _i : option.title) != null ? _k : String(
11411
+ (_j = option.value) != null ? _j : option.id
11412
+ ),
11413
+ serviceId: option.service_id
11414
+ });
11415
+ }
11416
+ }
11417
+ const seen = /* @__PURE__ */ new Set();
11418
+ return out.filter((item) => {
11419
+ if (seen.has(item.id)) return false;
11420
+ seen.add(item.id);
11421
+ return true;
11422
+ });
11423
+ }
11424
+ return [];
11425
+ }, [mode, snapshot, serviceProps, activeServiceId]);
11426
+ import_react22.default.useEffect(() => {
11427
+ if (hasGlobal && scope === "global") {
11428
+ setScope("node");
11429
+ }
11430
+ }, [hasGlobal, scope]);
11431
+ import_react22.default.useEffect(() => {
11432
+ if (scope === "node" && nodeId) {
11433
+ const exists = nodeTargets.some((node) => node.id === nodeId);
11434
+ if (!exists) setNodeId("");
11435
+ }
11436
+ }, [scope, nodeId, nodeTargets]);
11437
+ function handleContinue() {
11438
+ var _a;
11439
+ if (activeServiceId === void 0 || activeServiceId === null) return;
11440
+ if (scope === "global") {
11441
+ onSelect(
11442
+ {
11443
+ scope: "global",
11444
+ primary: activeServiceId
11445
+ },
11446
+ activeServiceId
11447
+ );
11448
+ return;
11449
+ }
11450
+ if (!nodeId) return;
11451
+ const node = nodeTargets.find((n) => n.id === nodeId);
11452
+ onSelect(
11453
+ {
11454
+ scope: "node",
11455
+ nodeId
11456
+ },
11457
+ (_a = node == null ? void 0 : node.serviceId) != null ? _a : activeServiceId
11458
+ );
11459
+ }
11460
+ if (!open) return null;
11461
+ const nodeScopeDisabled = nodeTargets.length === 0;
11462
+ 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: [
11463
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "border-b border-zinc-200 p-4 dark:border-zinc-800", children: [
11464
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("h3", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "Add registration" }),
11465
+ /* @__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." })
11466
+ ] }),
11467
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "space-y-4 p-4", children: [
11468
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "space-y-2", children: [
11469
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "text-sm font-medium text-zinc-900 dark:text-zinc-100", children: "Scope" }),
11470
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "grid gap-2", children: [
11471
+ !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: [
11472
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
11473
+ "input",
11474
+ {
11475
+ type: "radio",
11476
+ name: "scope",
11477
+ checked: scope === "global",
11478
+ onChange: () => setScope("global"),
11479
+ className: "mt-1 h-4 w-4"
11480
+ }
11481
+ ),
11482
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
11483
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "text-sm font-medium text-zinc-900 dark:text-zinc-100", children: "Global" }),
11484
+ /* @__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." })
11485
+ ] })
11486
+ ] }),
11487
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
11488
+ "label",
11489
+ {
11490
+ className: [
11491
+ "flex items-start gap-3 rounded-xl border p-3",
11492
+ nodeScopeDisabled ? "cursor-not-allowed border-zinc-200 opacity-60 dark:border-zinc-800" : "cursor-pointer border-zinc-200 dark:border-zinc-800"
11493
+ ].join(" "),
11494
+ children: [
11495
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
11496
+ "input",
11497
+ {
11498
+ type: "radio",
11499
+ name: "scope",
11500
+ checked: scope === "node",
11501
+ onChange: () => setScope("node"),
11502
+ disabled: nodeScopeDisabled,
11503
+ className: "mt-1 h-4 w-4"
11504
+ }
11505
+ ),
11506
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
11507
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "text-sm font-medium text-zinc-900 dark:text-zinc-100", children: "Node" }),
11508
+ /* @__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." })
11509
+ ] })
11510
+ ]
11511
+ }
11512
+ )
11513
+ ] })
11514
+ ] }),
11515
+ scope === "node" && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "space-y-2", children: [
11516
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "text-sm font-medium text-zinc-900 dark:text-zinc-100", children: "Node id" }),
11517
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
11518
+ "select",
11519
+ {
11520
+ value: nodeId,
11521
+ onChange: (e) => setNodeId(e.target.value),
11522
+ 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",
11523
+ children: [
11524
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "", children: "Select node\u2026" }),
11525
+ nodeTargets.map((node) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("option", { value: node.id, children: [
11526
+ "[",
11527
+ node.kind,
11528
+ "] ",
11529
+ node.label,
11530
+ " \xB7 #",
11531
+ String(node.serviceId)
11532
+ ] }, node.id))
11533
+ ]
11534
+ }
11535
+ ),
11536
+ 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
11537
+ ] })
11538
+ ] }),
11539
+ /* @__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: [
11540
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
11541
+ "button",
11542
+ {
11543
+ type: "button",
11544
+ onClick: onClose,
11545
+ 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",
11546
+ children: "Cancel"
11547
+ }
11548
+ ),
11549
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
11550
+ "button",
11551
+ {
11552
+ type: "button",
11553
+ onClick: handleContinue,
11554
+ disabled: activeServiceId === void 0 || scope === "node" && !nodeId,
11555
+ 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",
11556
+ children: "Continue"
11557
+ }
11558
+ )
11559
+ ] })
11560
+ ] }) });
11561
+ }
11562
+ function resolveNodeMeta(props, nodeId) {
11563
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
11564
+ if (!props) {
11565
+ return { kind: "node", label: nodeId };
11566
+ }
11567
+ const tag = (_a = props.filters) == null ? void 0 : _a.find((t) => t.id === nodeId);
11568
+ if (tag) {
11569
+ return {
11570
+ kind: "tag",
11571
+ label: (_c = (_b = tag.label) != null ? _b : tag.title) != null ? _c : tag.id
11572
+ };
11573
+ }
11574
+ const field = (_d = props.fields) == null ? void 0 : _d.find((f) => f.id === nodeId);
11575
+ if (field) {
11576
+ return {
11577
+ kind: "field",
11578
+ label: (_f = (_e = field.label) != null ? _e : field.title) != null ? _f : field.id
11579
+ };
11580
+ }
11581
+ for (const fieldItem of (_g = props.fields) != null ? _g : []) {
11582
+ const option = (_h = fieldItem.options) == null ? void 0 : _h.find((o) => o.id === nodeId);
11583
+ if (option) {
11584
+ return {
11585
+ kind: "option",
11586
+ label: (_k = (_i = option.label) != null ? _i : option.title) != null ? _k : String((_j = option.value) != null ? _j : option.id)
11587
+ };
11588
+ }
11589
+ }
11590
+ return { kind: "node", label: nodeId };
11591
+ }
11592
+
11593
+ // src/react/fallback-editor/FallbackRegistrationsPanel.tsx
11594
+ var import_jsx_runtime14 = require("react/jsx-runtime");
11595
+ function FallbackRegistrationsPanel() {
11596
+ const { activeServiceId, remove, clear, check } = useFallbackEditor();
11597
+ const registrations = useActiveFallbackRegistrations();
11598
+ const eligibleServices = useEligibleServiceList();
11599
+ const [candidatePickerOpen, setCandidatePickerOpen] = import_react23.default.useState(false);
11600
+ const [candidateContext, setCandidateContext] = import_react23.default.useState(null);
11601
+ const [candidatePrimaryId, setCandidatePrimaryId] = import_react23.default.useState(void 0);
11602
+ const [registrationDialogOpen, setRegistrationDialogOpen] = import_react23.default.useState(false);
11603
+ const makeContext = import_react23.default.useCallback(
11604
+ (registration) => {
11605
+ if (registration.scope === "global") {
11606
+ return {
11607
+ scope: "global",
11608
+ primary: registration.primary
11609
+ };
11610
+ }
11611
+ return {
11612
+ scope: "node",
11613
+ nodeId: registration.scopeId
11614
+ };
11615
+ },
11616
+ []
11617
+ );
11618
+ const openCandidatePicker = import_react23.default.useCallback(
11619
+ (context, primaryId) => {
11620
+ setCandidateContext(context);
11621
+ setCandidatePrimaryId(primaryId);
11622
+ setCandidatePickerOpen(true);
11623
+ },
11624
+ []
11625
+ );
11626
+ if (activeServiceId === void 0 || activeServiceId === null) {
11627
+ 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." }) });
11628
+ }
11629
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
11630
+ /* @__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: [
11631
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "mb-4 flex items-start justify-between gap-3", children: [
11632
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { children: [
11633
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("h3", { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: "Registered fallbacks" }),
11634
+ /* @__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." })
11635
+ ] }),
11636
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
11637
+ "button",
11638
+ {
11639
+ type: "button",
11640
+ onClick: () => setRegistrationDialogOpen(true),
11641
+ 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",
11642
+ children: "Add registration"
11643
+ }
11644
+ )
11645
+ ] }),
11646
+ /* @__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) => {
11647
+ var _a;
11648
+ const context = makeContext(reg);
11649
+ const candidates = reg.services;
11650
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
11651
+ "div",
11652
+ {
11653
+ className: "rounded-2xl border border-zinc-200 bg-zinc-50 p-4 dark:border-zinc-800 dark:bg-zinc-950",
11654
+ children: [
11655
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex flex-wrap items-start justify-between gap-3", children: [
11656
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { children: [
11657
+ /* @__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}` }),
11658
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "mt-1 text-xs text-zinc-500 dark:text-zinc-400", children: [
11659
+ "Primary #",
11660
+ String(reg.primary)
11661
+ ] })
11662
+ ] }),
11663
+ /* @__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: [
11664
+ reg.scope,
11665
+ reg.scopeId ? ` \xB7 ${reg.scopeId}` : ""
11666
+ ] })
11667
+ ] }),
11668
+ /* @__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) => {
11669
+ var _a2;
11670
+ const preview = check(context, [
11671
+ candidate
11672
+ ]);
11673
+ const rejected = preview.rejected[0];
11674
+ 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";
11675
+ const service = eligibleServices.find(
11676
+ (s) => String(s.id) === String(candidate)
11677
+ );
11678
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
11679
+ "div",
11680
+ {
11681
+ className: `inline-flex items-center gap-2 rounded-xl border px-3 py-2 text-xs ${tone}`,
11682
+ children: [
11683
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { children: service ? `#${String(service.id)} \xB7 ${(_a2 = service.name) != null ? _a2 : "Unnamed"}` : `#${String(candidate)}` }),
11684
+ 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(
11685
+ ", "
11686
+ ) }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "rounded-full border border-current/20 px-2 py-0.5 text-[10px]", children: "valid" }),
11687
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
11688
+ "button",
11689
+ {
11690
+ type: "button",
11691
+ onClick: () => remove(
11692
+ context,
11693
+ candidate
11694
+ ),
11695
+ className: "text-current/70 hover:text-current",
11696
+ children: "\xD7"
11697
+ }
11698
+ )
11699
+ ]
11700
+ },
11701
+ String(candidate)
11702
+ );
11703
+ }) }),
11704
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "mt-4 flex gap-2", children: [
11705
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
11706
+ "button",
11707
+ {
11708
+ type: "button",
11709
+ onClick: () => openCandidatePicker(
11710
+ context,
11711
+ reg.primary
11712
+ ),
11713
+ 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",
11714
+ children: "Add fallback"
11715
+ }
11716
+ ),
11717
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
11718
+ "button",
11719
+ {
11720
+ type: "button",
11721
+ onClick: () => clear(context),
11722
+ 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",
11723
+ children: "Clear"
11724
+ }
11725
+ )
11726
+ ] })
11727
+ ]
11728
+ },
11729
+ `${reg.scope}:${String((_a = reg.scopeId) != null ? _a : "global")}:${index}`
11730
+ );
11731
+ }) })
11732
+ ] }),
11733
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
11734
+ FallbackAddRegistrationDialog,
11735
+ {
11736
+ open: registrationDialogOpen,
11737
+ onClose: () => setRegistrationDialogOpen(false),
11738
+ onSelect: (context, primaryId) => {
11739
+ setRegistrationDialogOpen(false);
11740
+ openCandidatePicker(context, primaryId);
11741
+ }
11742
+ }
11743
+ ),
11744
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
11745
+ FallbackAddCandidatesDialog,
11746
+ {
11747
+ open: candidatePickerOpen,
11748
+ onClose: () => setCandidatePickerOpen(false),
11749
+ context: candidateContext,
11750
+ primaryId: candidatePrimaryId
11751
+ }
11752
+ )
11753
+ ] });
11754
+ }
9858
11755
  // Annotate the CommonJS export names for ESM import in node:
9859
11756
  0 && (module.exports = {
9860
11757
  CanvasAPI,
9861
11758
  EventBus,
11759
+ FallbackAddCandidatesDialog,
11760
+ FallbackAddRegistrationDialog,
11761
+ FallbackDetailsPanel,
11762
+ FallbackEditor,
11763
+ FallbackEditorHeader,
11764
+ FallbackEditorProvider,
11765
+ FallbackRegistrationsPanel,
11766
+ FallbackServiceSidebar,
11767
+ FallbackSettingsPanel,
9862
11768
  FormProvider,
9863
11769
  OrderFlowProvider,
9864
11770
  Provider,
11771
+ VirtualServiceList,
9865
11772
  Wrapper,
9866
11773
  createInputRegistry,
9867
11774
  registerEntries,
9868
11775
  resolveInputDescriptor,
11776
+ useActiveFallbackRegistrations,
11777
+ useEligibleServiceList,
11778
+ useFallbackEditor,
11779
+ useFallbackEditorContext,
9869
11780
  useFormApi,
9870
11781
  useInputs,
9871
11782
  useOptionalFormApi,
9872
11783
  useOrderFlow,
9873
- useOrderFlowContext
11784
+ useOrderFlowContext,
11785
+ usePrimaryServiceList
9874
11786
  });
9875
11787
  //# sourceMappingURL=index.cjs.map