@domainkit/react 0.9.1 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
3
3
  import { DnsRecord, DomainKit, Reason, Receipt } from "domainkit";
4
4
  import * as Effect from "effect/Effect";
5
- import { createContext, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
5
+ import { createContext, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
6
6
  import * as Data from "effect/Data";
7
7
  import { Transport } from "domainkit/client";
8
8
  import { useRender } from "@base-ui/react/use-render";
@@ -10,6 +10,9 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
10
10
  import * as Exit from "effect/Exit";
11
11
  import * as Option from "effect/Option";
12
12
  import { Dialog } from "@base-ui/react/dialog";
13
+ import { Collapsible } from "@base-ui/react/collapsible";
14
+ import { Switch } from "@base-ui/react/switch";
15
+ import { Menu } from "@base-ui/react/menu";
13
16
  import { Popover } from "@base-ui/react/popover";
14
17
  import * as DateTime from "effect/DateTime";
15
18
  import { Testing } from "domainkit/testing";
@@ -47,6 +50,7 @@ function Glyph({ children }) {
47
50
  });
48
51
  }
49
52
  const defaultIcons = {
53
+ chevron: /* @__PURE__ */ jsx(Glyph, { children: /* @__PURE__ */ jsx("path", { d: "m6 9 6 6 6-6" }) }),
50
54
  close: /* @__PURE__ */ jsxs(Glyph, { children: [/* @__PURE__ */ jsx("path", { d: "M18 6 6 18" }), /* @__PURE__ */ jsx("path", { d: "m6 6 12 12" })] }),
51
55
  copied: /* @__PURE__ */ jsx(Glyph, { children: /* @__PURE__ */ jsx("path", { d: "M20 6 9 17l-5-5" }) }),
52
56
  copy: /* @__PURE__ */ jsxs(Glyph, { children: [/* @__PURE__ */ jsx("rect", {
@@ -84,7 +88,12 @@ const defaultIcons = {
84
88
  cx: "12",
85
89
  cy: "12",
86
90
  r: "9"
87
- }), /* @__PURE__ */ jsx("path", { d: "m8.5 12.5 2.5 2.5 4.5-5" })] })
91
+ }), /* @__PURE__ */ jsx("path", { d: "m8.5 12.5 2.5 2.5 4.5-5" })] }),
92
+ warning: /* @__PURE__ */ jsxs(Glyph, { children: [
93
+ /* @__PURE__ */ jsx("path", { d: "M10.3 3.9 2.4 17.5a2 2 0 0 0 1.7 3h15.8a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z" }),
94
+ /* @__PURE__ */ jsx("path", { d: "M12 9v4" }),
95
+ /* @__PURE__ */ jsx("path", { d: "M12 17h.01" })
96
+ ] })
88
97
  };
89
98
  const IconsContext = createContext(defaultIcons);
90
99
  function IconsProvider({ children, icons }) {
@@ -105,7 +114,8 @@ function useIcons() {
105
114
  var messages_exports = /* @__PURE__ */ __exportAll({
106
115
  english: () => english,
107
116
  failure: () => failure,
108
- merge: () => merge
117
+ merge: () => merge,
118
+ outcome: () => outcome
109
119
  });
110
120
  const entity = {
111
121
  approval: "approval",
@@ -135,6 +145,10 @@ const recordTypes = {
135
145
  SRV: "SRV",
136
146
  TXT: "TXT"
137
147
  };
148
+ /** The provider the customer acted on, or a neutral stand-in when the flow does not know it. */
149
+ const named = (provider) => provider ?? "The provider";
150
+ /** The domain the failure is about, or a neutral stand-in. */
151
+ const where = (domain) => domain ?? "this domain";
138
152
  /** `accountId` reads as "Account id" until a host names the field itself. */
139
153
  const humanize = (name) => {
140
154
  const spaced = name.replaceAll(/([a-z0-9])([A-Z])/g, "$1 $2").replaceAll(/[_-]+/g, " ").trim();
@@ -142,19 +156,32 @@ const humanize = (name) => {
142
156
  };
143
157
  const english = {
144
158
  connect: "Connect",
145
- connectWith: (method) => method,
159
+ methodToken: "Connect with an API token",
160
+ methodOAuth: (provider) => `Continue with ${provider}`,
161
+ methodIntegration: (provider) => `Install the ${provider} integration`,
146
162
  disconnect: "Disconnect",
147
163
  detach: "Detach domain",
148
164
  cancel: "Cancel",
149
165
  close: "Close",
150
166
  retry: "Try again",
151
167
  approve: "Approve",
168
+ addRecords: (count) => count === 1 ? "Add 1 record" : `Add ${count} records`,
152
169
  decline: "Decline",
170
+ everyRecordConflicts: "Resolve the records above at your provider, then review again.",
153
171
  reviewChanges: "Review changes",
154
172
  cleanUp: "Remove records",
155
173
  checkDns: "Check DNS",
156
174
  checkAgain: "Check again",
157
175
  moreActions: "More actions",
176
+ moreOptions: "Add an account id",
177
+ useAnotherProvider: "Use a different provider",
178
+ useTokenInstead: "Use an API token instead",
179
+ back: "Back",
180
+ connected: "Connected",
181
+ needsReconnect: "Needs reconnecting",
182
+ disconnectScope: "This connection serves other domains.",
183
+ disconnectThisDomain: "Only this domain",
184
+ disconnectEveryDomain: (count) => `All ${count} domains`,
158
185
  copy: "Copy",
159
186
  copied: "Copied",
160
187
  copyZone: "Copy zone file",
@@ -175,13 +202,16 @@ const english = {
175
202
  cleaning: "Removing records…",
176
203
  observing: "Checking DNS…",
177
204
  connectTitle: (provider) => `Connect ${provider}`,
178
- connectAnyTitle: "Connect your DNS provider",
205
+ connectAnyTitle: "Connect a DNS provider",
206
+ hostOwnsZone: "Owns DNS for this domain.",
179
207
  connectDescription: (domain) => `Authorize DNS changes for ${domain}.`,
180
208
  connectedTo: (provider) => `${provider} connected`,
181
209
  notConnected: "No DNS provider is connected.",
182
210
  reconnectRequired: (provider) => `${provider} needs to be reconnected`,
183
211
  disconnectTitle: (provider) => `Disconnect ${provider}?`,
184
- disconnectConsent: "The provider connection is removed. Existing DNS records are preserved.",
212
+ disconnectConsent: (domain, provider) => `DomainKit stops managing DNS for ${domain} through ${provider}.`,
213
+ disconnectWithCleanup: (count) => count === 1 ? "Remove the 1 record DomainKit added" : `Remove the ${count} records DomainKit added`,
214
+ disconnectKeepsRecords: (provider) => `Records stay in ${provider}.`,
185
215
  detachConsent: "This domain is detached from the provider. Existing DNS records are preserved.",
186
216
  detached: "Domain detached. DNS records were preserved.",
187
217
  reusableConnections: "Connections you already have",
@@ -240,9 +270,11 @@ const english = {
240
270
  recordType: (record) => record._tag === "Opaque" ? record.type : recordTypes[record._tag],
241
271
  applied: (receipt) => receipt.status === "complete" ? "DNS records added." : "Some DNS records were not added.",
242
272
  cleaned: (receipt) => receipt.status === "complete" ? "DNS records removed." : "Some DNS records were not removed.",
243
- partiallyApplied: "Some DNS changes failed. Review and retry safely.",
244
- partiallyCleaned: "Some DNS records could not be deleted. Review and retry safely.",
245
- outcome: (outcome) => {
273
+ partiallyAppliedTitle: "Some DNS changes did not land",
274
+ partiallyApplied: "Review what applied, then retry. Retrying is safe.",
275
+ partiallyCleanedTitle: "Some DNS records are still there",
276
+ partiallyCleaned: "Review what is left, then retry. Retrying is safe.",
277
+ receiptOutcome: (outcome) => {
246
278
  switch (outcome._tag) {
247
279
  case "Applied": return "Done";
248
280
  case "Skipped": return "Skipped";
@@ -292,49 +324,99 @@ const english = {
292
324
  nextCheckAt: (at) => `Next check ${at}`,
293
325
  hostEvidence: "Other checks",
294
326
  noEvidence: "Nothing has been checked yet.",
295
- invalidInput: (reason) => reason.field === void 0 ? reason.message : `${humanize(reason.field)}: ${reason.message}`,
296
- unauthenticated: () => "The provider rejected these credentials.",
297
- forbidden: () => "You do not have access to this domain.",
298
- notFound: (reason) => `That ${entity[reason.entity]} no longer exists.`,
299
- conflict: (reason) => reason.operations.length === 1 ? "One DNS record conflicts with what is already in the zone." : `${reason.operations.length} DNS records conflict with what is already in the zone.`,
300
- stale: () => "The zone changed while you were reviewing. Build a new plan.",
301
- expired: (reason) => `That ${expiredEntity[reason.entity]} expired. Start again.`,
302
- busy: () => "Another change is running. Try again in a moment.",
303
- providerRejected: (reason) => `${reason.provider} refused the change: ${reason.message}`,
304
- providerUnavailable: (reason) => `${reason.provider} is unreachable right now.`,
305
- providerConflict: (reason) => `${reason.provider} already holds a conflicting record.`,
306
- unsupported: (reason) => `${reason.provider} cannot ${reason.operation}.`,
307
- reconnect: (reason) => `Reconnect ${reason.provider} to continue.`,
308
- storageFailed: () => "The change could not be saved. Try again.",
309
- cryptoFailed: () => "A stored credential could not be read.",
310
- resolverFailed: (reason) => `DNS lookups through ${reason.resolver} failed.`
327
+ invalidInput: (reason) => ({
328
+ description: reason.message,
329
+ title: reason.field === void 0 ? "Check what you entered" : `Check the ${humanize(reason.field).toLowerCase()}`
330
+ }),
331
+ unauthenticated: () => ({ title: "Token not accepted" }),
332
+ forbidden: (_reason, context) => ({
333
+ description: `The connection needs permission to edit DNS for ${where(context.domain)}.`,
334
+ title: "This connection can't change DNS here"
335
+ }),
336
+ notFound: (reason) => ({
337
+ description: "Reload the page and start this step again.",
338
+ title: `That ${entity[reason.entity]} no longer exists`
339
+ }),
340
+ conflict: (reason) => ({
341
+ description: "Review what the zone already holds, then build a new plan.",
342
+ title: reason.operations.length === 1 ? "A record is in the way" : `${reason.operations.length} records are in the way`
343
+ }),
344
+ stale: () => ({
345
+ description: "Review the new plan before you apply it.",
346
+ title: "The zone changed since you reviewed"
347
+ }),
348
+ expired: (reason) => ({
349
+ description: "Start this step again.",
350
+ title: `That ${expiredEntity[reason.entity]} expired`
351
+ }),
352
+ busy: () => ({
353
+ description: "Wait for it to finish, then try again.",
354
+ title: "Another change is running"
355
+ }),
356
+ providerRejected: (reason, context) => ({
357
+ description: reason.message,
358
+ title: `${named(context.provider ?? reason.provider)} refused the change`
359
+ }),
360
+ providerUnavailable: (reason, context) => ({
361
+ description: "Nothing changed. Try again in a minute.",
362
+ title: `${named(context.provider ?? reason.provider)} isn't responding`
363
+ }),
364
+ providerConflict: (reason, context) => ({
365
+ description: "Remove or rename that record at the provider, then try again.",
366
+ title: `${named(context.provider ?? reason.provider)} already holds a conflicting record`
367
+ }),
368
+ unsupported: (reason, context) => ({
369
+ description: "Nothing changed. This step needs a provider that can do it.",
370
+ title: `${named(context.provider ?? reason.provider)} cannot ${reason.operation}`
371
+ }),
372
+ reconnect: (reason, context) => ({
373
+ description: "The stored credential no longer works. Connect again to continue.",
374
+ title: `Reconnect ${named(context.provider ?? reason.provider)}`
375
+ }),
376
+ storageFailed: () => ({
377
+ description: "Nothing changed. Try again.",
378
+ title: "The change could not be saved"
379
+ }),
380
+ cryptoFailed: () => ({
381
+ description: "Connect the provider again to replace it.",
382
+ title: "A stored credential could not be read"
383
+ }),
384
+ resolverFailed: (reason) => ({
385
+ description: "The lookup failed, which says nothing about your DNS. Try again in a moment.",
386
+ title: `DNS lookups through ${reason.resolver} failed`
387
+ })
311
388
  };
312
389
  const merge = (overrides = {}) => ({
313
390
  ...english,
314
391
  ...overrides
315
392
  });
316
- /** The user-facing sentence for a failure, chosen by the error's reason. */
317
- const failure = (error, catalog) => {
393
+ /** The title and description for a failure, chosen by the error's reason. */
394
+ const outcome = (error, catalog, context = {}) => {
318
395
  const reason = error.reason;
319
396
  switch (reason._tag) {
320
- case "InvalidInput": return catalog.invalidInput(reason);
321
- case "Unauthenticated": return catalog.unauthenticated(reason);
322
- case "Forbidden": return catalog.forbidden(reason);
323
- case "NotFound": return catalog.notFound(reason);
324
- case "Conflict": return catalog.conflict(reason);
325
- case "Stale": return catalog.stale(reason);
326
- case "Expired": return catalog.expired(reason);
327
- case "Busy": return catalog.busy(reason);
328
- case "ProviderRejected": return catalog.providerRejected(reason);
329
- case "ProviderUnavailable": return catalog.providerUnavailable(reason);
330
- case "ProviderConflict": return catalog.providerConflict(reason);
331
- case "Unsupported": return catalog.unsupported(reason);
332
- case "Reconnect": return catalog.reconnect(reason);
333
- case "StorageFailed": return catalog.storageFailed(reason);
334
- case "CryptoFailed": return catalog.cryptoFailed(reason);
335
- case "ResolverFailed": return catalog.resolverFailed(reason);
397
+ case "InvalidInput": return catalog.invalidInput(reason, context);
398
+ case "Unauthenticated": return catalog.unauthenticated(reason, context);
399
+ case "Forbidden": return catalog.forbidden(reason, context);
400
+ case "NotFound": return catalog.notFound(reason, context);
401
+ case "Conflict": return catalog.conflict(reason, context);
402
+ case "Stale": return catalog.stale(reason, context);
403
+ case "Expired": return catalog.expired(reason, context);
404
+ case "Busy": return catalog.busy(reason, context);
405
+ case "ProviderRejected": return catalog.providerRejected(reason, context);
406
+ case "ProviderUnavailable": return catalog.providerUnavailable(reason, context);
407
+ case "ProviderConflict": return catalog.providerConflict(reason, context);
408
+ case "Unsupported": return catalog.unsupported(reason, context);
409
+ case "Reconnect": return catalog.reconnect(reason, context);
410
+ case "StorageFailed": return catalog.storageFailed(reason, context);
411
+ case "CryptoFailed": return catalog.cryptoFailed(reason, context);
412
+ case "ResolverFailed": return catalog.resolverFailed(reason, context);
336
413
  }
337
414
  };
415
+ /** The same failure as one sentence, for a host that renders text rather than a card. */
416
+ const failure = (error, catalog, context = {}) => {
417
+ const { description, title } = outcome(error, catalog, context);
418
+ return description === void 0 ? `${title}.` : `${title}. ${description}`;
419
+ };
338
420
  //#endregion
339
421
  //#region src/theme.ts
340
422
  var theme_exports = /* @__PURE__ */ __exportAll({ toStyle: () => toStyle });
@@ -352,7 +434,8 @@ const variables = {
352
434
  radius: "--domainkit-radius",
353
435
  shadow: "--domainkit-shadow",
354
436
  success: "--domainkit-success",
355
- text: "--domainkit-text"
437
+ text: "--domainkit-text",
438
+ warning: "--domainkit-warning"
356
439
  };
357
440
  function toStyle(theme = {}) {
358
441
  return Object.fromEntries(Object.entries(theme).flatMap(([key, value]) => value === void 0 ? [] : [[variables[key], value]]));
@@ -361,13 +444,13 @@ function toStyle(theme = {}) {
361
444
  //#region src/domain-kit.tsx
362
445
  var domain_kit_exports = /* @__PURE__ */ __exportAll({
363
446
  ReadOnly: () => ReadOnly,
364
- Root: () => Root$6,
447
+ Root: () => Root$7,
365
448
  useCapabilities: () => useCapabilities,
366
449
  useDomainKit: () => useDomainKit,
367
450
  useReadOnly: () => useReadOnly,
368
451
  useTransport: () => useTransport
369
452
  });
370
- const Context = createContext(null);
453
+ const Context$1 = createContext(null);
371
454
  const navigateInBrowser = (url) => {
372
455
  if (typeof window === "undefined") throw new Error("DomainKit provider authorization requires a browser or a `navigate` prop");
373
456
  window.location.assign(url);
@@ -417,7 +500,7 @@ const usePortalContainer = (container) => {
417
500
  const getSnapshot = useCallback(() => resolvePortalContainer(container, ownerDocument), [container, ownerDocument]);
418
501
  return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
419
502
  };
420
- function Root$6({ children, colorScheme = "inherit", icons, marks = {}, messages, navigate = navigateInBrowser, onEvent, portalContainer = null, readOnly = false, revision = 0, theme, transport, ...props }) {
503
+ function Root$7({ children, colorScheme = "inherit", icons, marks = {}, messages, navigate = navigateInBrowser, onEvent, portalContainer = null, readOnly = false, revision = 0, theme, transport, ...props }) {
421
504
  const { capabilities, transport: stable } = useStableTransport(transport);
422
505
  const onEventRef = useRef(onEvent);
423
506
  useLayoutEffect(() => {
@@ -450,7 +533,7 @@ function Root$6({ children, colorScheme = "inherit", icons, marks = {}, messages
450
533
  "data-domainkit-root": "",
451
534
  style: themeStyle
452
535
  });
453
- return /* @__PURE__ */ jsx(Context.Provider, {
536
+ return /* @__PURE__ */ jsx(Context$1.Provider, {
454
537
  value,
455
538
  children: /* @__PURE__ */ jsx(IconsProvider, {
456
539
  ...icons === void 0 ? {} : { icons },
@@ -459,7 +542,7 @@ function Root$6({ children, colorScheme = "inherit", icons, marks = {}, messages
459
542
  });
460
543
  }
461
544
  function useDomainKit() {
462
- const value = useContext(Context);
545
+ const value = useContext(Context$1);
463
546
  if (value === null) throw new Error("DomainKit components must be rendered inside DomainKit.Root");
464
547
  return value;
465
548
  }
@@ -760,6 +843,156 @@ function useAttempt(options) {
760
843
  };
761
844
  }
762
845
  //#endregion
846
+ //#region src/outcome.tsx
847
+ /**
848
+ * What a flow says when a step ends badly: media, title, description, and the action that follows.
849
+ * Every flow's `Outcome` binds its controller to these parts, so a failure reads the same way in
850
+ * the connect dialog, the review dialog, and the verification popover.
851
+ *
852
+ * <Connect.Outcome controller={connection} /> the default composition
853
+ *
854
+ * <Connect.Outcome controller={connection}> a composition of your own
855
+ * <Outcome.Header>
856
+ * <Outcome.Media><MyIcon /></Outcome.Media>
857
+ * <Outcome.Title />
858
+ * <Outcome.Description />
859
+ * </Outcome.Header>
860
+ * <Outcome.Content />
861
+ * </Connect.Outcome>
862
+ */
863
+ var outcome_exports = /* @__PURE__ */ __exportAll({
864
+ Composition: () => Composition,
865
+ Content: () => Content,
866
+ Description: () => Description,
867
+ Header: () => Header$1,
868
+ Media: () => Media,
869
+ Provider: () => Provider,
870
+ Root: () => Root$6,
871
+ Title: () => Title,
872
+ useOutcome: () => useOutcome
873
+ });
874
+ const fallback = {
875
+ description: "",
876
+ layout: "card",
877
+ retry: null,
878
+ retryPart: "flow-retry",
879
+ title: "",
880
+ tone: "danger"
881
+ };
882
+ const Context = createContext(null);
883
+ /** What the surrounding flow resolved, for a part of your own inside `X.Outcome`. */
884
+ function useOutcome() {
885
+ return useContext(Context) ?? fallback;
886
+ }
887
+ /** Binds a resolved outcome to the parts below it. Flows render this; hosts rarely do. */
888
+ function Provider({ children, value }) {
889
+ return /* @__PURE__ */ jsx(Context.Provider, {
890
+ value,
891
+ children
892
+ });
893
+ }
894
+ /**
895
+ * The outcome itself. It announces, so a customer reading with assistive tech hears it. A `tone`
896
+ * or `layout` of its own reaches every part below it, so the glyph and the words agree with the
897
+ * root's own attributes.
898
+ */
899
+ function Root$6({ layout, tone, ...props }) {
900
+ const value = useOutcome();
901
+ const resolved = {
902
+ ...value,
903
+ layout: layout ?? value.layout,
904
+ tone: tone ?? value.tone
905
+ };
906
+ const element = usePart("div", props, {
907
+ layout: resolved.layout,
908
+ tone: resolved.tone
909
+ }, {
910
+ "data-domainkit-part": "outcome",
911
+ "data-layout": resolved.layout,
912
+ "data-tone": resolved.tone,
913
+ role: "alert"
914
+ });
915
+ return /* @__PURE__ */ jsx(Provider, {
916
+ value: resolved,
917
+ children: element
918
+ });
919
+ }
920
+ /** Media, title, and description together, so the action below them can move on its own. */
921
+ function Header$1(props) {
922
+ const { layout, tone } = useOutcome();
923
+ return usePart("div", props, {
924
+ layout,
925
+ tone
926
+ }, { "data-domainkit-part": "outcome-header" });
927
+ }
928
+ /** The glyph for the tone. Children replace it; `icons` on `DomainKit.Root` replaces the set. */
929
+ function Media({ variant = "icon", ...props }) {
930
+ const { layout, tone } = useOutcome();
931
+ const icons = useIcons();
932
+ const glyph = tone === "success" ? icons.success : tone === "warning" ? icons.warning : icons.failure;
933
+ return usePart("div", props, {
934
+ layout,
935
+ tone
936
+ }, {
937
+ "aria-hidden": "true",
938
+ children: glyph,
939
+ "data-domainkit-part": "outcome-media",
940
+ "data-variant": variant
941
+ });
942
+ }
943
+ /** The heading from the catalog. Children win, for a host that words it itself. */
944
+ function Title(props) {
945
+ const { layout, title, tone } = useOutcome();
946
+ return usePart("div", props, {
947
+ layout,
948
+ tone
949
+ }, {
950
+ children: title,
951
+ "data-domainkit-part": "outcome-title"
952
+ });
953
+ }
954
+ /** What to do about it, from the catalog. An outcome whose title says everything renders none. */
955
+ function Description(props) {
956
+ const { description, layout, tone } = useOutcome();
957
+ const element = usePart("p", props, {
958
+ layout,
959
+ tone
960
+ }, {
961
+ children: description,
962
+ "data-domainkit-part": "outcome-description"
963
+ });
964
+ return description === "" && props.children === void 0 ? null : element;
965
+ }
966
+ /**
967
+ * The actions under the header. Without children it is the flow's retry, and a flow that allows
968
+ * none renders nothing: read-only hides every control that would start a write again.
969
+ */
970
+ function Content(props) {
971
+ const { messages } = useDomainKit();
972
+ const { layout, retry, retryPart, tone } = useOutcome();
973
+ const element = usePart("div", props, {
974
+ layout,
975
+ tone
976
+ }, {
977
+ children: retry === null ? null : /* @__PURE__ */ jsx("button", {
978
+ "data-domainkit-part": retryPart,
979
+ onClick: retry,
980
+ type: "button",
981
+ children: messages.retry
982
+ }),
983
+ "data-domainkit-part": "outcome-content"
984
+ });
985
+ return retry === null && props.children === void 0 ? null : element;
986
+ }
987
+ /** Media, title, and description in a header, then the action. What `X.Outcome` renders alone. */
988
+ function Composition() {
989
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Header$1, { children: [
990
+ /* @__PURE__ */ jsx(Media, {}),
991
+ /* @__PURE__ */ jsx(Title, {}),
992
+ /* @__PURE__ */ jsx(Description, {})
993
+ ] }), /* @__PURE__ */ jsx(Content, {})] });
994
+ }
995
+ //#endregion
763
996
  //#region src/records.tsx
764
997
  var records_exports = /* @__PURE__ */ __exportAll({
765
998
  Body: () => Body$1,
@@ -1087,12 +1320,12 @@ function Status$4({ controller, kind, ...props }) {
1087
1320
  switch (state._tag) {
1088
1321
  case "Idle": return "";
1089
1322
  case "Planning": return kind === "provisioning" ? messages.planning : messages.planningCleanup;
1090
- case "Planned": return kind === "provisioning" ? messages.planConsent : messages.cleanupConsent;
1323
+ case "Planned": return "";
1091
1324
  case "Approving": return messages.approving;
1092
1325
  case "Applying": return kind === "provisioning" ? messages.applying : messages.cleaning;
1093
1326
  case "Rejecting": return messages.rejecting;
1094
1327
  case "Rejected": return messages.declinedBy(state.attempt.rejection?.actorId ?? "");
1095
- case "Applied": return kind === "provisioning" ? messages.applied(state.receipt) : messages.cleaned(state.receipt);
1328
+ case "Applied": return "";
1096
1329
  case "Failure": return "";
1097
1330
  }
1098
1331
  };
@@ -1103,28 +1336,47 @@ function Status$4({ controller, kind, ...props }) {
1103
1336
  role: busy(state._tag) ? "status" : void 0
1104
1337
  });
1105
1338
  }
1106
- /** The failure sentence from the error's reason, with the retry the flow allows. */
1107
- function Outcome$4({ controller, kind, ...props }) {
1339
+ /**
1340
+ * How the attempt ended: a failure, a receipt that only partly landed, or one that landed whole.
1341
+ * Media, title, description, and the retry the flow allows. Children replace the composition and
1342
+ * keep the binding.
1343
+ */
1344
+ function Outcome$4({ children, controller, kind, ...props }) {
1108
1345
  const { messages } = useDomainKit();
1109
1346
  const readOnly = useReadOnly();
1110
1347
  const state = controller.state;
1348
+ const retryPart = kind === "provisioning" ? "provisioning-retry" : "cleanup-retry";
1349
+ if (state._tag !== "Failure" && state._tag !== "Applied") return null;
1111
1350
  const partial = state._tag === "Applied" && state.receipt.status === "partial";
1112
- const element = usePart("p", props, { status: state._tag }, {
1113
- children: state._tag === "Failure" ? /* @__PURE__ */ jsxs(Fragment, { children: [failure(state.error, messages), readOnly ? null : /* @__PURE__ */ jsxs(Fragment, { children: [" ", /* @__PURE__ */ jsx("button", {
1114
- "data-domainkit-part": kind === "provisioning" ? "provisioning-retry" : "cleanup-retry",
1115
- onClick: controller.retry,
1116
- type: "button",
1117
- children: messages.retry
1118
- })] })] }) : partial ? kind === "provisioning" ? messages.partiallyApplied : messages.partiallyCleaned : null,
1119
- "data-domainkit-part": "flow-outcome",
1120
- "data-tone": "danger",
1121
- role: "alert"
1351
+ const words = state._tag === "Failure" ? outcome(state.error, messages) : partial ? {
1352
+ description: kind === "provisioning" ? messages.partiallyApplied : messages.partiallyCleaned,
1353
+ title: kind === "provisioning" ? messages.partiallyAppliedTitle : messages.partiallyCleanedTitle
1354
+ } : {
1355
+ description: "",
1356
+ title: kind === "provisioning" ? messages.applied(state.receipt) : messages.cleaned(state.receipt)
1357
+ };
1358
+ return /* @__PURE__ */ jsx(Provider, {
1359
+ value: {
1360
+ description: words.description ?? "",
1361
+ layout: props.layout ?? "card",
1362
+ retry: readOnly || state._tag !== "Failure" ? null : controller.retry,
1363
+ retryPart,
1364
+ title: words.title,
1365
+ tone: state._tag === "Failure" ? "danger" : partial ? "warning" : "success"
1366
+ },
1367
+ children: /* @__PURE__ */ jsx(Root$6, {
1368
+ ...props,
1369
+ children: children ?? /* @__PURE__ */ jsx(Composition, {})
1370
+ })
1122
1371
  });
1123
- return state._tag === "Failure" || partial ? element : null;
1124
1372
  }
1373
+ /** What a plan would actually write: an operation blocked by a conflict is not one of them. */
1374
+ const addable = (plan) => plan === null ? [] : plan.operations.filter((operation) => operation._tag !== "Conflict");
1125
1375
  /**
1126
- * Approve and Decline. Approve authorizes the digest and applies it; Decline records the refusal
1127
- * and ends the attempt. Neither renders until there is a plan to act on.
1376
+ * The primary action, and Decline. The primary authorizes the digest and applies it in one step,
1377
+ * so it says what it will do; where a conflict blocks part of a plan it approves the rest by id,
1378
+ * and where every operation is blocked it says what to fix instead. Neither renders until there is
1379
+ * a plan to act on.
1128
1380
  */
1129
1381
  function Actions$2({ controller, kind, ...props }) {
1130
1382
  const { messages } = useDomainKit();
@@ -1132,24 +1384,33 @@ function Actions$2({ controller, kind, ...props }) {
1132
1384
  const state = controller.state;
1133
1385
  const plan = state._tag === "Planned" ? state.plan : null;
1134
1386
  const running = busy(state._tag);
1387
+ const writes = addable(plan);
1388
+ const blocked = plan !== null && writes.length === 0;
1135
1389
  const element = usePart("div", props, { status: state._tag }, {
1136
- children: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("button", {
1137
- "data-domainkit-part": kind === "provisioning" ? "plan-apply" : "cleanup-apply",
1138
- disabled: running || plan === null || plan.operations.length === 0,
1139
- onClick: () => controller.approve(),
1140
- type: "button",
1141
- children: messages.approve
1142
- }), /* @__PURE__ */ jsx("button", {
1143
- "data-domainkit-part": "plan-decline",
1144
- disabled: running || plan === null,
1145
- onClick: () => controller.reject(),
1146
- type: "button",
1147
- children: messages.decline
1148
- })] }),
1390
+ children: /* @__PURE__ */ jsxs(Fragment, { children: [
1391
+ /* @__PURE__ */ jsx("button", {
1392
+ "data-domainkit-part": kind === "provisioning" ? "plan-apply" : "cleanup-apply",
1393
+ disabled: running || plan === null || writes.length === 0,
1394
+ onClick: () => controller.approve(writes.length === plan?.operations.length ? void 0 : writes.map((operation) => operation.id)),
1395
+ type: "button",
1396
+ children: kind === "provisioning" ? messages.addRecords(writes.length) : messages.approve
1397
+ }),
1398
+ blocked ? /* @__PURE__ */ jsx("p", {
1399
+ "data-domainkit-part": "plan-blocked",
1400
+ children: messages.everyRecordConflicts
1401
+ }) : null,
1402
+ /* @__PURE__ */ jsx("button", {
1403
+ "data-domainkit-part": "plan-decline",
1404
+ disabled: running || plan === null,
1405
+ onClick: () => controller.reject(),
1406
+ type: "button",
1407
+ children: messages.decline
1408
+ })
1409
+ ] }),
1149
1410
  "data-domainkit-part": "review-actions"
1150
1411
  });
1151
1412
  if (readOnly) return null;
1152
- return plan === null && !running ? null : element;
1413
+ return plan === null ? null : element;
1153
1414
  }
1154
1415
  /** The operations under review, or the reason there is nothing to review. */
1155
1416
  function Body({ controller, kind }) {
@@ -1184,10 +1445,17 @@ function Dialog$4({ children, controller, kind, onOpenChange, open, render, trig
1184
1445
  const readOnly = useReadOnly();
1185
1446
  const [uncontrolled, setUncontrolled] = useState(false);
1186
1447
  const isOpen = open ?? uncontrolled;
1187
- const setOpen = (next) => {
1448
+ const close = useRef(() => {});
1449
+ close.current = (next) => {
1188
1450
  if (open === void 0) setUncontrolled(next);
1189
1451
  onOpenChange?.(next);
1190
1452
  };
1453
+ const setOpen = (next) => close.current(next);
1454
+ const state = controller.state;
1455
+ const landed = state._tag === "Applied" && state.receipt.status === "complete";
1456
+ useEffect(() => {
1457
+ if (landed) close.current(false);
1458
+ }, [landed]);
1191
1459
  const running = busy(controller.state._tag);
1192
1460
  const body = children ?? /* @__PURE__ */ jsx(Body, {
1193
1461
  controller,
@@ -1257,7 +1525,7 @@ function Dialog$4({ children, controller, kind, onOpenChange, open, render, trig
1257
1525
  children: [/* @__PURE__ */ jsx(Actions$2, {
1258
1526
  controller,
1259
1527
  kind
1260
- }), running ? null : /* @__PURE__ */ jsx(Dialog.Close, {
1528
+ }), running || kind === "provisioning" ? null : /* @__PURE__ */ jsx(Dialog.Close, {
1261
1529
  "data-domainkit-part": "dialog-cancel",
1262
1530
  children: messages.cancel
1263
1531
  })]
@@ -1349,13 +1617,16 @@ function Flow$3({ domain, onCleaned, receiptId, trigger, ...props }) {
1349
1617
  ...onCleaned === void 0 ? {} : { onCleaned },
1350
1618
  ...receiptId === void 0 ? {} : { receiptId }
1351
1619
  });
1620
+ const [open, setOpen] = useState(false);
1352
1621
  return /* @__PURE__ */ jsxs(Root$3, {
1353
1622
  controller,
1354
1623
  ...props,
1355
1624
  children: [/* @__PURE__ */ jsx(Dialog$3, {
1356
1625
  controller,
1626
+ onOpenChange: setOpen,
1627
+ open,
1357
1628
  ...trigger === void 0 ? {} : { trigger }
1358
- }), /* @__PURE__ */ jsx(Outcome$3, { controller })]
1629
+ }), open ? null : /* @__PURE__ */ jsx(Outcome$3, { controller })]
1359
1630
  });
1360
1631
  }
1361
1632
  //#endregion
@@ -1385,15 +1656,41 @@ function Mark({ provider, ...props }) {
1385
1656
  var connect_exports = /* @__PURE__ */ __exportAll({
1386
1657
  Card: () => Card,
1387
1658
  Dialog: () => Dialog$2,
1659
+ DisconnectDialog: () => DisconnectDialog,
1388
1660
  Flow: () => Flow$2,
1389
1661
  Form: () => Form,
1390
1662
  Outcome: () => Outcome$2,
1663
+ Prompt: () => Prompt,
1391
1664
  Root: () => Root$2,
1392
1665
  State: () => State$1,
1393
1666
  Status: () => Status$2,
1667
+ answeredInPlace: () => answeredInPlace,
1668
+ holdsConnection: () => holdsConnection,
1669
+ hostProvider: () => hostProvider,
1670
+ offering: () => offering,
1394
1671
  useController: () => useController$2
1395
1672
  });
1396
1673
  const State$1 = Data.taggedEnum();
1674
+ /**
1675
+ * An interactive method leaves the page and comes back, so what the library knew is gone with it.
1676
+ * It writes down the domain it sent the customer away for, and the load that follows reads it once:
1677
+ * that, not a query parameter a host would have to keep, is how a return is told from a reload.
1678
+ */
1679
+ const RETURNING = "domainkit.returning";
1680
+ const markReturn = (domain) => {
1681
+ try {
1682
+ sessionStorage.setItem(RETURNING, domain);
1683
+ } catch {}
1684
+ };
1685
+ const tookReturn = (domain) => {
1686
+ try {
1687
+ if (sessionStorage.getItem(RETURNING) !== domain) return false;
1688
+ sessionStorage.removeItem(RETURNING);
1689
+ return true;
1690
+ } catch {
1691
+ return false;
1692
+ }
1693
+ };
1397
1694
  /** Read at connect time, not at render, so it is the page the customer is actually on. */
1398
1695
  const currentUrl = () => typeof window === "undefined" ? null : window.location.href;
1399
1696
  const snapshotOf = (state) => {
@@ -1403,9 +1700,9 @@ const snapshotOf = (state) => {
1403
1700
  case "Disconnected": return state.snapshot;
1404
1701
  case "Loading":
1405
1702
  case "Submitting":
1406
- case "SelectionRequired": return state.snapshot;
1407
- case "Redirecting":
1408
- case "Failure": return null;
1703
+ case "SelectionRequired":
1704
+ case "Failure": return state.snapshot;
1705
+ case "Redirecting": return null;
1409
1706
  }
1410
1707
  };
1411
1708
  const settled = (snapshot, discovery) => {
@@ -1435,6 +1732,8 @@ function useController$2({ domain, returnTo }) {
1435
1732
  snapshot: null
1436
1733
  });
1437
1734
  const lastCommand = useRef(null);
1735
+ const attempted = useRef(null);
1736
+ const [established, setEstablished] = useState(0);
1438
1737
  const [inspected, setInspected] = useState(domain);
1439
1738
  if (inspected !== domain) {
1440
1739
  setInspected(domain);
@@ -1448,7 +1747,12 @@ function useController$2({ domain, returnTo }) {
1448
1747
  setState(State$1.Loading({ snapshot: null }));
1449
1748
  }
1450
1749
  const onFailure = useCallback((error) => {
1451
- setState(State$1.Failure({ error }));
1750
+ setState(State$1.Failure({
1751
+ attempt: attempted.current,
1752
+ discovery: held.current.discovery,
1753
+ error,
1754
+ snapshot: held.current.snapshot
1755
+ }));
1452
1756
  emit(Event.Failed({
1453
1757
  domain,
1454
1758
  error
@@ -1457,6 +1761,7 @@ function useController$2({ domain, returnTo }) {
1457
1761
  const load = useCallback(() => {
1458
1762
  if (connection === void 0) return;
1459
1763
  lastCommand.current = null;
1764
+ attempted.current = null;
1460
1765
  held.current = {
1461
1766
  discovery: null,
1462
1767
  domain,
@@ -1478,6 +1783,7 @@ function useController$2({ domain, returnTo }) {
1478
1783
  domain,
1479
1784
  snapshot
1480
1785
  };
1786
+ if (tookReturn(domain) && snapshot.status === "connected") setEstablished((count) => count + 1);
1481
1787
  setState(settled(snapshot, discovery));
1482
1788
  }
1483
1789
  });
@@ -1497,6 +1803,7 @@ function useController$2({ domain, returnTo }) {
1497
1803
  domain,
1498
1804
  snapshot: result.snapshot
1499
1805
  };
1806
+ setEstablished((count) => count + 1);
1500
1807
  setState(settled(result.snapshot, null));
1501
1808
  if (result.snapshot.connectionId !== null) emit(Event.Connected({
1502
1809
  connectionId: result.snapshot.connectionId,
@@ -1505,6 +1812,7 @@ function useController$2({ domain, returnTo }) {
1505
1812
  }));
1506
1813
  return;
1507
1814
  case "Redirect":
1815
+ markReturn(domain);
1508
1816
  setState(State$1.Redirecting({ url: result.authorizationUrl }));
1509
1817
  navigate(result.authorizationUrl);
1510
1818
  return;
@@ -1521,10 +1829,14 @@ function useController$2({ domain, returnTo }) {
1521
1829
  emit,
1522
1830
  navigate
1523
1831
  ]);
1524
- const submit = useCallback((effect) => {
1832
+ const submit = useCallback((effect, attempt) => {
1525
1833
  if (held.current.domain !== domain) return;
1834
+ attempted.current = attempt;
1526
1835
  const command = () => {
1527
- setState(State$1.Submitting({ snapshot: held.current.snapshot }));
1836
+ setState(State$1.Submitting({
1837
+ discovery: held.current.discovery,
1838
+ snapshot: held.current.snapshot
1839
+ }));
1528
1840
  runner.run(effect, {
1529
1841
  onFailure,
1530
1842
  onSuccess: started
@@ -1550,7 +1862,10 @@ function useController$2({ domain, returnTo }) {
1550
1862
  domain,
1551
1863
  method,
1552
1864
  provider: input.provider
1553
- }));
1865
+ }), {
1866
+ method: input.method,
1867
+ provider: input.provider
1868
+ });
1554
1869
  }, [
1555
1870
  connection,
1556
1871
  domain,
@@ -1563,7 +1878,7 @@ function useController$2({ domain, returnTo }) {
1563
1878
  connectionId: input.connectionId,
1564
1879
  domain,
1565
1880
  ...input.zone === void 0 ? {} : { zone: input.zone }
1566
- }));
1881
+ }), null);
1567
1882
  }, [
1568
1883
  connection,
1569
1884
  domain,
@@ -1578,8 +1893,12 @@ function useController$2({ domain, returnTo }) {
1578
1893
  }, [reuse, state]);
1579
1894
  const release = useCallback((effect, event) => {
1580
1895
  if (held.current.domain !== domain) return;
1896
+ attempted.current = null;
1581
1897
  const command = () => {
1582
- setState(State$1.Submitting({ snapshot: held.current.snapshot }));
1898
+ setState(State$1.Submitting({
1899
+ discovery: held.current.discovery,
1900
+ snapshot: held.current.snapshot
1901
+ }));
1583
1902
  runner.run(effect, {
1584
1903
  onFailure,
1585
1904
  onSuccess: () => {
@@ -1634,7 +1953,9 @@ function useController$2({ domain, returnTo }) {
1634
1953
  connect,
1635
1954
  detach,
1636
1955
  disconnect,
1637
- discovery: state._tag === "Disconnected" ? state.discovery : null,
1956
+ domain,
1957
+ established,
1958
+ discovery: state._tag === "Disconnected" || state._tag === "Failure" || state._tag === "Submitting" ? state.discovery : null,
1638
1959
  providers: snapshotOf(state)?.providers ?? [],
1639
1960
  refresh: load,
1640
1961
  retry,
@@ -1650,28 +1971,63 @@ function Root$2({ controller, ...props }) {
1650
1971
  "data-state": controller.state._tag
1651
1972
  });
1652
1973
  }
1653
- /** The failure sentence, chosen by the error's reason, plus the retry that reason allows. */
1654
- function Outcome$2({ controller, ...props }) {
1974
+ /**
1975
+ * Whether a failure is already answered where it was raised: beside the method the customer used.
1976
+ * A flow rendering its own `Outcome` beside the dialog skips it, so the failure announces once.
1977
+ */
1978
+ const answeredInPlace = (controller) => controller.state._tag === "Failure" && controller.state.attempt !== null;
1979
+ /**
1980
+ * Whether the surface should read as connected: it is, or a command it started is in flight over a
1981
+ * connection that was. The card and the dialog it opened stay on screen for the length of the
1982
+ * command rather than vanishing under the customer half way through it.
1983
+ */
1984
+ const holdsConnection = (controller) => controller.state._tag === "Connected" || controller.state._tag === "Submitting" && controller.snapshot?.status === "connected";
1985
+ /**
1986
+ * The provider whose nameservers serve this domain, as the snapshot describes it. Discovery names
1987
+ * it by id; without a descriptor there is nothing to draw, so there is no host.
1988
+ */
1989
+ const hostProvider = (controller) => {
1990
+ const discovery = controller.discovery;
1991
+ if (discovery === null || discovery._tag !== "NotFound" || discovery.host === null) return null;
1992
+ const named = discovery.host.provider;
1993
+ return controller.providers.find((entry) => entry.id === named) ?? null;
1994
+ };
1995
+ /** The provider as the customer knows it, falling back to the id when the snapshot has no name. */
1996
+ const displayName = (controller, provider) => controller.providers.find((entry) => entry.id === provider)?.name ?? provider;
1997
+ /**
1998
+ * The last failure as a card: media, the catalog's title and description, and the retry the flow
1999
+ * allows. Children replace the composition and keep the binding.
2000
+ */
2001
+ function Outcome$2({ children, controller, ...props }) {
1655
2002
  const { messages } = useDomainKit();
1656
2003
  const readOnly = useReadOnly();
1657
2004
  const state = controller.state;
1658
- const element = usePart("p", props, { status: state._tag }, {
1659
- children: state._tag === "Failure" ? /* @__PURE__ */ jsxs(Fragment, { children: [failure(state.error, messages), readOnly ? null : /* @__PURE__ */ jsxs(Fragment, { children: [" ", /* @__PURE__ */ jsx("button", {
1660
- "data-domainkit-part": "connection-retry",
1661
- onClick: controller.retry,
1662
- type: "button",
1663
- children: messages.retry
1664
- })] })] }) : null,
1665
- "data-domainkit-part": "flow-outcome",
1666
- "data-tone": "danger",
1667
- role: "alert"
2005
+ if (state._tag !== "Failure") return null;
2006
+ const acted = state.attempt === null ? void 0 : displayName(controller, state.attempt.provider);
2007
+ const words = outcome(state.error, messages, {
2008
+ ...controller.snapshot === null ? {} : { domain: controller.snapshot.domain },
2009
+ ...acted === void 0 ? {} : { provider: acted }
2010
+ });
2011
+ return /* @__PURE__ */ jsx(Provider, {
2012
+ value: {
2013
+ description: words.description ?? "",
2014
+ layout: props.layout ?? "card",
2015
+ retry: readOnly ? null : controller.retry,
2016
+ retryPart: "connection-retry",
2017
+ title: words.title,
2018
+ tone: "danger"
2019
+ },
2020
+ children: /* @__PURE__ */ jsx(Root$6, {
2021
+ ...props,
2022
+ children: children ?? /* @__PURE__ */ jsx(Composition, {})
2023
+ })
1668
2024
  });
1669
- return state._tag === "Failure" ? element : null;
1670
2025
  }
1671
2026
  function Status$2({ controller, ...props }) {
1672
2027
  const { messages } = useDomainKit();
1673
2028
  const state = controller.state;
1674
- const provider = controller.snapshot?.provider ?? "";
2029
+ const attached = controller.snapshot?.provider ?? null;
2030
+ const provider = attached === null ? "" : displayName(controller, attached);
1675
2031
  const text = () => {
1676
2032
  switch (state._tag) {
1677
2033
  case "Loading": return messages.loading;
@@ -1692,14 +2048,83 @@ function Status$2({ controller, ...props }) {
1692
2048
  });
1693
2049
  }
1694
2050
  /**
2051
+ * The one field a rejected command was about, if any. A provider that turns down credentials is
2052
+ * answering the secret it was given, and it names no field, so the first secret carries the
2053
+ * answer; anything else is about the request rather than one input.
2054
+ */
2055
+ const rejected = (reason, fields) => {
2056
+ if (reason === null) return null;
2057
+ if (reason._tag === "InvalidInput") return fields.find((field) => field.name === reason.field)?.name ?? null;
2058
+ if (reason._tag !== "Unauthenticated" && reason._tag !== "Forbidden") return null;
2059
+ return fields.find((field) => field.secret)?.name ?? null;
2060
+ };
2061
+ /**
2062
+ * One declared field in shadcn's `Field` anatomy: the label, the control, the note under it, and
2063
+ * the error the provider answered with. The docs link rides the field it explains. The value is
2064
+ * the form's, not the input's, so a rejection answers beside what the customer typed.
2065
+ */
2066
+ function TokenField({ controller, docsUrl, field, invalid, onEnter, value }) {
2067
+ const { messages } = useDomainKit();
2068
+ const icons = useIcons();
2069
+ const id = useId();
2070
+ return /* @__PURE__ */ jsxs("div", {
2071
+ "data-domainkit-part": "field",
2072
+ "data-invalid": invalid ? "" : void 0,
2073
+ "data-orientation": "vertical",
2074
+ children: [
2075
+ /* @__PURE__ */ jsxs("label", {
2076
+ "data-domainkit-part": "field-label",
2077
+ htmlFor: id,
2078
+ children: [messages.fieldLabel(field.name), field.required ? null : /* @__PURE__ */ jsx("span", {
2079
+ "data-domainkit-part": "field-optional",
2080
+ children: messages.optionalField
2081
+ })]
2082
+ }),
2083
+ /* @__PURE__ */ jsx("input", {
2084
+ "aria-invalid": invalid ? true : void 0,
2085
+ autoComplete: "off",
2086
+ "data-domainkit-part": "field-input",
2087
+ id,
2088
+ name: field.name,
2089
+ onChange: (event) => onEnter(event.target.value),
2090
+ required: field.required,
2091
+ type: field.secret ? "password" : "text",
2092
+ value
2093
+ }),
2094
+ docsUrl === null ? null : /* @__PURE__ */ jsxs("a", {
2095
+ "data-domainkit-part": "field-description",
2096
+ href: docsUrl,
2097
+ rel: "noreferrer",
2098
+ target: "_blank",
2099
+ children: [messages.getToken, /* @__PURE__ */ jsx("span", {
2100
+ "aria-hidden": "true",
2101
+ "data-icon": "inline-end",
2102
+ children: icons.external
2103
+ })]
2104
+ }),
2105
+ invalid ? /* @__PURE__ */ jsx("div", {
2106
+ "data-domainkit-part": "field-error",
2107
+ children: /* @__PURE__ */ jsx(Outcome$2, {
2108
+ controller,
2109
+ layout: "inline"
2110
+ })
2111
+ }) : null
2112
+ ]
2113
+ });
2114
+ }
2115
+ /**
1695
2116
  * One auth method, rendered from its descriptor. A token method draws an input per declared
1696
- * field, so a provider that needs an account id alongside a token needs no code here.
2117
+ * field, so a provider that needs an account id alongside a token needs no code here. The fields
2118
+ * a provider does not need sit behind a disclosure, so the common form is one field.
1697
2119
  */
1698
- function Method({ controller, method, provider }) {
2120
+ function Method({ controller, method, onEnter, provider, values }) {
1699
2121
  const { messages } = useDomainKit();
1700
2122
  const icons = useIcons();
1701
- const busy = controller.state._tag === "Submitting";
1702
- if (method.fields === null) return /* @__PURE__ */ jsx("button", {
2123
+ const [more, setMore] = useState(false);
2124
+ const state = controller.state;
2125
+ const busy = state._tag === "Submitting";
2126
+ const failed = state._tag === "Failure" && state.attempt !== null && state.attempt.provider === provider.id && state.attempt.method === method.kind;
2127
+ if (method.fields === null) return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("button", {
1703
2128
  "data-domainkit-part": method.kind === "oauth" ? "oauth-connect" : "integration-connect",
1704
2129
  "data-provider": provider.id,
1705
2130
  disabled: busy,
@@ -1708,70 +2133,138 @@ function Method({ controller, method, provider }) {
1708
2133
  provider: provider.id
1709
2134
  }),
1710
2135
  type: "button",
1711
- children: messages.connectWith(method.label)
1712
- });
2136
+ children: method.kind === "oauth" ? messages.methodOAuth(provider.name) : messages.methodIntegration(provider.name)
2137
+ }), failed ? /* @__PURE__ */ jsx(Outcome$2, {
2138
+ controller,
2139
+ layout: "inline"
2140
+ }) : null] });
1713
2141
  const fields = method.fields;
2142
+ const answered = rejected(failed && state._tag === "Failure" ? state.error.reason : null, fields);
2143
+ const required = fields.filter((field) => field.required);
2144
+ const optional = fields.filter((field) => !field.required);
2145
+ const showMore = more || optional.some((field) => field.name === answered);
2146
+ const explains = (required.find((field) => field.secret) ?? required[0] ?? optional[0])?.name;
2147
+ const render = (field) => /* @__PURE__ */ jsx(TokenField, {
2148
+ controller,
2149
+ docsUrl: field.name === explains ? method.docsUrl : null,
2150
+ field,
2151
+ invalid: field.name === answered,
2152
+ onEnter: (value) => onEnter(field.name, value),
2153
+ value: values[field.name] ?? ""
2154
+ }, field.name);
1714
2155
  return /* @__PURE__ */ jsxs("form", {
1715
2156
  "data-domainkit-part": "token-connect",
1716
2157
  "data-provider": provider.id,
1717
2158
  onSubmit: (event) => {
1718
2159
  event.preventDefault();
1719
- const data = new FormData(event.currentTarget);
1720
- const values = {};
2160
+ const submitted = {};
1721
2161
  for (const field of fields) {
1722
- const value = data.get(field.name);
1723
- if (typeof value === "string" && (value !== "" || field.required)) values[field.name] = value;
2162
+ const value = values[field.name] ?? "";
2163
+ if (value !== "" || field.required) submitted[field.name] = value;
1724
2164
  }
1725
2165
  controller.connect({
1726
2166
  method: method.kind,
1727
2167
  provider: provider.id,
1728
- values
2168
+ values: submitted
1729
2169
  });
1730
2170
  },
1731
2171
  children: [
1732
- fields.map((field) => /* @__PURE__ */ jsxs("label", {
1733
- "data-domainkit-part": "token-field",
1734
- children: [/* @__PURE__ */ jsxs("span", {
1735
- "data-domainkit-part": "token-field-label",
1736
- children: [messages.fieldLabel(field.name), field.required ? null : /* @__PURE__ */ jsx("span", {
1737
- "data-domainkit-part": "token-field-optional",
1738
- children: messages.optionalField
2172
+ required.map(render),
2173
+ optional.length === 0 ? null : /* @__PURE__ */ jsxs(Collapsible.Root, {
2174
+ "data-domainkit-part": "more-options",
2175
+ "data-state": showMore ? "open" : "closed",
2176
+ onOpenChange: setMore,
2177
+ open: showMore,
2178
+ children: [/* @__PURE__ */ jsxs(Collapsible.Trigger, {
2179
+ "data-domainkit-part": "more-options-trigger",
2180
+ children: [messages.moreOptions, /* @__PURE__ */ jsx("span", {
2181
+ "aria-hidden": "true",
2182
+ "data-icon": "inline-end",
2183
+ children: icons.chevron
1739
2184
  })]
1740
- }), /* @__PURE__ */ jsx("input", {
1741
- autoComplete: "off",
1742
- "data-domainkit-part": "token-field-input",
1743
- name: field.name,
1744
- required: field.required,
1745
- type: field.secret ? "password" : "text"
1746
- })]
1747
- }, field.name)),
1748
- method.docsUrl === null ? null : /* @__PURE__ */ jsxs("a", {
1749
- "data-domainkit-part": "token-docs",
1750
- href: method.docsUrl,
1751
- rel: "noreferrer",
1752
- target: "_blank",
1753
- children: [messages.getToken, /* @__PURE__ */ jsx("span", {
1754
- "aria-hidden": "true",
1755
- "data-icon": "inline-end",
1756
- children: icons.external
2185
+ }), /* @__PURE__ */ jsx(Collapsible.Panel, {
2186
+ "data-domainkit-part": "more-options-panel",
2187
+ children: optional.map(render)
1757
2188
  })]
1758
2189
  }),
2190
+ failed && answered === null ? /* @__PURE__ */ jsx(Outcome$2, {
2191
+ controller,
2192
+ layout: "inline"
2193
+ }) : null,
1759
2194
  /* @__PURE__ */ jsx("button", {
1760
2195
  "data-domainkit-part": "token-submit",
1761
2196
  disabled: busy,
1762
2197
  type: "submit",
1763
- children: messages.connectWith(method.label)
2198
+ children: messages.methodToken
1764
2199
  })
1765
2200
  ]
1766
2201
  });
1767
2202
  }
1768
2203
  /**
2204
+ * One provider's methods, in the order the descriptor declares them: the interactive ones a
2205
+ * customer clicks through, then the token. Where a provider offers both, the interactive method is
2206
+ * the offer and a plain button opens the token form in its place, so the dialog asks for one
2207
+ * decision at a time. With a heading the whole provider is a disclosure the customer opens; the
2208
+ * provider a narrowed dialog is about carries no heading, because the dialog title already names
2209
+ * it.
2210
+ */
2211
+ function Authentication({ controller, onEnter, onOpen, onType, open, provider, typing, values }) {
2212
+ const { messages } = useDomainKit();
2213
+ const state = controller.state;
2214
+ const render = (method) => /* @__PURE__ */ jsx(Method, {
2215
+ controller,
2216
+ method,
2217
+ onEnter,
2218
+ provider,
2219
+ values
2220
+ }, `${provider.id}:${method.kind}`);
2221
+ const interactive = provider.methods.filter((method) => method.fields === null);
2222
+ const typed = provider.methods.filter((method) => method.fields !== null);
2223
+ const methods = !(interactive.length > 0 && typed.length > 0) ? provider.methods.map(render) : typing ? typed.map(render) : /* @__PURE__ */ jsxs(Fragment, { children: [interactive.map(render), /* @__PURE__ */ jsx("button", {
2224
+ "data-domainkit-part": "method-alternate",
2225
+ "data-provider": provider.id,
2226
+ disabled: state._tag === "Submitting",
2227
+ onClick: onType,
2228
+ type: "button",
2229
+ children: messages.useTokenInstead
2230
+ })] });
2231
+ return /* @__PURE__ */ jsxs("section", {
2232
+ "data-domainkit-part": "provider-authentication",
2233
+ "data-provider": provider.id,
2234
+ "data-state": open ? "open" : "closed",
2235
+ children: [onOpen === void 0 ? null : /* @__PURE__ */ jsxs("button", {
2236
+ "aria-expanded": open,
2237
+ "data-domainkit-part": "provider-heading",
2238
+ onClick: onOpen,
2239
+ type: "button",
2240
+ children: [/* @__PURE__ */ jsx(Mark, { provider }), /* @__PURE__ */ jsx("strong", { children: provider.name })]
2241
+ }), open ? methods : null]
2242
+ });
2243
+ }
2244
+ /**
1769
2245
  * Everything a disconnected domain can do: a connection discovery already found, the connections
1770
2246
  * this owner already has, then each provider's declared methods.
1771
2247
  */
1772
- function Form({ controller, ...props }) {
2248
+ function Form({ controller, provider: narrowed, ...props }) {
1773
2249
  const { messages } = useDomainKit();
2250
+ const [opened, setOpened] = useState(null);
2251
+ const [entered, setEntered] = useState(null);
1774
2252
  const state = controller.state;
2253
+ const owner = controller.domain;
2254
+ if (entered !== null && (entered.domain !== owner || state._tag === "Connected")) setEntered(null);
2255
+ const kept = entered?.domain === owner ? entered : null;
2256
+ const enter = (provider, name, value) => setEntered((previous) => {
2257
+ const held = previous?.domain === owner && previous.provider === provider ? previous : null;
2258
+ return {
2259
+ domain: owner,
2260
+ provider,
2261
+ values: held === null ? { [name]: value } : {
2262
+ ...held.values,
2263
+ [name]: value
2264
+ }
2265
+ };
2266
+ });
2267
+ const valuesFor = (provider) => kept?.provider === provider ? kept.values : {};
1775
2268
  const snapshot = controller.snapshot;
1776
2269
  const discovery = controller.discovery;
1777
2270
  const candidates = state._tag === "SelectionRequired" ? state.candidates.map((candidate) => ({
@@ -1781,6 +2274,18 @@ function Form({ controller, ...props }) {
1781
2274
  const resolved = discovery !== null && discovery._tag === "Resolved" ? discovery : null;
1782
2275
  const reusable = snapshot?.reusable ?? [];
1783
2276
  const providers = controller.providers;
2277
+ const host = providers.find((entry) => entry.id === narrowed) ?? hostProvider(controller);
2278
+ const [typing, setTyping] = useState(null);
2279
+ const authentication = (provider, open) => /* @__PURE__ */ jsx(Authentication, {
2280
+ controller,
2281
+ onEnter: (name, value) => enter(provider.id, name, value),
2282
+ onOpen: () => setOpened(open ? "" : provider.id),
2283
+ onType: () => setTyping(provider.id),
2284
+ open,
2285
+ provider,
2286
+ typing: typing === provider.id,
2287
+ values: valuesFor(provider.id)
2288
+ }, provider.id);
1784
2289
  return usePart("div", props, { status: state._tag }, {
1785
2290
  children: /* @__PURE__ */ jsxs(Fragment, { children: [
1786
2291
  resolved === null ? null : /* @__PURE__ */ jsxs("section", {
@@ -1827,17 +2332,20 @@ function Form({ controller, ...props }) {
1827
2332
  providers.length === 0 ? /* @__PURE__ */ jsx("p", {
1828
2333
  "data-domainkit-part": "target-unavailable",
1829
2334
  children: messages.noProviders
1830
- }) : providers.map((provider) => /* @__PURE__ */ jsxs("section", {
1831
- "data-domainkit-part": "provider-authentication",
1832
- children: [/* @__PURE__ */ jsxs("div", {
1833
- "data-domainkit-part": "provider-heading",
1834
- children: [/* @__PURE__ */ jsx(Mark, { provider }), /* @__PURE__ */ jsx("strong", { children: provider.name })]
1835
- }), provider.methods.map((method) => /* @__PURE__ */ jsx(Method, {
1836
- controller,
1837
- method,
1838
- provider
1839
- }, `${provider.id}:${method.kind}`))]
1840
- }, provider.id))
2335
+ }) : host === null ? providers.map((provider) => authentication(provider, opened === null ? provider.id === providers[0]?.id : opened === provider.id)) : /* @__PURE__ */ jsxs(Fragment, { children: [typing === host.id ? /* @__PURE__ */ jsx("button", {
2336
+ "data-domainkit-part": "dialog-back",
2337
+ onClick: () => setTyping(null),
2338
+ type: "button",
2339
+ children: messages.back
2340
+ }) : null, /* @__PURE__ */ jsx(Authentication, {
2341
+ controller,
2342
+ onEnter: (name, value) => enter(host.id, name, value),
2343
+ onType: () => setTyping(host.id),
2344
+ open: true,
2345
+ provider: host,
2346
+ typing: typing === host.id,
2347
+ values: valuesFor(host.id)
2348
+ })] })
1841
2349
  ] }),
1842
2350
  "data-domainkit-part": "connection-form"
1843
2351
  });
@@ -1845,19 +2353,32 @@ function Form({ controller, ...props }) {
1845
2353
  /** The connect surface. Without `render` it is a Base UI dialog behind `trigger`. */
1846
2354
  function Dialog$2({ children, controller, render, title, trigger }) {
1847
2355
  const { colorScheme, messages, portalContainer, themeStyle } = useDomainKit();
2356
+ const icons = useIcons();
1848
2357
  const readOnly = useReadOnly();
1849
2358
  const [open, setOpen] = useState(false);
1850
- const busy = controller.state._tag === "Submitting";
2359
+ const state = controller.state;
2360
+ const busy = state._tag === "Submitting";
2361
+ const unattributed = state._tag === "Failure" && !answeredInPlace(controller);
1851
2362
  const snapshot = controller.snapshot;
1852
- const provider = snapshot?.provider;
1853
- const heading = title ?? (provider === null || provider === void 0 ? messages.connectAnyTitle : messages.connectTitle(provider));
1854
- const body = children ?? /* @__PURE__ */ jsx(Form, { controller });
2363
+ const [chosen, setChosen] = useState(null);
2364
+ const attached = snapshot?.provider ?? null;
2365
+ const host = hostProvider(controller);
2366
+ const narrowed = controller.providers.find((entry) => entry.id === (chosen ?? attached)) ?? host;
2367
+ const named = narrowed?.name ?? null;
2368
+ const heading = title ?? (named === null ? messages.connectAnyTitle : messages.connectTitle(named));
2369
+ const alternatives = narrowed === null ? [] : controller.providers.filter((entry) => entry.id !== narrowed.id);
2370
+ const running = state._tag === "Loading" || state._tag === "Submitting" || state._tag === "Redirecting" || state._tag === "SelectionRequired";
2371
+ const label = named === null ? messages.connectAnyTitle : messages.connect;
2372
+ const body = children ?? /* @__PURE__ */ jsx(Form, {
2373
+ controller,
2374
+ ...chosen === null ? {} : { provider: chosen }
2375
+ });
1855
2376
  if (readOnly) return /* @__PURE__ */ jsx(Fragment, {});
1856
2377
  if (render !== void 0) return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("button", {
1857
2378
  "data-domainkit-part": "connection-trigger",
1858
2379
  onClick: () => setOpen(!open),
1859
2380
  type: "button",
1860
- children: trigger ?? messages.connect
2381
+ children: trigger ?? label
1861
2382
  }), render({
1862
2383
  children: body,
1863
2384
  open
@@ -1873,7 +2394,7 @@ function Dialog$2({ children, controller, render, title, trigger }) {
1873
2394
  open,
1874
2395
  children: [/* @__PURE__ */ jsx(Dialog.Trigger, {
1875
2396
  "data-domainkit-part": "connection-trigger",
1876
- children: trigger ?? messages.connect
2397
+ children: trigger ?? label
1877
2398
  }), /* @__PURE__ */ jsxs(Dialog.Portal, {
1878
2399
  container: portalContainer,
1879
2400
  children: [/* @__PURE__ */ jsx(Dialog.Backdrop, {
@@ -1886,6 +2407,229 @@ function Dialog$2({ children, controller, render, title, trigger }) {
1886
2407
  "data-domainkit-part": "connection-dialog",
1887
2408
  "data-domainkit-root": "",
1888
2409
  style: themeStyle,
2410
+ children: [
2411
+ /* @__PURE__ */ jsxs("div", {
2412
+ "data-domainkit-part": "dialog-header",
2413
+ children: [
2414
+ narrowed === null ? null : /* @__PURE__ */ jsx("div", {
2415
+ "data-domainkit-part": "dialog-media",
2416
+ children: /* @__PURE__ */ jsx(Mark, { provider: narrowed })
2417
+ }),
2418
+ /* @__PURE__ */ jsxs("div", {
2419
+ "data-domainkit-part": "dialog-heading",
2420
+ children: [/* @__PURE__ */ jsx(Dialog.Title, {
2421
+ "data-domainkit-part": "dialog-title",
2422
+ children: alternatives.length === 0 || title !== void 0 ? heading : /* @__PURE__ */ jsxs(Menu.Root, { children: [/* @__PURE__ */ jsxs(Menu.Trigger, {
2423
+ "data-domainkit-part": "dialog-provider-menu",
2424
+ disabled: busy,
2425
+ children: [heading, /* @__PURE__ */ jsx("span", {
2426
+ "aria-hidden": "true",
2427
+ "data-icon": "inline-end",
2428
+ children: icons.chevron
2429
+ })]
2430
+ }), /* @__PURE__ */ jsx(Menu.Portal, {
2431
+ container: portalContainer,
2432
+ children: /* @__PURE__ */ jsx(Menu.Positioner, {
2433
+ align: "start",
2434
+ "data-domainkit-part": "provider-menu-positioner",
2435
+ sideOffset: 6,
2436
+ children: /* @__PURE__ */ jsx(Menu.Popup, {
2437
+ "aria-label": messages.useAnotherProvider,
2438
+ "data-color-scheme": colorScheme,
2439
+ "data-domainkit-part": "provider-menu",
2440
+ "data-domainkit-root": "",
2441
+ style: themeStyle,
2442
+ children: alternatives.map((entry) => /* @__PURE__ */ jsxs(Menu.Item, {
2443
+ "data-domainkit-part": "provider-menu-item",
2444
+ "data-provider": entry.id,
2445
+ onClick: () => setChosen(entry.id),
2446
+ children: [/* @__PURE__ */ jsx(Mark, { provider: entry }), entry.name]
2447
+ }, entry.id))
2448
+ })
2449
+ })
2450
+ })] })
2451
+ }), /* @__PURE__ */ jsx(Dialog.Description, {
2452
+ "data-domainkit-part": "dialog-description",
2453
+ children: messages.connectDescription(snapshot?.domain ?? "")
2454
+ })]
2455
+ }),
2456
+ busy ? null : /* @__PURE__ */ jsx(Dialog.Close, {
2457
+ "aria-label": messages.close,
2458
+ "data-domainkit-part": "dialog-close",
2459
+ children: "×"
2460
+ })
2461
+ ]
2462
+ }),
2463
+ /* @__PURE__ */ jsxs("div", {
2464
+ "data-domainkit-part": "dialog-body",
2465
+ children: [
2466
+ running ? /* @__PURE__ */ jsx(Status$2, { controller }) : null,
2467
+ body,
2468
+ unattributed ? /* @__PURE__ */ jsx(Outcome$2, { controller }) : null
2469
+ ]
2470
+ }),
2471
+ busy ? null : /* @__PURE__ */ jsx("div", {
2472
+ "data-domainkit-part": "dialog-footer",
2473
+ children: /* @__PURE__ */ jsx(Dialog.Close, {
2474
+ "data-domainkit-part": "dialog-cancel",
2475
+ children: messages.cancel
2476
+ })
2477
+ })
2478
+ ]
2479
+ })]
2480
+ })]
2481
+ });
2482
+ }
2483
+ /**
2484
+ * Whether the connect surface has anything to offer for this domain: a provider that serves the
2485
+ * zone, a connection discovery already found, one the owner already holds, or a command already
2486
+ * running. Never while a connection is held, which is what `holdsConnection` answers. `Prompt`
2487
+ * renders on this, and `Domain.Flow` reports it so a host can order its own offers beside
2488
+ * DomainKit's rather than competing with them.
2489
+ */
2490
+ const offering = (controller, connect = "detected") => {
2491
+ if (holdsConnection(controller)) return false;
2492
+ const state = controller.state;
2493
+ if (state._tag === "Submitting" || state._tag === "Redirecting" || state._tag === "SelectionRequired" || state._tag === "Reconnect") return true;
2494
+ if (connect !== "detected") return connect === "always";
2495
+ const discovery = controller.discovery;
2496
+ return hostProvider(controller) !== null || discovery !== null && discovery._tag !== "NotFound" || (controller.snapshot?.reusable.length ?? 0) > 0;
2497
+ };
2498
+ /**
2499
+ * The disconnected offer: the provider whose nameservers serve the domain, what that means, and
2500
+ * the trigger that connects it. With no host detected there is nothing to offer, so nothing
2501
+ * renders unless the host application asks for the dialog anyway.
2502
+ */
2503
+ function Prompt({ connect = "detected", controller, ...props }) {
2504
+ const { messages } = useDomainKit();
2505
+ const readOnly = useReadOnly();
2506
+ const state = controller.state;
2507
+ const host = hostProvider(controller);
2508
+ const element = usePart("div", props, { status: state._tag }, {
2509
+ children: /* @__PURE__ */ jsxs(Fragment, { children: [host === null ? null : /* @__PURE__ */ jsxs("div", {
2510
+ "data-domainkit-part": "host-identity",
2511
+ children: [
2512
+ /* @__PURE__ */ jsx(Mark, { provider: host }),
2513
+ /* @__PURE__ */ jsx("span", {
2514
+ "data-domainkit-part": "host-name",
2515
+ children: host.name
2516
+ }),
2517
+ /* @__PURE__ */ jsx("span", {
2518
+ "data-domainkit-part": "host-statement",
2519
+ children: messages.hostOwnsZone
2520
+ })
2521
+ ]
2522
+ }), /* @__PURE__ */ jsx(Dialog$2, { controller })] }),
2523
+ "data-domainkit-part": "connect-prompt",
2524
+ "data-host": host?.id
2525
+ });
2526
+ if (!offering(controller, connect)) return null;
2527
+ if (readOnly && host === null) return null;
2528
+ return element;
2529
+ }
2530
+ /** The plan an attempt holds, whatever step it is on; `null` before there is one. */
2531
+ const planOf = (state) => {
2532
+ switch (state._tag) {
2533
+ case "Planned":
2534
+ case "Approving":
2535
+ case "Applying":
2536
+ case "Rejecting":
2537
+ case "Rejected": return state.plan;
2538
+ case "Applied": return state.plan;
2539
+ case "Idle":
2540
+ case "Planning":
2541
+ case "Failure": return null;
2542
+ }
2543
+ };
2544
+ /**
2545
+ * Letting a provider go, with the records DomainKit added as one decision rather than two.
2546
+ * Confirming removes what the domain's apply receipt proves DomainKit created, then releases the
2547
+ * connection; a plan with nothing in it is not a failure, so it goes straight to the release.
2548
+ *
2549
+ * The cleanup is this dialog's own, so nothing re-reads the snapshot in between: the disconnect
2550
+ * needs the connection the cleanup just used.
2551
+ */
2552
+ function DisconnectDialog({ controller, onCleaned, trigger }) {
2553
+ const { capabilities, colorScheme, messages, portalContainer, themeStyle } = useDomainKit();
2554
+ const readOnly = useReadOnly();
2555
+ const [open, setOpen] = useState(false);
2556
+ const [alsoRemove, setAlsoRemove] = useState(true);
2557
+ const [removing, setRemoving] = useState(false);
2558
+ const snapshot = controller.snapshot;
2559
+ const receiptId = snapshot?.lastReceiptId ?? null;
2560
+ const removable = capabilities.includes("cleanup") && receiptId !== null;
2561
+ const shared = (snapshot?.connectionDomains ?? 0) > 1;
2562
+ const [everyDomain, setEveryDomain] = useState(false);
2563
+ const cleanup = useController$3({
2564
+ domain: controller.domain,
2565
+ ...onCleaned === void 0 ? {} : { onCleaned },
2566
+ ...receiptId === null ? {} : { receiptId: Receipt.ReceiptId.make(receiptId) }
2567
+ });
2568
+ const { approve, plan, state: cleaning } = cleanup;
2569
+ const removals = planOf(cleaning);
2570
+ useEffect(() => {
2571
+ if (open && removable && cleaning._tag === "Idle") plan();
2572
+ }, [
2573
+ cleaning,
2574
+ open,
2575
+ plan,
2576
+ removable
2577
+ ]);
2578
+ const detach = controller.detach;
2579
+ const release = controller.disconnect;
2580
+ const disconnect = useCallback(() => shared && !everyDomain ? detach() : release(), [
2581
+ detach,
2582
+ everyDomain,
2583
+ release,
2584
+ shared
2585
+ ]);
2586
+ const releasing = controller.state._tag === "Submitting";
2587
+ useEffect(() => {
2588
+ if (!removing) return;
2589
+ if (cleaning._tag === "Applied") {
2590
+ setRemoving(false);
2591
+ disconnect();
2592
+ return;
2593
+ }
2594
+ if (cleaning._tag === "Failure" || cleaning._tag === "Rejected") setRemoving(false);
2595
+ }, [
2596
+ cleaning,
2597
+ disconnect,
2598
+ removing
2599
+ ]);
2600
+ const busy = removing || releasing;
2601
+ const settling = removable && removals === null;
2602
+ const provider = snapshot?.provider ?? null;
2603
+ const named = provider === null ? "" : displayName(controller, provider);
2604
+ const heading = provider === null ? messages.disconnect : messages.disconnectTitle(named);
2605
+ const count = removals?.operations.length ?? 0;
2606
+ const choosable = removable && count > 0;
2607
+ if (readOnly) return /* @__PURE__ */ jsx(Fragment, {});
2608
+ return /* @__PURE__ */ jsxs(Dialog.Root, {
2609
+ onOpenChange: (next, details) => {
2610
+ if (!next && busy) {
2611
+ details.cancel();
2612
+ return;
2613
+ }
2614
+ setOpen(next);
2615
+ },
2616
+ open,
2617
+ children: [/* @__PURE__ */ jsx(Dialog.Trigger, {
2618
+ "data-domainkit-part": "disconnect-action",
2619
+ children: trigger ?? messages.disconnect
2620
+ }), /* @__PURE__ */ jsxs(Dialog.Portal, {
2621
+ container: portalContainer,
2622
+ children: [/* @__PURE__ */ jsx(Dialog.Backdrop, {
2623
+ "data-color-scheme": colorScheme,
2624
+ "data-domainkit-part": "dialog-backdrop",
2625
+ "data-domainkit-root": "",
2626
+ style: themeStyle
2627
+ }), /* @__PURE__ */ jsxs(Dialog.Popup, {
2628
+ "data-color-scheme": colorScheme,
2629
+ "data-cleanup": choosable ? "offered" : "none",
2630
+ "data-domainkit-part": "disconnect-dialog",
2631
+ "data-domainkit-root": "",
2632
+ style: themeStyle,
1889
2633
  children: [
1890
2634
  /* @__PURE__ */ jsxs("div", {
1891
2635
  "data-domainkit-part": "dialog-header",
@@ -1896,7 +2640,7 @@ function Dialog$2({ children, controller, render, title, trigger }) {
1896
2640
  children: heading
1897
2641
  }), /* @__PURE__ */ jsx(Dialog.Description, {
1898
2642
  "data-domainkit-part": "dialog-description",
1899
- children: messages.connectDescription(snapshot?.domain ?? "")
2643
+ children: messages.disconnectConsent(snapshot?.domain ?? "", named)
1900
2644
  })]
1901
2645
  }), busy ? null : /* @__PURE__ */ jsx(Dialog.Close, {
1902
2646
  "aria-label": messages.close,
@@ -1907,68 +2651,136 @@ function Dialog$2({ children, controller, render, title, trigger }) {
1907
2651
  /* @__PURE__ */ jsxs("div", {
1908
2652
  "data-domainkit-part": "dialog-body",
1909
2653
  children: [
1910
- /* @__PURE__ */ jsx(Status$2, { controller }),
1911
- body,
2654
+ shared ? /* @__PURE__ */ jsxs("fieldset", {
2655
+ "data-domainkit-part": "disconnect-scope",
2656
+ children: [
2657
+ /* @__PURE__ */ jsx("legend", { children: messages.disconnectScope }),
2658
+ /* @__PURE__ */ jsxs("label", { children: [/* @__PURE__ */ jsx("input", {
2659
+ checked: !everyDomain,
2660
+ disabled: busy,
2661
+ name: "disconnect-scope",
2662
+ onChange: () => setEveryDomain(false),
2663
+ type: "radio"
2664
+ }), messages.disconnectThisDomain] }),
2665
+ /* @__PURE__ */ jsxs("label", { children: [/* @__PURE__ */ jsx("input", {
2666
+ checked: everyDomain,
2667
+ disabled: busy,
2668
+ name: "disconnect-scope",
2669
+ onChange: () => setEveryDomain(true),
2670
+ type: "radio"
2671
+ }), messages.disconnectEveryDomain(snapshot?.connectionDomains ?? 0)] })
2672
+ ]
2673
+ }) : null,
2674
+ choosable ? /* @__PURE__ */ jsxs("div", {
2675
+ "data-domainkit-part": "disconnect-cleanup",
2676
+ "data-state": alsoRemove ? "on" : "off",
2677
+ children: [/* @__PURE__ */ jsxs("label", {
2678
+ "data-domainkit-part": "disconnect-cleanup-label",
2679
+ children: [/* @__PURE__ */ jsx(Switch.Root, {
2680
+ checked: alsoRemove,
2681
+ "data-domainkit-part": "disconnect-cleanup-switch",
2682
+ disabled: busy,
2683
+ onCheckedChange: setAlsoRemove,
2684
+ children: /* @__PURE__ */ jsx(Switch.Thumb, { "data-domainkit-part": "disconnect-cleanup-thumb" })
2685
+ }), messages.disconnectWithCleanup(count)]
2686
+ }), /* @__PURE__ */ jsxs("div", {
2687
+ "data-domainkit-part": "disconnect-cleanup-panel",
2688
+ children: [removals === null ? null : /* @__PURE__ */ jsx(List, { plan: removals }), alsoRemove ? null : /* @__PURE__ */ jsx("p", {
2689
+ "data-domainkit-part": "disconnect-cleanup-note",
2690
+ children: messages.disconnectKeepsRecords(named)
2691
+ })]
2692
+ })]
2693
+ }) : null,
2694
+ /* @__PURE__ */ jsx(Status$3, { controller: cleanup }),
2695
+ releasing ? /* @__PURE__ */ jsx("p", {
2696
+ "data-domainkit-part": "disconnect-status",
2697
+ role: "status",
2698
+ children: messages.disconnecting
2699
+ }) : null,
2700
+ /* @__PURE__ */ jsx(Outcome$3, { controller: cleanup }),
1912
2701
  /* @__PURE__ */ jsx(Outcome$2, { controller })
1913
2702
  ]
1914
2703
  }),
1915
- busy ? null : /* @__PURE__ */ jsx("div", {
2704
+ /* @__PURE__ */ jsxs("div", {
1916
2705
  "data-domainkit-part": "dialog-footer",
1917
- children: /* @__PURE__ */ jsx(Dialog.Close, {
2706
+ children: [/* @__PURE__ */ jsx("button", {
2707
+ "data-domainkit-part": "disconnect-confirm",
2708
+ disabled: busy || settling,
2709
+ onClick: () => {
2710
+ if (choosable && alsoRemove) {
2711
+ setRemoving(true);
2712
+ approve();
2713
+ return;
2714
+ }
2715
+ disconnect();
2716
+ },
2717
+ type: "button",
2718
+ children: messages.disconnect
2719
+ }), busy ? null : /* @__PURE__ */ jsx(Dialog.Close, {
1918
2720
  "data-domainkit-part": "dialog-cancel",
1919
2721
  children: messages.cancel
1920
- })
2722
+ })]
1921
2723
  })
1922
2724
  ]
1923
2725
  })]
1924
2726
  })]
1925
2727
  });
1926
2728
  }
1927
- /** A connected domain: which provider holds it, and how to let it go. */
1928
- function Card({ controller, ...props }) {
2729
+ /**
2730
+ * A connected domain, with the prompt card's anatomy: the provider's mark, the name the customer
2731
+ * knows it by, where the connection stands, and the one action that ends it.
2732
+ */
2733
+ function Card({ controller, onCleaned, ...props }) {
1929
2734
  const { messages } = useDomainKit();
1930
2735
  const readOnly = useReadOnly();
1931
2736
  const state = controller.state;
1932
2737
  const snapshot = controller.snapshot;
1933
2738
  const provider = controller.providers.find((entry) => entry.id === snapshot?.provider);
2739
+ const standing = state._tag === "Reconnect" ? messages.needsReconnect : messages.connected;
1934
2740
  return usePart("div", props, { status: state._tag }, {
1935
2741
  children: /* @__PURE__ */ jsxs(Fragment, { children: [
1936
2742
  /* @__PURE__ */ jsxs("div", {
1937
2743
  "data-domainkit-part": "connected-identity",
1938
- children: [provider === void 0 ? null : /* @__PURE__ */ jsx(Mark, { provider }), /* @__PURE__ */ jsx(Status$2, { controller })]
2744
+ children: [
2745
+ provider === void 0 ? null : /* @__PURE__ */ jsx(Mark, { provider }),
2746
+ /* @__PURE__ */ jsx("span", {
2747
+ "data-domainkit-part": "host-name",
2748
+ children: provider?.name ?? snapshot?.provider ?? ""
2749
+ }),
2750
+ /* @__PURE__ */ jsx("span", {
2751
+ "data-domainkit-part": "host-statement",
2752
+ children: standing
2753
+ })
2754
+ ]
1939
2755
  }),
1940
- readOnly ? null : /* @__PURE__ */ jsxs("div", {
2756
+ readOnly ? null : /* @__PURE__ */ jsx("div", {
1941
2757
  "data-domainkit-part": "connected-actions",
1942
- children: [/* @__PURE__ */ jsx("button", {
1943
- "data-domainkit-part": "disconnect-trigger",
1944
- disabled: state._tag === "Submitting",
1945
- onClick: controller.detach,
1946
- type: "button",
1947
- children: messages.detach
1948
- }), /* @__PURE__ */ jsx("button", {
1949
- "data-domainkit-part": "disconnect-action",
1950
- disabled: state._tag === "Submitting",
1951
- onClick: controller.disconnect,
1952
- type: "button",
1953
- children: messages.disconnect
1954
- })]
2758
+ children: /* @__PURE__ */ jsx(DisconnectDialog, {
2759
+ controller,
2760
+ ...onCleaned === void 0 ? {} : { onCleaned }
2761
+ })
1955
2762
  }),
1956
2763
  /* @__PURE__ */ jsx(Outcome$2, { controller })
1957
2764
  ] }),
1958
2765
  "data-domainkit-part": "connected-card"
1959
2766
  });
1960
2767
  }
1961
- /** Connection on its own: the card once connected, the dialog until then. */
1962
- function Flow$2({ domain, returnTo, ...props }) {
2768
+ /** Connection on its own: the card once connected, the prompt until then. */
2769
+ function Flow$2({ connect, domain, onCleaned, returnTo, ...props }) {
1963
2770
  const controller = useController$2({
1964
2771
  domain,
1965
2772
  ...returnTo === void 0 ? {} : { returnTo }
1966
2773
  });
1967
- const state = controller.state;
1968
2774
  return /* @__PURE__ */ jsx(Root$2, {
1969
2775
  controller,
1970
2776
  ...props,
1971
- children: state._tag === "Connected" ? /* @__PURE__ */ jsx(Card, { controller }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Dialog$2, { controller }), /* @__PURE__ */ jsx(Outcome$2, { controller })] })
2777
+ children: holdsConnection(controller) ? /* @__PURE__ */ jsx(Card, {
2778
+ controller,
2779
+ ...onCleaned === void 0 ? {} : { onCleaned }
2780
+ }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Prompt, {
2781
+ controller,
2782
+ ...connect === void 0 ? {} : { connect }
2783
+ }), answeredInPlace(controller) ? null : /* @__PURE__ */ jsx(Outcome$2, { controller })] })
1972
2784
  });
1973
2785
  }
1974
2786
  //#endregion
@@ -2049,13 +2861,16 @@ function Flow$1({ domain, onApplied, requirements, trigger, ...props }) {
2049
2861
  requirements,
2050
2862
  ...onApplied === void 0 ? {} : { onApplied }
2051
2863
  });
2864
+ const [open, setOpen] = useState(false);
2052
2865
  return /* @__PURE__ */ jsxs(Root$1, {
2053
2866
  controller,
2054
2867
  ...props,
2055
2868
  children: [/* @__PURE__ */ jsx(Dialog$1, {
2056
2869
  controller,
2870
+ onOpenChange: setOpen,
2871
+ open,
2057
2872
  ...trigger === void 0 ? {} : { trigger }
2058
- }), /* @__PURE__ */ jsx(Outcome$1, { controller })]
2873
+ }), open ? null : /* @__PURE__ */ jsx(Outcome$1, { controller })]
2059
2874
  });
2060
2875
  }
2061
2876
  //#endregion
@@ -2242,25 +3057,29 @@ function Evidence({ readiness }) {
2242
3057
  })]
2243
3058
  });
2244
3059
  }
2245
- function Outcome({ controller, ...props }) {
3060
+ /**
3061
+ * The last observation that failed. Observing reads the world rather than changing the domain, so
3062
+ * the retry stays even in read-only.
3063
+ */
3064
+ function Outcome({ children, controller, ...props }) {
2246
3065
  const { messages } = useDomainKit();
2247
3066
  const state = controller.state;
2248
- const element = usePart("p", props, { status: state._tag }, {
2249
- children: state._tag === "Failure" ? /* @__PURE__ */ jsxs(Fragment, { children: [
2250
- failure(state.error, messages),
2251
- " ",
2252
- /* @__PURE__ */ jsx("button", {
2253
- "data-domainkit-part": "verification-retry",
2254
- onClick: controller.retry,
2255
- type: "button",
2256
- children: messages.retry
2257
- })
2258
- ] }) : null,
2259
- "data-domainkit-part": "flow-outcome",
2260
- "data-tone": "danger",
2261
- role: "alert"
3067
+ if (state._tag !== "Failure") return null;
3068
+ const words = outcome(state.error, messages);
3069
+ return /* @__PURE__ */ jsx(Provider, {
3070
+ value: {
3071
+ description: words.description ?? "",
3072
+ layout: props.layout ?? "card",
3073
+ retry: controller.retry,
3074
+ retryPart: "verification-retry",
3075
+ title: words.title,
3076
+ tone: "danger"
3077
+ },
3078
+ children: /* @__PURE__ */ jsx(Root$6, {
3079
+ ...props,
3080
+ children: children ?? /* @__PURE__ */ jsx(Composition, {})
3081
+ })
2262
3082
  });
2263
- return state._tag === "Failure" ? element : null;
2264
3083
  }
2265
3084
  /** The default verification slot: a button that observes, with the evidence behind it. */
2266
3085
  function Status({ controller, render, ...props }) {
@@ -2306,54 +3125,51 @@ function Status({ controller, render, ...props }) {
2306
3125
  //#endregion
2307
3126
  //#region src/domain.tsx
2308
3127
  var domain_exports = /* @__PURE__ */ __exportAll({ Flow: () => Flow });
2309
- function DefaultConnection({ controller }) {
2310
- return controller.state._tag === "Connected" ? /* @__PURE__ */ jsx(Card, { controller }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2311
- /* @__PURE__ */ jsx(Status$2, { controller }),
2312
- /* @__PURE__ */ jsx(Dialog$2, { controller }),
2313
- /* @__PURE__ */ jsx(Outcome$2, { controller })
3128
+ /** Built from primitives alone, so the effect that announces it depends on exactly those. */
3129
+ const flowState = (input) => ({
3130
+ applied: input.receipt !== null,
3131
+ connected: input.connected,
3132
+ connection: input.status,
3133
+ offering: input.offering,
3134
+ provider: input.provider,
3135
+ provisioning: input.planning,
3136
+ receiptId: input.receipt === null ? null : Receipt.ReceiptId.make(input.receipt)
3137
+ });
3138
+ function DefaultConnection({ connect, controller, onCleaned }) {
3139
+ const disconnected = controller.state._tag === "Disconnected";
3140
+ const host = hostProvider(controller);
3141
+ if (disconnected && host === null && !offering(controller, connect)) return null;
3142
+ const stated = disconnected && host !== null && offering(controller, connect);
3143
+ return holdsConnection(controller) ? /* @__PURE__ */ jsx(Card, {
3144
+ controller,
3145
+ ...onCleaned === void 0 ? {} : { onCleaned }
3146
+ }) : /* @__PURE__ */ jsxs(Fragment, { children: [
3147
+ stated ? null : /* @__PURE__ */ jsx(Status$2, { controller }),
3148
+ /* @__PURE__ */ jsx(Prompt, {
3149
+ connect,
3150
+ controller
3151
+ }),
3152
+ answeredInPlace(controller) ? null : /* @__PURE__ */ jsx(Outcome$2, { controller })
2314
3153
  ] });
2315
3154
  }
2316
- function DefaultActions({ cleanup, connection, provisioning }) {
2317
- const { capabilities, messages } = useDomainKit();
3155
+ function DefaultActions({ connection, onOpenChange, open, provisioning }) {
2318
3156
  const readOnly = useReadOnly();
2319
- const icons = useIcons();
3157
+ const { capabilities } = useDomainKit();
2320
3158
  const connected = connection.state._tag === "Connected";
2321
3159
  if (readOnly) return null;
2322
- const hasReceipt = connection.snapshot?.lastReceiptId != null;
2323
- return /* @__PURE__ */ jsxs(Fragment, { children: [capabilities.includes("provisioning") && connected ? /* @__PURE__ */ jsxs(Fragment, { children: [
2324
- /* @__PURE__ */ jsx("button", {
2325
- "data-domainkit-part": "plan-trigger",
2326
- disabled: provisioning.state._tag === "Planning",
2327
- onClick: provisioning.plan,
2328
- type: "button",
2329
- children: messages.reviewChanges
2330
- }),
2331
- /* @__PURE__ */ jsx(Status$1, { controller: provisioning }),
2332
- /* @__PURE__ */ jsx(Actions, { controller: provisioning }),
2333
- /* @__PURE__ */ jsx(Outcome$1, { controller: provisioning })
2334
- ] }) : null, capabilities.includes("cleanup") && connected && hasReceipt ? /* @__PURE__ */ jsxs(Fragment, { children: [
2335
- /* @__PURE__ */ jsxs("button", {
2336
- "data-domainkit-part": "cleanup-trigger",
2337
- disabled: cleanup.state._tag === "Planning",
2338
- onClick: cleanup.plan,
2339
- type: "button",
2340
- children: [/* @__PURE__ */ jsx("span", {
2341
- "aria-hidden": "true",
2342
- "data-icon": "inline-start",
2343
- children: icons.close
2344
- }), messages.cleanUp]
2345
- }),
2346
- /* @__PURE__ */ jsx(Status$3, { controller: cleanup }),
2347
- /* @__PURE__ */ jsx(Actions$1, { controller: cleanup }),
2348
- /* @__PURE__ */ jsx(Outcome$3, { controller: cleanup })
2349
- ] }) : null] });
3160
+ if (!capabilities.includes("provisioning") || !connected) return null;
3161
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Dialog$1, {
3162
+ controller: provisioning,
3163
+ onOpenChange,
3164
+ open
3165
+ }), open ? null : /* @__PURE__ */ jsx(Outcome$1, { controller: provisioning })] });
2350
3166
  }
2351
3167
  /**
2352
3168
  * The whole lifecycle for one domain: connect, review, apply, observe, clean up. The flow renders
2353
3169
  * only what the transport declares, and every part of it is a slot with a default. It adds no
2354
3170
  * layout containers of its own, so a host's grid can place the slot output directly.
2355
3171
  */
2356
- function Flow({ domain, onApplied, onCleaned, readOnly, requirements, returnTo, slots = {}, ...props }) {
3172
+ function Flow({ connect = "detected", domain, onApplied, onCleaned, onState, readOnly, requirements, returnTo, review = "auto", slots = {}, ...props }) {
2357
3173
  const { capabilities } = useDomainKit();
2358
3174
  const inherited = useReadOnly();
2359
3175
  const connection = useController$2({
@@ -2382,17 +3198,72 @@ function Flow({ domain, onApplied, onCleaned, readOnly, requirements, returnTo,
2382
3198
  requirements
2383
3199
  });
2384
3200
  const readiness = verification.readiness;
2385
- return usePart("div", props, {
2386
- connection: connection.state._tag,
2387
- provisioning: provisioning.state._tag
2388
- }, {
3201
+ const status = connection.state._tag;
3202
+ const planning = provisioning.state._tag;
3203
+ const [reviewing, setReviewing] = useState(false);
3204
+ const [reviewed, setReviewed] = useState(domain);
3205
+ if (reviewed !== domain) {
3206
+ setReviewed(domain);
3207
+ setReviewing(false);
3208
+ }
3209
+ const established = connection.established;
3210
+ const answered = useRef(established);
3211
+ const buildPlan = provisioning.plan;
3212
+ useEffect(() => {
3213
+ if (answered.current === established) return;
3214
+ answered.current = established;
3215
+ if (review === "manual") return;
3216
+ buildPlan();
3217
+ setReviewing(true);
3218
+ }, [
3219
+ buildPlan,
3220
+ established,
3221
+ review
3222
+ ]);
3223
+ const connected = holdsConnection(connection);
3224
+ const offering$1 = offering(connection, connect);
3225
+ const provider = connection.snapshot?.provider ?? hostProvider(connection)?.id ?? null;
3226
+ const receipt = connection.snapshot?.lastReceiptId ?? null;
3227
+ const state = flowState({
3228
+ connected,
3229
+ offering: offering$1,
3230
+ planning,
3231
+ provider,
3232
+ receipt,
3233
+ status
3234
+ });
3235
+ const announce = useRef(onState);
3236
+ useEffect(() => {
3237
+ announce.current = onState;
3238
+ });
3239
+ useEffect(() => {
3240
+ announce.current?.(flowState({
3241
+ connected,
3242
+ offering: offering$1,
3243
+ planning,
3244
+ provider,
3245
+ receipt,
3246
+ status
3247
+ }));
3248
+ }, [
3249
+ connected,
3250
+ offering$1,
3251
+ planning,
3252
+ provider,
3253
+ receipt,
3254
+ status
3255
+ ]);
3256
+ return usePart("div", props, state, {
2389
3257
  children: /* @__PURE__ */ jsxs(ReadOnly, {
2390
3258
  value: readOnly ?? inherited,
2391
3259
  children: [
2392
3260
  !capabilities.includes("connection") ? null : slots.connection === void 0 ? /* @__PURE__ */ jsx(DefaultConnection, {
3261
+ connect,
2393
3262
  controller: connection,
2394
- domain
3263
+ domain,
3264
+ ...onCleaned === void 0 ? {} : { onCleaned }
2395
3265
  }) : slots.connection({
3266
+ connect,
2396
3267
  controller: connection,
2397
3268
  domain
2398
3269
  }),
@@ -2413,6 +3284,8 @@ function Flow({ domain, onApplied, onCleaned, readOnly, requirements, returnTo,
2413
3284
  cleanup,
2414
3285
  connection,
2415
3286
  domain,
3287
+ onOpenChange: setReviewing,
3288
+ open: reviewing,
2416
3289
  provisioning
2417
3290
  }) : slots.actions({
2418
3291
  cleanup,
@@ -2438,6 +3311,6 @@ const transport = Testing.transport;
2438
3311
  //#region src/failure.ts
2439
3312
  const Failure = Data.taggedEnum();
2440
3313
  //#endregion
2441
- export { cleanup_exports as Cleanup, connect_exports as Connect, domain_exports as Domain, domain_kit_exports as DomainKit, Event, Failure, messages_exports as Messages, operations_exports as Operations, provider_exports as Provider, provision_exports as Provision, records_exports as Records, testing_exports as Testing, theme_exports as Theme, verify_exports as Verify, defaultIcons, useIcons };
3314
+ export { cleanup_exports as Cleanup, connect_exports as Connect, domain_exports as Domain, domain_kit_exports as DomainKit, Event, Failure, messages_exports as Messages, operations_exports as Operations, outcome_exports as Outcome, provider_exports as Provider, provision_exports as Provision, records_exports as Records, testing_exports as Testing, theme_exports as Theme, verify_exports as Verify, defaultIcons, useIcons };
2442
3315
 
2443
3316
  //# sourceMappingURL=index.mjs.map