@capxul/sdk-react 4.2.0 → 4.20.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -28,10 +28,12 @@ export function Providers({ children }: { children: React.ReactNode }) {
28
28
  session. Account and Organization readiness transitions refresh active
29
29
  authenticated queries.
30
30
  - `CapxulAuthenticationController` and `CapxulOnboardingController` select
31
- app-owned slots and render no SDK-owned DOM. Supplying the onboarding
32
- controller's `recoveryOptions` factory lets a fresh actor resume account
33
- prerequisites before the stored Organization replay; a failed prerequisite
34
- returns the same manual replay instead of looping.
31
+ app-owned slots and render no SDK-owned DOM. The onboarding controller keeps
32
+ no Organization submission: after a reload the Core SDK reads the pending
33
+ Organization from the backend and attaches it, so the app re-submits nothing.
34
+ A claimed identity with no Organization and no app-held form step reports
35
+ `organizationProgress` with `{ at: "loading", orgId: null }` until that
36
+ attach lands.
35
37
  - Rich-data hooks such as `useCapxulProfile`, `useCapxulOrgs`, members, roles,
36
38
  treasury, and money hooks remain TanStack Query projections. Personal
37
39
  holdings and activity reads start only after the Account is claimed.
@@ -37,6 +37,24 @@ function useCapxulClientOrNull() {
37
37
  }
38
38
  //#endregion
39
39
  //#region src/internal/reactivity-keys.ts
40
+ /**
41
+ * The account a read is answered FOR, as a key segment (#2007 R19).
42
+ *
43
+ * A read whose answer depends on who is asking belongs to the (scope, account)
44
+ * pair, not to the scope. Replacing one claimed account with another is not a
45
+ * state-label change. Keying is what
46
+ * makes the new person miss rather than read the previous person's answer.
47
+ * `undefined` while no account is claimed — no such read may run then anyway.
48
+ */
49
+ function actingAccount(identity) {
50
+ return identity !== null && identity.phase === "authenticated" && identity.account.at === "claimed" ? identity.account.address : void 0;
51
+ }
52
+ /** Keep invalidation prefixes shared, but never share a personal result across accounts.
53
+ * Session reads also run before an Account is claimed (for example `orgs()`).
54
+ */
55
+ function accountKey(key, identity) {
56
+ return [...key, actingAccount(identity) ?? (identity?.phase === "authenticated" ? identity.session.authUserId : null)];
57
+ }
40
58
  const capxulKeys = {
41
59
  root: ["capxul"],
42
60
  profile: ["capxul", "profile"],
@@ -80,11 +98,12 @@ const capxulKeys = {
80
98
  orgId ?? "pending",
81
99
  "permissions"
82
100
  ],
83
- orgMe: (orgId) => [
101
+ orgMe: (orgId, account) => [
84
102
  "capxul",
85
103
  "org",
86
104
  orgId ?? "pending",
87
- "me"
105
+ "me",
106
+ account ?? "pending"
88
107
  ],
89
108
  orgLifecycle: (orgId) => [
90
109
  "capxul",
@@ -151,10 +170,35 @@ const capxulKeys = {
151
170
  ]
152
171
  };
153
172
  //#endregion
154
- //#region src/identity.tsx
155
- const MISSING_IDENTITY_PROVIDER = Symbol("MISSING_IDENTITY_PROVIDER");
156
- const IdentityContext = createContext(MISSING_IDENTITY_PROVIDER);
157
- function controls(options) {
173
+ //#region src/internal/invocation-controls.ts
174
+ /**
175
+ * Keep only the five public option fields, and only when they hold the right
176
+ * kind of value. A named action verb takes its attempt as an optional FIRST
177
+ * argument, so a screen that wires one straight to a DOM handler
178
+ * (`onClick={row.hide}`) hands us a React SyntheticEvent. That is not an
179
+ * attempt: it must reach neither the SDK call nor the callback echo.
180
+ */
181
+ function toInvocationOptions(options) {
182
+ if (typeof options !== "object" || options === null) return void 0;
183
+ const attempt = {};
184
+ if (typeof options.correlationId === "string") attempt.correlationId = options.correlationId;
185
+ if (typeof options.journeyId === "string") attempt.journeyId = options.journeyId;
186
+ if (typeof options.timeoutMs === "number") attempt.timeoutMs = options.timeoutMs;
187
+ if (typeof options.deadlineMs === "number") attempt.deadlineMs = options.deadlineMs;
188
+ if (options.signal instanceof AbortSignal) attempt.signal = options.signal;
189
+ return Object.keys(attempt).length === 0 ? void 0 : attempt;
190
+ }
191
+ /**
192
+ * Map the public camel-case invocation options onto the Core SDK's control
193
+ * spelling. ONE definition, because the identity facade and every headless
194
+ * engine must hand the SDK the same shape: a caller that captured one user
195
+ * attempt before it started gets that attempt's `correlation_id`/`journey_id`
196
+ * on whichever operation it starts (#1960 R02).
197
+ *
198
+ * It creates no identifiers. An absent option stays absent, so an automatic
199
+ * read keeps its own invocation context instead of borrowing an attempt's.
200
+ */
201
+ function toInvocationControls(options) {
158
202
  if (options === void 0) return void 0;
159
203
  return {
160
204
  ...options.signal === void 0 ? {} : { signal: options.signal },
@@ -164,12 +208,16 @@ function controls(options) {
164
208
  ...options.journeyId === void 0 ? {} : { journey_id: options.journeyId }
165
209
  };
166
210
  }
211
+ //#endregion
212
+ //#region src/identity.tsx
213
+ const MISSING_IDENTITY_PROVIDER = Symbol("MISSING_IDENTITY_PROVIDER");
214
+ const IdentityContext = createContext(MISSING_IDENTITY_PROVIDER);
167
215
  const failure = (result) => result.ok ? null : {
168
216
  ok: false,
169
217
  reason: result.refused
170
218
  };
171
219
  async function guarded(runtime, verb, options, run) {
172
- const invocation = controls(options);
220
+ const invocation = toInvocationControls(options);
173
221
  try {
174
222
  return await (runtime.runFacade?.(verb, invocation, run) ?? run(invocation));
175
223
  } catch {
@@ -215,18 +263,102 @@ function normalizeOrganization(organization) {
215
263
  return null;
216
264
  }
217
265
  }
266
+ /** The Organization id when its lane is already running or finished, else null. */
267
+ const runningOrganization = (state) => {
268
+ if (state.phase !== "authenticated" || state.account.at !== "claimed") return null;
269
+ const org = state.account.org;
270
+ return org !== null && (org.at === "loading" || org.at === "settingUp" || org.at === "ready") ? org.orgId : null;
271
+ };
272
+ /** A superseded session read waits at most this long for the winning read. */
273
+ const SESSION_SETTLE_LIMIT_MS = 3e4;
274
+ const accountInFlight = (state) => state.phase === "authenticated" && (state.account.at === "deriving" || state.account.at === "claiming");
218
275
  function createAuth(client, clearAuthenticatedQueries) {
219
276
  const runtime = client._internal.identity;
277
+ const settledSession = (invocation) => new Promise((resolve) => {
278
+ let done = false;
279
+ let stop = null;
280
+ let timer = null;
281
+ const onAbort = () => finish({
282
+ ok: false,
283
+ reason: "CANCELLED"
284
+ });
285
+ const finish = (result) => {
286
+ if (done) return;
287
+ done = true;
288
+ stop?.();
289
+ if (timer !== null) clearTimeout(timer);
290
+ invocation?.signal?.removeEventListener("abort", onAbort);
291
+ resolve(result);
292
+ };
293
+ if (invocation?.signal?.aborted) {
294
+ onAbort();
295
+ return;
296
+ }
297
+ invocation?.signal?.addEventListener("abort", onAbort, { once: true });
298
+ const unsubscribe = runtime.subscribeTransitions((record) => {
299
+ if (record.slot !== "identity:session") return;
300
+ if (record.outcome === "applied" && record.event === "SessionRead") finish({ ok: true });
301
+ else if (record.outcome === "failed" || record.outcome === "cancelled") finish({
302
+ ok: false,
303
+ reason: record.error_code
304
+ });
305
+ });
306
+ if (done) {
307
+ unsubscribe();
308
+ return;
309
+ }
310
+ stop = unsubscribe;
311
+ timer = setTimeout(() => finish({
312
+ ok: false,
313
+ reason: "UNKNOWN"
314
+ }), SESSION_SETTLE_LIMIT_MS);
315
+ });
220
316
  const read = async (invocation) => {
221
317
  const result = await runtime.send({ _tag: "ReadSession" }, invocation);
222
- return failure(result) ?? { ok: true };
318
+ const refused = failure(result);
319
+ if (refused === null) return { ok: true };
320
+ return refused.reason === "SUPERSEDED" ? settledSession(invocation) : refused;
223
321
  };
224
322
  const ensureAccount = async (invocation) => {
225
323
  const result = await runtime.send({ _tag: "EnsureAccount" }, invocation);
226
324
  return failure(result) ?? { ok: true };
227
325
  };
326
+ const settledAccount = (invocation) => new Promise((resolve) => {
327
+ let done = false;
328
+ let stop = null;
329
+ const onAbort = () => finish({
330
+ ok: false,
331
+ reason: "CANCELLED"
332
+ });
333
+ const finish = (result) => {
334
+ if (done) return;
335
+ done = true;
336
+ stop?.();
337
+ invocation?.signal?.removeEventListener("abort", onAbort);
338
+ resolve(result);
339
+ };
340
+ if (invocation?.signal?.aborted) {
341
+ onAbort();
342
+ return;
343
+ }
344
+ invocation?.signal?.addEventListener("abort", onAbort, { once: true });
345
+ const unsubscribe = runtime.subscribe((state) => {
346
+ if (!accountInFlight(state)) finish({ ok: true });
347
+ });
348
+ if (done) {
349
+ unsubscribe();
350
+ return;
351
+ }
352
+ stop = unsubscribe;
353
+ if (!accountInFlight(runtime.snapshot())) finish({ ok: true });
354
+ });
228
355
  const reachClaimed = async (invocation) => {
229
356
  let state = runtime.snapshot();
357
+ if (accountInFlight(state)) {
358
+ const settled = await settledAccount(invocation);
359
+ if (!settled.ok) return settled;
360
+ state = runtime.snapshot();
361
+ }
230
362
  if (state.phase !== "authenticated" || state.account.at === "unknown") {
231
363
  const result = await runtime.send({ _tag: "EnsureAccount" }, invocation);
232
364
  const refused = failure(result);
@@ -238,6 +370,16 @@ function createAuth(client, clearAuthenticatedQueries) {
238
370
  reason: "WRONG_STATE"
239
371
  };
240
372
  if (state.account.at === "claimed") return { ok: true };
373
+ if (accountInFlight(state)) {
374
+ const settled = await settledAccount(invocation);
375
+ if (!settled.ok) return settled;
376
+ state = runtime.snapshot();
377
+ if (state.phase !== "authenticated") return {
378
+ ok: false,
379
+ reason: "WRONG_STATE"
380
+ };
381
+ if (state.account.at === "claimed") return { ok: true };
382
+ }
241
383
  const event = state.account.at === "failed" ? { _tag: "RetryAccount" } : state.account.at === "counterfactual" ? { _tag: "ClaimAccount" } : { _tag: "EnsureAccount" };
242
384
  const result = await runtime.send(event, invocation);
243
385
  const refused = failure(result);
@@ -258,13 +400,13 @@ function createAuth(client, clearAuthenticatedQueries) {
258
400
  ok: false,
259
401
  reason: "INVALID_INPUT"
260
402
  };
403
+ const refreshed = await read(invocation);
404
+ if (!refreshed.ok) return refreshed;
261
405
  const current = runtime.snapshot();
262
406
  if (current.phase !== "authenticated" || !current.profileComplete) {
263
407
  const completed = await runtime.completeProfile(submission.profileDetails, invocation);
264
408
  if (!completed.ok) return completed;
265
409
  }
266
- const refreshed = await read(invocation);
267
- if (!refreshed.ok) return refreshed;
268
410
  const claimed = await reachClaimed(invocation);
269
411
  return claimed.ok ? {
270
412
  ok: true,
@@ -341,12 +483,13 @@ function createAuth(client, clearAuthenticatedQueries) {
341
483
  }),
342
484
  createOrganization: (submission, options) => guarded(runtime, "createOrganization", options, async (invocation) => {
343
485
  const current = runtime.snapshot();
486
+ const running = runningOrganization(current);
487
+ if (running !== null) return {
488
+ ok: true,
489
+ orgId: running
490
+ };
344
491
  if (current.phase === "authenticated" && current.account.at === "claimed") {
345
492
  const org = current.account.org;
346
- if (org !== null && (org.at === "loading" || org.at === "settingUp" || org.at === "ready")) return {
347
- ok: true,
348
- orgId: org.orgId
349
- };
350
493
  if (org?.at === "failed" && org.orgId !== null) {
351
494
  if (!org.retryable) return {
352
495
  ok: false,
@@ -361,6 +504,11 @@ function createAuth(client, clearAuthenticatedQueries) {
361
504
  }
362
505
  const prepared = await prepareOrganization(submission, invocation);
363
506
  if (!prepared.ok) return prepared;
507
+ const opened = runningOrganization(runtime.snapshot());
508
+ if (opened !== null) return {
509
+ ok: true,
510
+ orgId: opened
511
+ };
364
512
  const created = await runtime.send({
365
513
  _tag: "CreateOrganization",
366
514
  draft: prepared.organization
@@ -426,9 +574,16 @@ function CapxulIdentityProvider({ client, children }) {
426
574
  }, [client, runtime]);
427
575
  useEffect(() => {
428
576
  if (runtime === null) return;
577
+ let previousAccount = actingAccount(runtime.snapshot());
429
578
  return runtime.subscribeTransitions((record) => {
430
579
  const state = runtime.snapshot();
431
- if (record.outcome === "applied" && record.from !== record.to && (record.to === "authenticated:claimed" || record.to === "authenticated:claimed:ready")) queryClient.invalidateQueries({ queryKey: capxulKeys.root });
580
+ const account = actingAccount(state);
581
+ const accountChanged = previousAccount !== void 0 && account !== void 0 && account !== previousAccount;
582
+ previousAccount = account;
583
+ if (record.outcome === "applied" && (record.from !== record.to || accountChanged) && (record.to === "authenticated:claimed" || record.to === "authenticated:claimed:ready")) queryClient.invalidateQueries({
584
+ queryKey: capxulKeys.root,
585
+ ...accountChanged ? { refetchType: "none" } : {}
586
+ });
432
587
  for (const listener of listeners.current) try {
433
588
  listener(record, state);
434
589
  } catch {
@@ -442,7 +597,7 @@ function CapxulIdentityProvider({ client, children }) {
442
597
  }, []);
443
598
  const value = useMemo(() => {
444
599
  if (client === null || runtime === null) return null;
445
- const send = (event, options) => runtime.send(event, controls(options));
600
+ const send = (event, options) => runtime.send(event, toInvocationControls(options));
446
601
  const clearAuthenticatedQueries = async () => {
447
602
  await queryClient.cancelQueries({ queryKey: capxulKeys.root });
448
603
  await queryClient.resetQueries({ queryKey: capxulKeys.root });
@@ -821,4 +976,4 @@ function organizationForm(props, state, auth, submitted) {
821
976
  });
822
977
  }
823
978
  //#endregion
824
- export { useCapxulAuth as a, useCapxulIdentityOrNull as c, capxulKeys as d, useCapxulClientOrNull as f, entered as i, useCapxulSend as l, CapxulOnboardingController as n, useCapxulDestination as o, useCapxul as p, CapxulProvider as r, useCapxulIdentity as s, CapxulAuthenticationController as t, useCapxulTransitions as u };
979
+ export { useCapxul as _, useCapxulAuth as a, useCapxulIdentityOrNull as c, toInvocationControls as d, toInvocationOptions as f, useCapxulClientOrNull as g, capxulKeys as h, entered as i, useCapxulSend as l, actingAccount as m, CapxulOnboardingController as n, useCapxulDestination as o, accountKey as p, CapxulProvider as r, useCapxulIdentity as s, CapxulAuthenticationController as t, useCapxulTransitions as u };
package/dist/index.d.mts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { A as entered, C as CreateOrganizationFailure, D as OrganizationDetails, E as InvocationOptions, F as useCapxulTransitions, M as useCapxulDestination, N as useCapxulIdentity, O as ProfileDetails, P as useCapxulSend, S as CapxulSend, T as Destination, _ as ReadyDestination, a as AuthenticationSlots, b as Slot, c as ControllerAction, d as OnboardingControllerProps, f as OrgFailure, g as ProfileSlotProps, h as PendingAuthState, i as AuthenticatedState, j as useCapxulAuth, k as SendResult, l as FaultedState, m as OtpPendingState, n as AccountProgress, o as CapxulAuthenticationController, p as OrgProgress, r as ActionResult, s as CapxulOnboardingController, t as AccountFailure, u as NavigationAction, v as RetryAction, w as CreateOrganizationSubmission, x as CapxulAuth, y as SignedOutState } from "./controllers-BWmKRFy6.mjs";
2
2
  import { ReactNode, RefCallback } from "react";
3
3
  import { QueryClient, UseMutationResult, UseQueryResult } from "@tanstack/react-query";
4
- import { Account, AccountRequirement, ActivityAnnotation, ActivityAnnotationInput, ActivityDetail, ActivityKind, ActivityListParams, ActivityPage, ActivityPhase, ActivityRange, ActivityReference, ActorReference, AddressBookEntry, BudgetId, CapxulClient, CapxulError, CapxulResult, CapxulSigner, CreateOrgInput, CurrentHoldings, HostObservability, IdentityDestination, InviteMemberInput, MemberView, Money, MoneyParseErrorReason, OrgId, OrgView, OrganizationPaymentBatchInput, OrganizationPaymentInput, PartyId, Payment, PaymentDirection, PaymentDocumentRef, PaymentDocumentRender, PaymentStatus, PaymentTiming, PaymentType, PaymentsPayInput, PayrollGroupId, PayrollGroupInput, PayrollRunId, PayrollRunStatus, Permission, PermissionMethods, PermissionReadResult, Profile, RoleView, SubmittedPermissionExecution, isClaimed, isRestoring } from "@capxul/sdk";
4
+ import { Account, AccountRequirement, ActivityAnnotation, ActivityAnnotationInput, ActivityDetail, ActivityKind, ActivityListParams, ActivityPage, ActivityPhase, ActivityRange, ActivityReference, ActorReference, AddressBookEntry, BudgetId, CapxulClient, CapxulError, CapxulResult, CapxulSigner, CreateOrgInput, CurrentHoldings, HostObservability, IdentityDestination, IdentityState, InviteMemberInput, MemberView, Money, MoneyParseErrorReason, OrgId, OrgView, OrganizationPaymentBatchInput, OrganizationPaymentInput, PartyId, Payment, PaymentDirection, PaymentDocumentRef, PaymentDocumentRender, PaymentStatus, PaymentTiming, PaymentType, PaymentsPayInput, PayrollGroupId, PayrollGroupInput, PayrollRunId, PayrollRunStatus, Permission, PermissionMethods, PermissionReadResult, Profile, RoleView, SubmittedPermissionExecution, isClaimed, isRestoring } from "@capxul/sdk";
5
5
  //#region src/provider.d.ts
6
6
  type CapxulProviderSharedProps = {
7
7
  /** Bring your own QueryClient; otherwise the provider creates one. */
@@ -200,12 +200,21 @@ type UseCapxulCreateOrgReturn = UseMutationResult<OrgView, CapxulError, CreateOr
200
200
  declare function useCapxulCreateOrg(): UseCapxulCreateOrgReturn;
201
201
  //#endregion
202
202
  //#region src/hooks/use-capxul-invite-member.d.ts
203
+ /**
204
+ * R06 (#1964): `options` is the browser attempt the caller captured before the
205
+ * person pressed Send. It reaches the SDK operation unchanged, so the durable
206
+ * `org_invite_sent` and the later `org_invite_progressed` carry that attempt's
207
+ * correlation. Absent, the invitation keeps its own invocation context.
208
+ */
209
+ type UseCapxulInviteMemberInput = InviteMemberInput & {
210
+ readonly options?: InvocationOptions;
211
+ };
203
212
  /**
204
213
  * Invite a member to an Organization by email. The hook uses the closed-over `orgId`.
205
214
  * It binds directly to
206
215
  * `capxul.org(orgId).invite(input)`. On success, invalidates the member list.
207
216
  */
208
- type UseCapxulInviteMemberReturn = UseMutationResult<MemberView, CapxulError, InviteMemberInput>;
217
+ type UseCapxulInviteMemberReturn = UseMutationResult<MemberView, CapxulError, UseCapxulInviteMemberInput>;
209
218
  declare function useCapxulInviteMember(orgId: OrgId | undefined): UseCapxulInviteMemberReturn;
210
219
  //#endregion
211
220
  //#region src/headless/journey/journey-state.d.ts
@@ -383,9 +392,9 @@ interface Contact {
383
392
  interface ContactRow extends Contact {
384
393
  /** Ours: one initials rule, not one copy per screen. */
385
394
  readonly initials: string;
386
- readonly rename: (name: string) => void;
387
- readonly hide: () => void;
388
- readonly unhide: () => void;
395
+ readonly rename: (name: string, options?: InvocationOptions) => void;
396
+ readonly hide: (options?: InvocationOptions) => void;
397
+ readonly unhide: (options?: InvocationOptions) => void;
389
398
  }
390
399
  /** ADR-0023 R1: refusal CODES, never sentences — the app owns the words. */
391
400
  type ContactsRefusal = "loading" | "org-unavailable" | "unavailable";
@@ -421,7 +430,13 @@ interface ContactsListOptions {
421
430
  interface ContactsAddSlice {
422
431
  readonly value: string;
423
432
  readonly change: (text: string) => void;
424
- readonly submit: () => void;
433
+ /**
434
+ * #1960 R02: hand it the options the screen captured BEFORE the click. The
435
+ * same snapshot reaches the SDK add call and comes back on `onAdded`/
436
+ * `onFailed`, so the UI observation and the operation share one identity even
437
+ * when the reply lands after an account switch. Omitted stays omitted.
438
+ */
439
+ readonly submit: (options?: InvocationOptions) => void;
425
440
  readonly isSubmitting: boolean;
426
441
  readonly blocked: boolean;
427
442
  readonly blockedReason: ContactsAddBlockedReason | null;
@@ -431,9 +446,14 @@ interface ContactsAddSlice {
431
446
  //#region src/headless/contacts/contacts.d.ts
432
447
  interface CapxulContactsProps {
433
448
  readonly actor: ContactsActor;
434
- readonly onAdded: (contact: Contact) => void;
449
+ /**
450
+ * `options` is exactly what the caller handed `.Add.submit` or a row verb, so
451
+ * the screen's own observation of the finished action can reuse the attempt
452
+ * identity the SDK operation ran under (#1960 R02).
453
+ */
454
+ readonly onAdded: (contact: Contact, options?: InvocationOptions) => void;
435
455
  /** ADR-0023 R1: the app maps `error.code` to its own copy; no SDK sentence. */
436
- readonly onFailed: (error: CapxulError) => void;
456
+ readonly onFailed: (error: CapxulError, options?: InvocationOptions) => void;
437
457
  readonly children: ReactNode;
438
458
  }
439
459
  declare function Root$6({ actor, onAdded, onFailed, children }: CapxulContactsProps): import("react/jsx-runtime").JSX.Element;
@@ -467,6 +487,7 @@ interface ActivityReceipt {
467
487
  */
468
488
  type ActivityRow = {
469
489
  readonly kind: "payment";
490
+ readonly execution?: Payment["execution"];
470
491
  readonly reference: ActivityReference;
471
492
  readonly direction: PaymentDirection;
472
493
  readonly counterpartyLabel: string;
@@ -505,6 +526,7 @@ type SummaryWindow = "all" | "last30days" | ActivityRange;
505
526
  * in its own words.
506
527
  */
507
528
  interface ActivitySummarySlice {
529
+ readonly readWarning?: ActivityPage["readWarning"];
508
530
  readonly currency: string | null;
509
531
  readonly moneyIn: string | null;
510
532
  readonly moneyOut: string | null;
@@ -541,11 +563,17 @@ interface ActivitySearchSlice {
541
563
  readonly change: (text: string) => void;
542
564
  }
543
565
  interface ActivityRowsSlice {
566
+ readonly readWarning?: ActivityPage["readWarning"];
544
567
  /** Already filtered and already searched. */
545
568
  readonly rows: readonly ActivityRow[];
546
569
  readonly isLoading: boolean;
547
570
  readonly error: CapxulError | null;
548
- readonly retry: () => void;
571
+ /**
572
+ * #1960 R02: pass the options captured before the retry was selected and the
573
+ * re-read runs under the same attempt identity as the UI observation. Omit
574
+ * them and the read keeps its own invocation context.
575
+ */
576
+ readonly retry: (options?: InvocationOptions) => void;
549
577
  /** Server read time of the newest page, so the app can say which read is older. */
550
578
  readonly observedAt: number | null;
551
579
  }
@@ -565,6 +593,8 @@ interface ActivityLoadMoreSlice {
565
593
  readonly sentinelRef: RefCallback<HTMLElement>;
566
594
  }
567
595
  interface ActivityDetailSlice {
596
+ /** Current Payment state from the existing detail read; absent for Movements. */
597
+ readonly payment?: Pick<Payment, "status" | "execution">;
568
598
  /** ONE finished document, never the array, so no consumer writes `documents[0]`. */
569
599
  readonly receiptDocument: PaymentDocumentRef | null;
570
600
  readonly openReceiptDocument: () => Promise<PaymentDocumentRender>;
@@ -583,13 +613,15 @@ interface ActivityDetailSlice {
583
613
  readonly error: CapxulError | null;
584
614
  }
585
615
  interface CsvExport {
616
+ readonly readWarning?: ActivityPage["readWarning"];
586
617
  readonly text: string;
587
618
  readonly rowCount: number;
588
619
  /** The 5,000-row ceiling was reached and rows were left unread. */
589
620
  readonly truncated: boolean;
590
621
  }
591
622
  interface ActivityExportSlice {
592
- readonly toCsv: () => Promise<CsvExport>;
623
+ /** Same contract as `retry`: the export's pages run under the caller's attempt. */
624
+ readonly toCsv: (options?: InvocationOptions) => Promise<CsvExport>;
593
625
  readonly isExporting: boolean;
594
626
  }
595
627
  //#endregion
@@ -816,8 +848,19 @@ type DashboardScope = {
816
848
  readonly orgId?: string;
817
849
  };
818
850
  type DashboardAccessPhase = "checking" | "error" | "redirect" | "granted";
851
+ /**
852
+ * Which of the three probes produced the error the screen is showing. The
853
+ * error code alone cannot say: a provider failure reads the same whether the
854
+ * session read, the membership list or the lifecycle read raised it, and a
855
+ * consumer that reports a displayed gate error has to keep those apart
856
+ * (#1963 R05-2). It names the SOURCE only; `retry` still re-runs every failed
857
+ * probe, exactly as before.
858
+ */
859
+ type DashboardAccessProbe = "session" | "membership" | "lifecycle";
819
860
  interface DashboardAccessErrorSlice {
820
861
  readonly error: CapxulError;
862
+ /** The probe the surfaced error came from, in the frozen precedence order. */
863
+ readonly probe: DashboardAccessProbe;
821
864
  /** Re-runs each failed probe. */
822
865
  readonly retry: () => void;
823
866
  }
@@ -836,6 +879,12 @@ declare function Checking({ children }: {
836
879
  declare function DashboardError({ children }: {
837
880
  readonly children: (slice: DashboardAccessErrorSlice) => ReactNode;
838
881
  }): import("react/jsx-runtime").JSX.Element | null;
882
+ /**
883
+ * Hands the destination and draws nothing. Reaching this part is a REQUEST to
884
+ * navigate, never an arrival: the app may cancel or supersede it, so nothing
885
+ * here — and nothing a consumer hangs off it — may claim a completed redirect
886
+ * (#1963 R05-1). The destination page's own normalized pageview proves arrival.
887
+ */
839
888
  declare function Redirect({ children }: {
840
889
  readonly children: (slice: DashboardAccessRedirectSlice) => ReactNode;
841
890
  }): import("react/jsx-runtime").JSX.Element | null;
@@ -874,8 +923,14 @@ interface PayrollSummarySlice {
874
923
  }
875
924
  interface PayrollGroupsSlice {
876
925
  readonly groups: readonly PayrollGroupRow[];
877
- readonly save: (input: PayrollGroupInput) => Promise<CapxulResult<PayrollGroupRow>>;
878
- readonly remove: (id: PayrollGroupId) => Promise<CapxulResult<void>>;
926
+ /**
927
+ * R13 (#1968): `options` is the browser attempt the caller captured before the
928
+ * employer pressed the button. It reaches the SDK operation unchanged, so the
929
+ * group's `ui_payroll_action_completed` and the durable `payroll_group_changed`
930
+ * share one correlation. Absent, the write keeps its own invocation context.
931
+ */
932
+ readonly save: (input: PayrollGroupInput, options?: InvocationOptions) => Promise<CapxulResult<PayrollGroupRow>>;
933
+ readonly remove: (id: PayrollGroupId, options?: InvocationOptions) => Promise<CapxulResult<void>>;
879
934
  readonly isSaving: boolean;
880
935
  readonly isLoading: boolean;
881
936
  readonly error: CapxulError | null;
@@ -967,7 +1022,14 @@ interface PayrollRunSummarySlice {
967
1022
  interface PayrollRunActionsSlice {
968
1023
  readonly recovery: "none" | "restoring" | "required" | "unavailable";
969
1024
  readonly recoveryError: CapxulError | null;
970
- readonly submit: () => void;
1025
+ /**
1026
+ * R13 (#1968): `options` is the browser attempt captured before the employer
1027
+ * confirmed. It reaches `authorizeRun` unchanged, so the sheet's own
1028
+ * `ui_payroll_action_completed` and the durable `payroll_run_transitioned`
1029
+ * rows share one correlation. It is NEVER written into the persisted draft —
1030
+ * a restored run keeps its own identity, not a stale attempt's.
1031
+ */
1032
+ readonly submit: (options?: InvocationOptions) => void;
971
1033
  readonly isSubmitting: boolean;
972
1034
  readonly blocked: boolean;
973
1035
  readonly blockedReason: CapxulPayrollRunBlockedReason | null;
@@ -1036,7 +1098,7 @@ declare const capxulKeys: {
1036
1098
  orgRoles: (orgId: OrgId | undefined) => readonly ["capxul", "org", "pending" | OrgId, "roles"];
1037
1099
  orgTreasury: (orgId: OrgId | undefined) => readonly ["capxul", "org", "pending" | OrgId, "treasury"];
1038
1100
  orgPermissions: (orgId: OrgId | undefined) => readonly ["capxul", "org", "pending" | OrgId, "permissions"];
1039
- orgMe: (orgId: OrgId | undefined) => readonly ["capxul", "org", "pending" | OrgId, "me"];
1101
+ orgMe: (orgId: OrgId | undefined, account: string | undefined) => readonly ["capxul", "org", "pending" | OrgId, "me", string];
1040
1102
  orgLifecycle: (orgId: OrgId | undefined) => readonly ["capxul", "org", "pending" | OrgId, "lifecycle"];
1041
1103
  contacts: (scope: string) => readonly ["capxul", "contacts", string];
1042
1104
  contactsList: (scope: string, includeHidden: boolean) => readonly ["capxul", "contacts", string, "all" | "visible"];
@@ -1051,4 +1113,4 @@ declare const capxulKeys: {
1051
1113
  payment: (paymentId: string | undefined) => readonly ["capxul", "payments", string];
1052
1114
  };
1053
1115
  //#endregion
1054
- export { type AccountFailure, type AccountProgress, type ActionResult, type ActivityDetailSlice, type ActivityExportSlice, type ActivityFilterValues, type ActivityFiltersSlice, type ActivityLoadMoreSlice, type ActivityReceipt, type ActivityRow, type ActivityRowsSlice, type ActivitySearchSlice, type ActivitySummarySlice, type AuthenticatedState, type AuthenticationSlots, type CanReason, CapxulActivity, type CapxulActivityProps, type CapxulAuth, CapxulAuthenticationController, type CapxulBootstrapState, type CapxulBootstrapStatus, CapxulContacts, type CapxulContactsProps, CapxulDashboardAccess, type CapxulDashboardAccessProps, CapxulOnboardingController, CapxulOrgMember, type CapxulOrgMemberProps, CapxulPayroll, type CapxulPayrollProps, CapxulPayrollRun, type CapxulPayrollRunBlockedReason, type CapxulPayrollRunProps, CapxulProvider, type CapxulProviderProps, type CapxulSend, CapxulSendMoney, type CapxulSendMoneyBlockedReason, type CapxulSendMoneyProps, type Contact, type ContactRelationship, type ContactRow, type ContactsActor, type ContactsAddBlockedReason, type ContactsAddError, type ContactsAddSlice, type ContactsListOptions, type ContactsListSlice, type ContactsRefusal, type ContactsSummarySlice, type ControllerAction, type CreateOrganizationFailure, type CreateOrganizationSubmission, type CsvExport, type DashboardAccessErrorSlice, type DashboardAccessPhase, type DashboardAccessRedirectSlice, type DashboardScope, type Destination, type FaultedState, type HandleAvailability, type ImageUploadInput, type ImageUploadTarget, type InvocationOptions, type NavigationAction, type OnboardingControllerProps, type OnboardingIntent, type OnboardingJourney, type OnboardingJourneyPosition, type OnboardingOrigin, type OnboardingStep, type OrgFailure, type OrgMemberAction, type OrgMemberCanSlice, type OrgMemberStanding, type OrgMemberStandingSlice, type OrgProgress, type OrganizationDetails, type OrganizationDraft, type OtpPendingState, type PayoutDraftChain, type PayoutDraftEntry, type PayrollGroupRow, type PayrollGroupsSlice, type PayrollRecipientOption, type PayrollRunActionsSlice, type PayrollRunAmountEntry, type PayrollRunAmountsSlice, type PayrollRunAssetSlice, type PayrollRunGroupOption, type PayrollRunPrefill, type PayrollRunRecipientsSlice, type PayrollRunStatus, type PayrollRunSummarySlice, type PayrollSummarySlice, type PendingAuthState, type ProfileDetails, type ProfileDraft, type ProfileSlotProps, type ReadyDestination, type RetryAction, type SendActor, type SendMoneyActionsSlice, type SendMoneyAmountSlice, type SendMoneyAssetError, type SendMoneyAssetOption, type SendMoneyAssetSlice, type SendMoneyRecipientSlice, type SendMoneyRecipientStatus, type SendMoneySourceError, type SendMoneySourceOption, type SendMoneySourceSlice, type SendResult, type SentPayment, type SentPayrollRun, type SignedOutState, type Slot, type SummaryWindow, type UseCapxulAccountBalanceOptions, type UseCapxulAccountBalanceReturn, type UseCapxulAccountFundReturn, type UseCapxulCreateCommitmentReturn, type UseCapxulCreateOrgReturn, type UseCapxulHandleAvailabilityReturn, type UseCapxulImageUploadReturn, type UseCapxulInviteMemberReturn, type UseCapxulOrgMembersOptions, type UseCapxulOrgMembersReturn, type UseCapxulOrgRolesOptions, type UseCapxulOrgRolesReturn, type UseCapxulOrgTreasuryOptions, type UseCapxulOrgTreasuryReturn, type UseCapxulOrgsReturn, type UseCapxulPayReturn, type UseCapxulPaymentReturn, type UseCapxulPaymentsReturn, type UseCapxulProfileReturn, type UseCapxulRedirectPaymentReturn, type UseCapxulUploadImageReturn, acknowledgeOnboardingDestination, activeOnboardingRecovery, capxulKeys, clearOnboardingJourney, currentOnboardingJourneyId, entered, invalidateOnboardingJourneyObservation, isClaimed, isRestoring, loadOnboardingJourney, onboardingJourneyPosition, saveOnboardingJourney, startOnboardingJourney, useCapxul, useCapxulAccountBalance, useCapxulAccountFund, useCapxulActivity, useCapxulActivityDetail, useCapxulAnnotateMovement, useCapxulAuth, useCapxulCancelPayment, useCapxulClaimPayment, useCapxulClientOrNull, useCapxulCreateCommitment, useCapxulCreateOrg, useCapxulDestination, useCapxulHandleAvailability, useCapxulHoldings, useCapxulIdentity, useCapxulImageUpload, useCapxulInviteMember, useCapxulOrgMembers, useCapxulOrgRoles, useCapxulOrgTreasury, useCapxulOrganizationPay, useCapxulOrganizationPayBatch, useCapxulOrgs, useCapxulPay, useCapxulPayment, useCapxulPayments, useCapxulPermission, useCapxulPermissionAssign, useCapxulPermissionChange, useCapxulPermissionCreate, useCapxulPermissionReplace, useCapxulPermissionRevoke, useCapxulPermissions, useCapxulProfile, useCapxulRedirectPayment, useCapxulSend, useCapxulTransitions, useCapxulUploadImage };
1116
+ export { type AccountFailure, type AccountProgress, type ActionResult, type ActivityDetailSlice, type ActivityExportSlice, type ActivityFilterValues, type ActivityFiltersSlice, type ActivityLoadMoreSlice, type ActivityReceipt, type ActivityRow, type ActivityRowsSlice, type ActivitySearchSlice, type ActivitySummarySlice, type AuthenticatedState, type AuthenticationSlots, type CanReason, CapxulActivity, type CapxulActivityProps, type CapxulAuth, CapxulAuthenticationController, type CapxulBootstrapState, type CapxulBootstrapStatus, CapxulContacts, type CapxulContactsProps, CapxulDashboardAccess, type CapxulDashboardAccessProps, CapxulOnboardingController, CapxulOrgMember, type CapxulOrgMemberProps, CapxulPayroll, type CapxulPayrollProps, CapxulPayrollRun, type CapxulPayrollRunBlockedReason, type CapxulPayrollRunProps, CapxulProvider, type CapxulProviderProps, type CapxulSend, CapxulSendMoney, type CapxulSendMoneyBlockedReason, type CapxulSendMoneyProps, type Contact, type ContactRelationship, type ContactRow, type ContactsActor, type ContactsAddBlockedReason, type ContactsAddError, type ContactsAddSlice, type ContactsListOptions, type ContactsListSlice, type ContactsRefusal, type ContactsSummarySlice, type ControllerAction, type CreateOrganizationFailure, type CreateOrganizationSubmission, type CsvExport, type DashboardAccessErrorSlice, type DashboardAccessPhase, type DashboardAccessProbe, type DashboardAccessRedirectSlice, type DashboardScope, type Destination, type FaultedState, type HandleAvailability, type ImageUploadInput, type ImageUploadTarget, type InvocationOptions, type NavigationAction, type OnboardingControllerProps, type OnboardingIntent, type OnboardingJourney, type OnboardingJourneyPosition, type OnboardingOrigin, type OnboardingStep, type OrgFailure, type OrgMemberAction, type OrgMemberCanSlice, type OrgMemberStanding, type OrgMemberStandingSlice, type OrgProgress, type OrganizationDetails, type OrganizationDraft, type OtpPendingState, type PayoutDraftChain, type PayoutDraftEntry, type PayrollGroupRow, type PayrollGroupsSlice, type PayrollRecipientOption, type PayrollRunActionsSlice, type PayrollRunAmountEntry, type PayrollRunAmountsSlice, type PayrollRunAssetSlice, type PayrollRunGroupOption, type PayrollRunPrefill, type PayrollRunRecipientsSlice, type PayrollRunStatus, type PayrollRunSummarySlice, type PayrollSummarySlice, type PendingAuthState, type ProfileDetails, type ProfileDraft, type ProfileSlotProps, type ReadyDestination, type RetryAction, type SendActor, type SendMoneyActionsSlice, type SendMoneyAmountSlice, type SendMoneyAssetError, type SendMoneyAssetOption, type SendMoneyAssetSlice, type SendMoneyRecipientSlice, type SendMoneyRecipientStatus, type SendMoneySourceError, type SendMoneySourceOption, type SendMoneySourceSlice, type SendResult, type SentPayment, type SentPayrollRun, type SignedOutState, type Slot, type SummaryWindow, type UseCapxulAccountBalanceOptions, type UseCapxulAccountBalanceReturn, type UseCapxulAccountFundReturn, type UseCapxulCreateCommitmentReturn, type UseCapxulCreateOrgReturn, type UseCapxulHandleAvailabilityReturn, type UseCapxulImageUploadReturn, type UseCapxulInviteMemberInput, type UseCapxulInviteMemberReturn, type UseCapxulOrgMembersOptions, type UseCapxulOrgMembersReturn, type UseCapxulOrgRolesOptions, type UseCapxulOrgRolesReturn, type UseCapxulOrgTreasuryOptions, type UseCapxulOrgTreasuryReturn, type UseCapxulOrgsReturn, type UseCapxulPayReturn, type UseCapxulPaymentReturn, type UseCapxulPaymentsReturn, type UseCapxulProfileReturn, type UseCapxulRedirectPaymentReturn, type UseCapxulUploadImageReturn, acknowledgeOnboardingDestination, activeOnboardingRecovery, capxulKeys, clearOnboardingJourney, currentOnboardingJourneyId, entered, invalidateOnboardingJourneyObservation, isClaimed, isRestoring, loadOnboardingJourney, onboardingJourneyPosition, saveOnboardingJourney, startOnboardingJourney, useCapxul, useCapxulAccountBalance, useCapxulAccountFund, useCapxulActivity, useCapxulActivityDetail, useCapxulAnnotateMovement, useCapxulAuth, useCapxulCancelPayment, useCapxulClaimPayment, useCapxulClientOrNull, useCapxulCreateCommitment, useCapxulCreateOrg, useCapxulDestination, useCapxulHandleAvailability, useCapxulHoldings, useCapxulIdentity, useCapxulImageUpload, useCapxulInviteMember, useCapxulOrgMembers, useCapxulOrgRoles, useCapxulOrgTreasury, useCapxulOrganizationPay, useCapxulOrganizationPayBatch, useCapxulOrgs, useCapxulPay, useCapxulPayment, useCapxulPayments, useCapxulPermission, useCapxulPermissionAssign, useCapxulPermissionChange, useCapxulPermissionCreate, useCapxulPermissionReplace, useCapxulPermissionRevoke, useCapxulPermissions, useCapxulProfile, useCapxulRedirectPayment, useCapxulSend, useCapxulTransitions, useCapxulUploadImage };
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { a as useCapxulAuth, c as useCapxulIdentityOrNull, d as capxulKeys, f as useCapxulClientOrNull, i as entered, l as useCapxulSend, n as CapxulOnboardingController, o as useCapxulDestination, p as useCapxul, r as CapxulProvider, s as useCapxulIdentity, t as CapxulAuthenticationController, u as useCapxulTransitions } from "./controllers-BPEhjKX9.mjs";
2
+ import { _ as useCapxul, a as useCapxulAuth, c as useCapxulIdentityOrNull, d as toInvocationControls, f as toInvocationOptions, g as useCapxulClientOrNull, h as capxulKeys, i as entered, l as useCapxulSend, m as actingAccount, n as CapxulOnboardingController, o as useCapxulDestination, p as accountKey, r as CapxulProvider, s as useCapxulIdentity, t as CapxulAuthenticationController, u as useCapxulTransitions } from "./controllers-DXmEHrnw.mjs";
3
3
  import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
4
4
  import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
5
5
  import { CAPXUL_OPERATIONS, CapxulError, EVM_ADDRESS_RE, Errors, HANDLE_RE, PAYMENT_DIRECTIONS, PAYMENT_STATUSES, fingerprintPaymentIntent, formatMoney, isCapxulError, isClaimed, isClaimed as isClaimed$1, isMoneyParseError, isRestoring, isRestoring as isRestoring$1, parseMoney, paymentPhase, resolveIdentityDestination, toEvmAddress, toPartyId } from "@capxul/sdk";
@@ -47,8 +47,9 @@ function useCapxulProfile() {
47
47
  //#region src/hooks/use-capxul-account-balance.ts
48
48
  function useCapxulAccountBalance(options) {
49
49
  const client = useCapxulClientOrNull();
50
+ const identity = useCapxulIdentityOrNull();
50
51
  return useQuery({
51
- queryKey: capxulKeys.accountBalance,
52
+ queryKey: accountKey(capxulKeys.accountBalance, identity),
52
53
  queryFn: async () => {
53
54
  return unwrapCapxulResult(await requireBootstrappedClient(client, CAPXUL_OPERATIONS.accounts.read).accounts.read());
54
55
  },
@@ -302,7 +303,7 @@ function useCapxulPayments(options) {
302
303
  const client = useCapxulClientOrNull();
303
304
  const identity = useCapxulIdentityOrNull();
304
305
  return useQuery({
305
- queryKey: capxulKeys.payments,
306
+ queryKey: accountKey(capxulKeys.payments, identity),
306
307
  queryFn: async () => {
307
308
  return unwrapCapxulResult(await requireBootstrappedClient(client, CAPXUL_OPERATIONS.payments.list).payments.list());
308
309
  },
@@ -311,8 +312,9 @@ function useCapxulPayments(options) {
311
312
  }
312
313
  function useCapxulPayment(paymentId, options) {
313
314
  const client = useCapxulClientOrNull();
315
+ const identity = useCapxulIdentityOrNull();
314
316
  return useQuery({
315
- queryKey: capxulKeys.payment(paymentId),
317
+ queryKey: accountKey(capxulKeys.payment(paymentId), identity),
316
318
  queryFn: async () => {
317
319
  if (paymentId === void 0) throw Errors.invalidInput("paymentId", "required for payments.get");
318
320
  return unwrapCapxulResult(await requireBootstrappedClient(client, CAPXUL_OPERATIONS.payments.get).payments.get(paymentId));
@@ -430,7 +432,7 @@ function useCapxulActivity(params) {
430
432
  const client = useCapxulClientOrNull();
431
433
  const identity = useCapxulIdentityOrNull();
432
434
  return useQuery({
433
- queryKey: [...capxulKeys.activity, params ?? {}],
435
+ queryKey: accountKey([...capxulKeys.activity, params ?? {}], identity),
434
436
  queryFn: async () => {
435
437
  return unwrapCapxulResult(await requireBootstrappedClient(client, CAPXUL_OPERATIONS.activity.list).activity.list(params));
436
438
  },
@@ -441,7 +443,7 @@ function useCapxulActivityDetail(reference, actor) {
441
443
  const client = useCapxulClientOrNull();
442
444
  const identity = useCapxulIdentityOrNull();
443
445
  return useQuery({
444
- queryKey: capxulKeys.activityDetail(reference, actor),
446
+ queryKey: accountKey(capxulKeys.activityDetail(reference, actor), identity),
445
447
  queryFn: async () => {
446
448
  if (reference === void 0) throw Errors.invalidInput("reference", "required for activity.get");
447
449
  return unwrapCapxulResult(await requireBootstrappedClient(client, CAPXUL_OPERATIONS.activity.get).activity.get(reference, actor === void 0 ? void 0 : { actor }));
@@ -466,7 +468,7 @@ function useCapxulHoldings(params) {
466
468
  const identity = useCapxulIdentityOrNull();
467
469
  const actor = params?.actor;
468
470
  return useQuery({
469
- queryKey: capxulKeys.holdings(actor),
471
+ queryKey: accountKey(capxulKeys.holdings(actor), identity),
470
472
  queryFn: async () => {
471
473
  return unwrapCapxulResult(await requireBootstrappedClient(client, CAPXUL_OPERATIONS.holdings.current).holdings.current(actor === void 0 ? void 0 : { actor }));
472
474
  },
@@ -477,8 +479,9 @@ function useCapxulHoldings(params) {
477
479
  //#region src/hooks/use-capxul-orgs.ts
478
480
  function useCapxulOrgs(options) {
479
481
  const client = useCapxulClientOrNull();
482
+ const identity = useCapxulIdentityOrNull();
480
483
  return useQuery({
481
- queryKey: capxulKeys.orgs,
484
+ queryKey: accountKey(capxulKeys.orgs, identity),
482
485
  queryFn: async () => {
483
486
  return unwrapCapxulResult(await requireBootstrappedClient(client, "orgs").orgs());
484
487
  },
@@ -517,9 +520,10 @@ function useCapxulOrgRoles(orgId, options) {
517
520
  //#region src/hooks/use-capxul-org-treasury.ts
518
521
  function useCapxulOrgTreasury(orgId, options) {
519
522
  const client = useCapxulClientOrNull();
523
+ const identity = useCapxulIdentityOrNull();
520
524
  const enabled = options?.enabled ?? true;
521
525
  return useQuery({
522
- queryKey: capxulKeys.orgTreasury(orgId),
526
+ queryKey: accountKey(capxulKeys.orgTreasury(orgId), identity),
523
527
  queryFn: async () => {
524
528
  if (orgId === void 0) throw Errors.invalidInput("orgId", "required for useCapxulOrgTreasury");
525
529
  return unwrapCapxulResult(await requireBootstrappedClient(client, CAPXUL_OPERATIONS.org.treasury).org(orgId).treasury());
@@ -547,9 +551,9 @@ function useCapxulInviteMember(orgId) {
547
551
  const client = useCapxulClientOrNull();
548
552
  const queryClient = useQueryClient();
549
553
  return useMutation({
550
- mutationFn: async (input) => {
554
+ mutationFn: async ({ options, ...input }) => {
551
555
  if (orgId === void 0) throw Errors.invalidInput("orgId", "required for useCapxulInviteMember");
552
- return unwrapCapxulResult(await requireBootstrappedClient(client, CAPXUL_OPERATIONS.org.invite).org(orgId).invite(input));
556
+ return unwrapCapxulResult(await requireBootstrappedClient(client, CAPXUL_OPERATIONS.org.invite).org(orgId).invite(input, toInvocationControls(options)));
553
557
  },
554
558
  onSuccess: async () => {
555
559
  if (orgId === void 0) return;
@@ -879,6 +883,8 @@ function useCapxulUploadImage() {
879
883
  }
880
884
  //#endregion
881
885
  //#region src/headless/contacts/use-contacts.ts
886
+ /** One captured snapshot, or nothing — never a fabricated empty options object. */
887
+ const carry$1 = (options) => options === void 0 ? {} : { options };
882
888
  function useContacts(input) {
883
889
  const { actor, onAdded, onFailed } = input;
884
890
  const client = useCapxulClientOrNull();
@@ -895,35 +901,40 @@ function useContacts(input) {
895
901
  const writeMutate = useMutation({
896
902
  mutationFn: async (command) => {
897
903
  const target = requireBook(book, `addressBook.${command.kind}`);
904
+ const controls = toInvocationControls(command.options);
898
905
  return unwrapCapxulResult(command.kind === "rename" ? await target.label({
899
906
  entryId: command.id,
900
907
  label: command.name
901
- }) : command.kind === "hide" ? await target.hide(command.id) : await target.unhide(command.id));
908
+ }, controls) : command.kind === "hide" ? await target.hide(command.id, controls) : await target.unhide(command.id, controls));
902
909
  },
903
910
  onSettled: invalidate,
904
- onError: onFailed
911
+ onError: (error, command) => onFailed(error, command.options)
905
912
  }).mutate;
906
913
  const [value, setValue] = useState("");
907
914
  const [addError, setAddError] = useState(null);
908
915
  const addContact = useMutation({
909
- mutationFn: async (ref) => unwrapCapxulResult(await requireBook(book, CAPXUL_OPERATIONS.addressBook.add).add({ ref })),
916
+ mutationFn: async (command) => unwrapCapxulResult(await requireBook(book, CAPXUL_OPERATIONS.addressBook.add).add({ ref: command.ref }, toInvocationControls(command.options))),
910
917
  onSettled: invalidate
911
918
  });
912
919
  const addMutate = addContact.mutate;
913
920
  const blockedReason = refusal !== null ? "org-unavailable" : value.trim() === "" ? "empty" : null;
914
921
  const submitGate = useRef(false);
915
- const submit = useCallback(() => {
922
+ const submit = useCallback((options) => {
916
923
  if (blockedReason !== null || submitGate.current) return;
924
+ const attempt = toInvocationOptions(options);
917
925
  submitGate.current = true;
918
926
  setAddError(null);
919
- addMutate(refFromTypedText(value), {
927
+ addMutate({
928
+ ref: refFromTypedText(value),
929
+ ...carry$1(attempt)
930
+ }, {
920
931
  onSuccess: (entry) => {
921
932
  setValue("");
922
- onAdded(toContact(entry));
933
+ onAdded(toContact(entry), attempt);
923
934
  },
924
935
  onError: (error) => {
925
936
  setAddError(error.code === "INVALID_INPUT" ? "unresolved" : "failed");
926
- onFailed(error);
937
+ onFailed(error, attempt);
927
938
  },
928
939
  onSettled: () => {
929
940
  submitGate.current = false;
@@ -944,18 +955,21 @@ function useContacts(input) {
944
955
  book,
945
956
  scope,
946
957
  refusal,
947
- rename: useCallback((id, name) => writeMutate({
958
+ rename: useCallback((id, name, options) => writeMutate({
948
959
  kind: "rename",
949
960
  id,
950
- name
961
+ name,
962
+ ...carry$1(toInvocationOptions(options))
951
963
  }), [writeMutate]),
952
- hide: useCallback((id) => writeMutate({
964
+ hide: useCallback((id, options) => writeMutate({
953
965
  kind: "hide",
954
- id
966
+ id,
967
+ ...carry$1(toInvocationOptions(options))
955
968
  }), [writeMutate]),
956
- unhide: useCallback((id) => writeMutate({
969
+ unhide: useCallback((id, options) => writeMutate({
957
970
  kind: "unhide",
958
- id
971
+ id,
972
+ ...carry$1(toInvocationOptions(options))
959
973
  }), [writeMutate]),
960
974
  add: {
961
975
  value,
@@ -973,10 +987,11 @@ function useContacts(input) {
973
987
  * query key, so React Query issues ONE request for them.
974
988
  */
975
989
  function useContactsList(engine, options) {
990
+ const identity = useCapxulIdentityOrNull();
976
991
  const includeHidden = options.show === "all";
977
992
  const { book, refusal } = engine;
978
993
  const query = useQuery({
979
- queryKey: capxulKeys.contactsList(engine.scope, includeHidden),
994
+ queryKey: accountKey(capxulKeys.contactsList(engine.scope, includeHidden), identity),
980
995
  queryFn: async () => unwrapCapxulResult(await requireBook(book, CAPXUL_OPERATIONS.addressBook.list).list({ includeHidden })),
981
996
  enabled: book !== null
982
997
  });
@@ -989,9 +1004,9 @@ function useContactsList(engine, options) {
989
1004
  if (entries === void 0) return [];
990
1005
  return (limit === void 0 ? entries : entries.slice(0, limit)).map((entry) => Object.assign(toContact(entry), {
991
1006
  initials: initialsOf(entry.label),
992
- rename: (name) => rename(entry.id, name),
993
- hide: () => hide(entry.id),
994
- unhide: () => unhide(entry.id)
1007
+ rename: (name, attempt) => rename(entry.id, name, attempt),
1008
+ hide: (attempt) => hide(entry.id, attempt),
1009
+ unhide: (attempt) => unhide(entry.id, attempt)
995
1010
  }));
996
1011
  }, [
997
1012
  entries,
@@ -1165,9 +1180,10 @@ function activityDetailKey(actor, reference) {
1165
1180
  }
1166
1181
  function useActivityDetail(reference, actor) {
1167
1182
  const client = useCapxulClientOrNull();
1183
+ const identity = useCapxulIdentityOrNull();
1168
1184
  const enabled = useActivityEnabled() && reference !== void 0;
1169
1185
  const detail = useQuery({
1170
- queryKey: capxulKeys.activityDetail(reference, actor),
1186
+ queryKey: accountKey(capxulKeys.activityDetail(reference, actor), identity),
1171
1187
  queryFn: async () => {
1172
1188
  if (reference === void 0) throw Errors.invalidInput("reference", "required for activity.get");
1173
1189
  return unwrapCapxulResult(await requireBootstrappedClient(client, CAPXUL_OPERATIONS.activity.get).activity.get(reference, actor === void 0 ? void 0 : { actor }));
@@ -1213,6 +1229,10 @@ function useActivityDetail(reference, actor) {
1213
1229
  typed
1214
1230
  ]);
1215
1231
  return {
1232
+ ...detail.data?.kind === "payment" ? { payment: {
1233
+ status: detail.data.payment.status,
1234
+ ...detail.data.payment.execution === void 0 ? {} : { execution: detail.data.payment.execution }
1235
+ } } : {},
1216
1236
  receiptDocument,
1217
1237
  openReceiptDocument,
1218
1238
  isOpeningDocument,
@@ -1322,10 +1342,11 @@ function useUtcDay(active) {
1322
1342
  }
1323
1343
  function useActivitySummary(actor, window) {
1324
1344
  const client = useCapxulClientOrNull();
1345
+ const identity = useCapxulIdentityOrNull();
1325
1346
  const enabled = useActivityEnabled();
1326
1347
  const range = resolveWindow(window, useUtcDay(window === "last30days") * DAY_MS);
1327
1348
  const summary = useQuery({
1328
- queryKey: capxulKeys.activitySummary(actor, range),
1349
+ queryKey: accountKey(capxulKeys.activitySummary(actor, range), identity),
1329
1350
  queryFn: async () => {
1330
1351
  const params = {};
1331
1352
  if (actor !== void 0) params.actor = actor;
@@ -1335,7 +1356,7 @@ function useActivitySummary(actor, window) {
1335
1356
  enabled
1336
1357
  });
1337
1358
  const primary = useQuery({
1338
- queryKey: [...capxulKeys.holdings(actor), "primary"],
1359
+ queryKey: accountKey([...capxulKeys.holdings(actor), "primary"], identity),
1339
1360
  queryFn: async () => {
1340
1361
  return unwrapCapxulResult(await requireBootstrappedClient(client, CAPXUL_OPERATIONS.holdings.primary).holdings.primary(actor === void 0 ? void 0 : { actor }));
1341
1362
  },
@@ -1357,7 +1378,8 @@ function useActivitySummary(actor, window) {
1357
1378
  pending: null,
1358
1379
  count: summary.data?.count ?? 0,
1359
1380
  isLoading,
1360
- error: asCapxulError$1(summary.error)
1381
+ error: asCapxulError$1(summary.error),
1382
+ ...summary.data?.readWarning === void 0 ? {} : { readWarning: summary.data.readWarning }
1361
1383
  };
1362
1384
  const display = (raw) => formatMoney({
1363
1385
  currency: bucket.currency,
@@ -1372,7 +1394,8 @@ function useActivitySummary(actor, window) {
1372
1394
  pending: display(bucket.pendingIn),
1373
1395
  count: summary.data?.count ?? 0,
1374
1396
  isLoading,
1375
- error: asCapxulError$1(summary.error)
1397
+ error: asCapxulError$1(summary.error),
1398
+ ...summary.data?.readWarning === void 0 ? {} : { readWarning: summary.data.readWarning }
1376
1399
  };
1377
1400
  }
1378
1401
  //#endregion
@@ -1413,16 +1436,18 @@ function toActivityRow(item, actor) {
1413
1436
  phase: MOVEMENT_PHASE,
1414
1437
  receipt: item.receipt
1415
1438
  };
1439
+ const reference = actor === void 0 ? {
1440
+ kind: "payment",
1441
+ id: item.id
1442
+ } : {
1443
+ kind: "payment",
1444
+ id: item.id,
1445
+ actor
1446
+ };
1416
1447
  return {
1417
1448
  kind: "payment",
1418
- reference: actor === void 0 ? {
1419
- kind: "payment",
1420
- id: item.id
1421
- } : {
1422
- kind: "payment",
1423
- id: item.id,
1424
- actor
1425
- },
1449
+ ...item.execution === void 0 ? {} : { execution: item.execution },
1450
+ reference,
1426
1451
  direction: item.direction,
1427
1452
  counterpartyLabel: item.counterpartyLabel,
1428
1453
  amountDisplay,
@@ -1432,7 +1457,10 @@ function toActivityRow(item, actor) {
1432
1457
  ledgerKind: item.ledgerKind,
1433
1458
  paymentType: item.paymentType,
1434
1459
  status: item.status,
1435
- phase: paymentPhase(item.status),
1460
+ phase: item.execution?.notSent ? {
1461
+ ...paymentPhase(item.status),
1462
+ needsAction: item.direction !== "in"
1463
+ } : paymentPhase(item.status),
1436
1464
  receipt: item.receipt
1437
1465
  };
1438
1466
  }
@@ -1506,7 +1534,7 @@ function buildCsv(items, actor, search) {
1506
1534
  row.direction,
1507
1535
  item.amount.value,
1508
1536
  item.amount.currency,
1509
- row.kind === "payment" ? row.status : "",
1537
+ row.kind === "payment" ? row.execution?.notSent ? "not_sent" : row.execution?.status === "unresolved" ? "verifying" : row.status : "",
1510
1538
  row.reference.id,
1511
1539
  row.receipt?.transactionHash ?? ""
1512
1540
  ]));
@@ -1538,6 +1566,8 @@ function clampPageSize(pageSize) {
1538
1566
  function useActivity(input) {
1539
1567
  const { actor, pageSize } = input;
1540
1568
  const client = useCapxulClientOrNull();
1569
+ const identity = useCapxulIdentityOrNull();
1570
+ const account = actingAccount(identity);
1541
1571
  const enabled = useActivityEnabled();
1542
1572
  const limit = clampPageSize(pageSize);
1543
1573
  const warned = useRef(false);
@@ -1567,21 +1597,51 @@ function useActivity(input) {
1567
1597
  if (filter !== void 0) params.filter = filter;
1568
1598
  return params;
1569
1599
  }, [actor, filter]);
1600
+ const selectedRetry = useRef(void 0);
1601
+ const takeSelectedRetry = useCallback(() => {
1602
+ const attempt = selectedRetry.current;
1603
+ selectedRetry.current = void 0;
1604
+ return toInvocationControls(attempt);
1605
+ }, []);
1570
1606
  const query = useInfiniteQuery({
1571
- queryKey: [
1607
+ queryKey: accountKey([
1572
1608
  ...capxulKeys.activity,
1573
1609
  "list",
1574
1610
  actor ?? "self",
1575
1611
  limit,
1576
1612
  filter ?? "none"
1577
- ],
1613
+ ], identity),
1578
1614
  queryFn: async ({ pageParam }) => {
1579
- return unwrapCapxulResult(await requireBootstrappedClient(client, CAPXUL_OPERATIONS.activity.list).activity.list(listParams(pageParam, limit)));
1615
+ return unwrapCapxulResult(await requireBootstrappedClient(client, CAPXUL_OPERATIONS.activity.list).activity.list(listParams(pageParam, limit), takeSelectedRetry()));
1580
1616
  },
1581
1617
  initialPageParam: void 0,
1582
1618
  getNextPageParam: (last) => last.cursor ?? void 0,
1583
1619
  enabled
1584
1620
  });
1621
+ const queryClient = useQueryClient();
1622
+ useEffect(() => {
1623
+ if (!enabled || client === null) return;
1624
+ let stop;
1625
+ let cancelled = false;
1626
+ client.activity.subscribe(() => {
1627
+ if (cancelled) return;
1628
+ queryClient.invalidateQueries({ queryKey: capxulKeys.activity });
1629
+ }, actor === void 0 ? void 0 : { actor }).then((result) => {
1630
+ if (!result.ok) return;
1631
+ if (cancelled) result.value();
1632
+ else stop = result.value;
1633
+ });
1634
+ return () => {
1635
+ cancelled = true;
1636
+ stop?.();
1637
+ };
1638
+ }, [
1639
+ account,
1640
+ actor,
1641
+ client,
1642
+ enabled,
1643
+ queryClient
1644
+ ]);
1585
1645
  const pages = query.data?.pages;
1586
1646
  const rows = useMemo(() => {
1587
1647
  return (pages?.flatMap((page) => page.items) ?? []).map((item) => toActivityRow(item, actor)).filter((row) => matchesSearch(row, searchText));
@@ -1591,6 +1651,16 @@ function useActivity(input) {
1591
1651
  searchText
1592
1652
  ]);
1593
1653
  const { fetchNextPage, hasNextPage, isFetchingNextPage, refetch } = query;
1654
+ const emptyContinuation = pages?.at(-1)?.items.length === 0 && hasNextPage && query.error === null;
1655
+ useEffect(() => {
1656
+ if (enabled && emptyContinuation && !isFetchingNextPage) fetchNextPage();
1657
+ }, [
1658
+ enabled,
1659
+ emptyContinuation,
1660
+ fetchNextPage,
1661
+ isFetchingNextPage,
1662
+ pages
1663
+ ]);
1594
1664
  const pagingRef = useRef({
1595
1665
  fetchNextPage,
1596
1666
  hasNextPage,
@@ -1624,21 +1694,25 @@ function useActivity(input) {
1624
1694
  observerRef.current = observer;
1625
1695
  }, [loadMore]);
1626
1696
  const [isExporting, setIsExporting] = useState(false);
1627
- const toCsv = useCallback(async () => {
1697
+ const toCsv = useCallback(async (options) => {
1628
1698
  setIsExporting(true);
1699
+ const controls = toInvocationControls(toInvocationOptions(options));
1629
1700
  try {
1630
1701
  const bootstrapped = requireBootstrappedClient(client, CAPXUL_OPERATIONS.activity.list);
1631
1702
  const items = [];
1703
+ let readWarning;
1632
1704
  let cursor;
1633
1705
  for (let page = 0; page < EXPORT_PAGE_CEILING; page += 1) {
1634
- const result = unwrapCapxulResult(await bootstrapped.activity.list(listParams(cursor, EXPORT_PAGE_SIZE)));
1706
+ const result = unwrapCapxulResult(await bootstrapped.activity.list(listParams(cursor, EXPORT_PAGE_SIZE), controls));
1707
+ readWarning ??= result.readWarning;
1635
1708
  items.push(...result.items);
1636
1709
  cursor = result.cursor ?? void 0;
1637
1710
  if (cursor === void 0) break;
1638
1711
  }
1639
1712
  return {
1640
1713
  ...buildCsv(items, actor, searchText),
1641
- truncated: cursor !== void 0
1714
+ truncated: cursor !== void 0,
1715
+ ...readWarning === void 0 ? {} : { readWarning }
1642
1716
  };
1643
1717
  } finally {
1644
1718
  setIsExporting(false);
@@ -1654,9 +1728,19 @@ function useActivity(input) {
1654
1728
  setDirection(void 0);
1655
1729
  setStatus([]);
1656
1730
  }, []);
1657
- const retry = useCallback(() => {
1731
+ const retry = useCallback((options) => {
1732
+ selectedRetry.current = toInvocationOptions(options);
1658
1733
  refetch();
1659
- }, [refetch]);
1734
+ queryClient.invalidateQueries({
1735
+ queryKey: capxulKeys.activitySummary(actor).slice(0, -1),
1736
+ predicate: (cached) => cached.queryKey.at(-1) === accountKey([], identity)[0]
1737
+ });
1738
+ }, [
1739
+ actor,
1740
+ identity,
1741
+ queryClient,
1742
+ refetch
1743
+ ]);
1660
1744
  return {
1661
1745
  actor,
1662
1746
  filters: {
@@ -1679,10 +1763,11 @@ function useActivity(input) {
1679
1763
  },
1680
1764
  rows: {
1681
1765
  rows,
1682
- isLoading: query.isLoading,
1766
+ isLoading: query.isLoading || rows.length === 0 && emptyContinuation,
1683
1767
  error: asCapxulError$1(query.error),
1684
1768
  retry,
1685
- observedAt: pages === void 0 ? null : pages[pages.length - 1]?.observedAt ?? null
1769
+ observedAt: pages === void 0 ? null : pages[pages.length - 1]?.observedAt ?? null,
1770
+ ...pages?.some((page) => page.readWarning === "indexer-unavailable") ? { readWarning: "indexer-unavailable" } : {}
1686
1771
  },
1687
1772
  loadMore: {
1688
1773
  hasMore: hasNextPage,
@@ -1744,11 +1829,12 @@ function DetailInstance({ actor, reference, children }) {
1744
1829
  }
1745
1830
  function Detail({ reference, children }) {
1746
1831
  const { actor } = useActivityContext();
1832
+ const identity = useCapxulIdentityOrNull();
1747
1833
  return /* @__PURE__ */ jsx(DetailInstance, {
1748
1834
  actor,
1749
1835
  reference,
1750
1836
  children
1751
- }, activityDetailKey(actor, reference));
1837
+ }, JSON.stringify(accountKey([activityDetailKey(actor, reference)], identity)));
1752
1838
  }
1753
1839
  function Export({ children }) {
1754
1840
  return /* @__PURE__ */ jsx(Fragment, { children: children(useActivityContext().csv) });
@@ -1771,7 +1857,7 @@ function useOrgMeReading(orgId) {
1771
1857
  const ready = client !== null && identity !== null && isClaimed$1(identity);
1772
1858
  const mayStillOpen = client === null || identity === null || isRestoring$1(identity);
1773
1859
  const query = useQuery({
1774
- queryKey: capxulKeys.orgMe(orgId),
1860
+ queryKey: capxulKeys.orgMe(orgId, actingAccount(identity)),
1775
1861
  queryFn: async () => {
1776
1862
  return unwrapCapxulResult(await requireBootstrappedClient(client, CAPXUL_OPERATIONS.org.me).org(orgId).me());
1777
1863
  },
@@ -2275,6 +2361,16 @@ function decidePhase(input) {
2275
2361
  if (input.orgLaneSettled && input.destination !== null) return "redirect";
2276
2362
  return "checking";
2277
2363
  }
2364
+ /**
2365
+ * Which probe produced the surfaced error — the SAME precedence `error` itself
2366
+ * is chosen by, written once so the code and its source can never disagree.
2367
+ */
2368
+ function probeOf(session, membership, lifecycle) {
2369
+ if (session !== null) return "session";
2370
+ if (membership !== null) return "membership";
2371
+ if (lifecycle !== null) return "lifecycle";
2372
+ return null;
2373
+ }
2278
2374
  /** The engine — all the logic. NOT exported from the package barrel. */
2279
2375
  function useDashboardAccess(scope) {
2280
2376
  const client = useCapxulClientOrNull();
@@ -2311,6 +2407,7 @@ function useDashboardAccess(scope) {
2311
2407
  lifecycle.refetch
2312
2408
  ]);
2313
2409
  const error = sessionError ?? membershipError ?? lifecycleError;
2410
+ const probe = probeOf(sessionError, membershipError, lifecycleError);
2314
2411
  const phase = decidePhase({
2315
2412
  error,
2316
2413
  settled: client !== null && identity !== null && !isRestoring$1(identity) && membershipsSettled && lifecycleSettled,
@@ -2321,11 +2418,13 @@ function useDashboardAccess(scope) {
2321
2418
  return useMemo(() => ({
2322
2419
  phase,
2323
2420
  error,
2421
+ probe,
2324
2422
  destination,
2325
2423
  retry
2326
2424
  }), [
2327
2425
  phase,
2328
2426
  error,
2427
+ probe,
2329
2428
  destination,
2330
2429
  retry
2331
2430
  ]);
@@ -2370,12 +2469,19 @@ function Checking({ children }) {
2370
2469
  }
2371
2470
  function DashboardError({ children }) {
2372
2471
  const state = usePart("error");
2373
- if (state === null || state.error === null) return null;
2472
+ if (state === null || state.error === null || state.probe === null) return null;
2374
2473
  return /* @__PURE__ */ jsx(Fragment, { children: children({
2375
2474
  error: state.error,
2475
+ probe: state.probe,
2376
2476
  retry: state.retry
2377
2477
  }) });
2378
2478
  }
2479
+ /**
2480
+ * Hands the destination and draws nothing. Reaching this part is a REQUEST to
2481
+ * navigate, never an arrival: the app may cancel or supersede it, so nothing
2482
+ * here — and nothing a consumer hangs off it — may claim a completed redirect
2483
+ * (#1963 R05-1). The destination page's own normalized pageview proves arrival.
2484
+ */
2379
2485
  function Redirect({ children }) {
2380
2486
  const state = usePart("redirect");
2381
2487
  if (state === null || state.destination === null) return null;
@@ -2452,8 +2558,9 @@ function toRecipientOption(entry) {
2452
2558
  */
2453
2559
  function useOrgRoster(orgId) {
2454
2560
  const client = useCapxulClientOrNull();
2561
+ const identity = useCapxulIdentityOrNull();
2455
2562
  const query = useQuery({
2456
- queryKey: capxulKeys.contactsList(orgId ?? "pending", false),
2563
+ queryKey: accountKey(capxulKeys.contactsList(orgId ?? "pending", false), identity),
2457
2564
  queryFn: async () => unwrapCapxulResult(await scopedClient(client, CAPXUL_OPERATIONS.org.addressBook.list, orgId).addressBook.list({ includeHidden: false })),
2458
2565
  enabled: client !== null && orgId !== void 0
2459
2566
  });
@@ -2474,16 +2581,18 @@ function useOrgRoster(orgId) {
2474
2581
  }
2475
2582
  function usePayrollGroups(orgId) {
2476
2583
  const client = useCapxulClientOrNull();
2584
+ const identity = useCapxulIdentityOrNull();
2477
2585
  return useQuery({
2478
- queryKey: capxulKeys.payrollGroups(orgId),
2586
+ queryKey: accountKey(capxulKeys.payrollGroups(orgId), identity),
2479
2587
  queryFn: async () => unwrapCapxulResult(await scopedClient(client, CAPXUL_OPERATIONS.org.payroll.groups.list, orgId).payroll.groups.list()),
2480
2588
  enabled: client !== null && orgId !== void 0
2481
2589
  });
2482
2590
  }
2483
2591
  function usePayrollRuns(orgId) {
2484
2592
  const client = useCapxulClientOrNull();
2593
+ const identity = useCapxulIdentityOrNull();
2485
2594
  return useQuery({
2486
- queryKey: capxulKeys.payrollRuns(orgId),
2595
+ queryKey: accountKey(capxulKeys.payrollRuns(orgId), identity),
2487
2596
  queryFn: async () => unwrapCapxulResult(await scopedClient(client, CAPXUL_OPERATIONS.org.payroll.runs, orgId).payroll.runs()),
2488
2597
  enabled: client !== null && orgId !== void 0
2489
2598
  });
@@ -2498,29 +2607,34 @@ function usePayrollRuns(orgId) {
2498
2607
  */
2499
2608
  function useOrgTerms(orgId) {
2500
2609
  const client = useCapxulClientOrNull();
2610
+ const identity = useCapxulIdentityOrNull();
2501
2611
  return useQuery({
2502
- queryKey: capxulKeys.payrollTerms(orgId),
2612
+ queryKey: accountKey(capxulKeys.payrollTerms(orgId), identity),
2503
2613
  queryFn: async () => unwrapCapxulResult(await scopedClient(client, CAPXUL_OPERATIONS.org.payroll.terms, orgId).payroll.terms()),
2504
2614
  enabled: client !== null && orgId !== void 0
2505
2615
  });
2506
2616
  }
2507
2617
  function useOrgTreasury(orgId) {
2508
2618
  const client = useCapxulClientOrNull();
2619
+ const identity = useCapxulIdentityOrNull();
2509
2620
  return useQuery({
2510
- queryKey: capxulKeys.orgTreasury(orgId),
2621
+ queryKey: accountKey(capxulKeys.orgTreasury(orgId), identity),
2511
2622
  queryFn: async () => unwrapCapxulResult(await scopedClient(client, CAPXUL_OPERATIONS.org.treasury, orgId).treasury()),
2512
2623
  enabled: client !== null && orgId !== void 0
2513
2624
  });
2514
2625
  }
2515
2626
  /**
2516
2627
  * The caller's own standing, read for its Budgets alone (ruling Q2 grades
2517
- * them). Keyed as `CapxulOrgMember` keys it, so a payroll screen inside a
2518
- * `.Can` wrapper reads `org(id).me()` once.
2628
+ * them). Keyed as `CapxulOrgMember` keys it — Organization AND acting account
2629
+ * (#2007 R19) — so a payroll screen inside a `.Can` wrapper still reads
2630
+ * `org(id).me()` once, and an account replacement is a miss for both of them
2631
+ * rather than the previous person's Budgets for both.
2519
2632
  */
2520
2633
  function useOrgMe(orgId) {
2521
2634
  const client = useCapxulClientOrNull();
2635
+ const identity = useCapxulIdentityOrNull();
2522
2636
  return useQuery({
2523
- queryKey: capxulKeys.orgMe(orgId),
2637
+ queryKey: capxulKeys.orgMe(orgId, actingAccount(identity)),
2524
2638
  queryFn: async () => unwrapCapxulResult(await scopedClient(client, CAPXUL_OPERATIONS.org.me, orgId).me()),
2525
2639
  enabled: client !== null && orgId !== void 0
2526
2640
  });
@@ -2539,6 +2653,14 @@ function scopedClient(client, method, orgId) {
2539
2653
  //#region src/headless/payroll/use-payroll.ts
2540
2654
  /** Money reads the same way everywhere on this screen: amount then code. */
2541
2655
  const GRAMMAR$1 = { grammar: "code" };
2656
+ /**
2657
+ * One captured snapshot, or nothing — never a fabricated empty options object,
2658
+ * and never a DOM event a screen wired straight to `onClick`.
2659
+ */
2660
+ function carry(options) {
2661
+ const attempt = toInvocationOptions(options);
2662
+ return attempt === void 0 ? {} : { options: attempt };
2663
+ }
2542
2664
  function toPayrollGroupRow(group) {
2543
2665
  const currency = new Set(group.members.map((member) => member.currency)).size === 1 ? group.members[0]?.currency : void 0;
2544
2666
  return {
@@ -2578,16 +2700,17 @@ function usePayroll(orgId) {
2578
2700
  const write = useMutation({
2579
2701
  mutationFn: async (command) => {
2580
2702
  const payroll = scopedClient(client, CAPXUL_OPERATIONS.org.payroll.groups[command.kind], orgId).payroll;
2703
+ const controls = toInvocationControls(command.options);
2581
2704
  if (command.kind === "remove") {
2582
- unwrapCapxulResult(await payroll.groups.remove(command.id));
2705
+ unwrapCapxulResult(await payroll.groups.remove(command.id, controls));
2583
2706
  return null;
2584
2707
  }
2585
- return unwrapCapxulResult(await payroll.groups.save(command.input));
2708
+ return unwrapCapxulResult(await payroll.groups.save(command.input, controls));
2586
2709
  },
2587
2710
  onSettled: invalidate
2588
2711
  });
2589
2712
  const mutateAsync = write.mutateAsync;
2590
- const save = useCallback(async (input) => {
2713
+ const save = useCallback(async (input, options) => {
2591
2714
  const refusal = invalidGroup(input);
2592
2715
  if (refusal !== null) return {
2593
2716
  ok: false,
@@ -2596,7 +2719,8 @@ function usePayroll(orgId) {
2596
2719
  try {
2597
2720
  const saved = await mutateAsync({
2598
2721
  kind: "save",
2599
- input
2722
+ input,
2723
+ ...carry(options)
2600
2724
  });
2601
2725
  return saved === null ? {
2602
2726
  ok: false,
@@ -2612,11 +2736,12 @@ function usePayroll(orgId) {
2612
2736
  };
2613
2737
  }
2614
2738
  }, [mutateAsync]);
2615
- const remove = useCallback(async (id) => {
2739
+ const remove = useCallback(async (id, options) => {
2616
2740
  try {
2617
2741
  await mutateAsync({
2618
2742
  kind: "remove",
2619
- id
2743
+ id,
2744
+ ...carry(options)
2620
2745
  });
2621
2746
  return {
2622
2747
  ok: true,
@@ -3134,11 +3259,12 @@ function usePayrollRun(input) {
3134
3259
  amountsParse: total !== null
3135
3260
  });
3136
3261
  const authorize = useMutation({
3137
- mutationFn: async (candidate) => {
3262
+ mutationFn: async ({ candidate, options }) => {
3138
3263
  if (candidate.scope !== currentScope.current) throw Errors.notAuthenticated();
3139
3264
  return executeRunDraft(candidate, async (persisted, onRequestKey, onResolved) => {
3140
3265
  if (candidate.scope === currentScope.current) setDraft(persisted);
3141
3266
  return unwrapCapxulResult(await scopedClient(client, CAPXUL_OPERATIONS.org.payroll.authorizeRun, orgId).payroll.authorizeRun(persisted.input, {
3267
+ ...toInvocationControls(options),
3142
3268
  onResolved,
3143
3269
  onRequestKey: (key) => {
3144
3270
  if (candidate.scope !== currentScope.current) throw Errors.notAuthenticated();
@@ -3147,14 +3273,15 @@ function usePayrollRun(input) {
3147
3273
  }));
3148
3274
  });
3149
3275
  },
3150
- onSettled: (_run, _error, candidate) => {
3276
+ onSettled: (_run, _error, { candidate }) => {
3151
3277
  queryClient.invalidateQueries({ queryKey: capxulKeys.payrollRuns(orgId) });
3152
3278
  if (candidate.scope === currentScope.current) restoreDraft(candidate.scope);
3153
3279
  }
3154
3280
  });
3155
3281
  const totalDisplay = total === null ? null : formatMoney(total, GRAMMAR);
3156
3282
  const authorizeMutate = authorize.mutate;
3157
- const submit = useCallback(() => {
3283
+ const submit = useCallback((options) => {
3284
+ const attempt = toInvocationOptions(options);
3158
3285
  const permissionId = budgets[0]?.id;
3159
3286
  if (blockedReason !== null || submitGate.current || draftBlocked || draftScope === null || assetMoney === null || selectedAsset === null) return;
3160
3287
  if (permissionId === void 0 || totalDisplay === null || parsedAmounts === null) return;
@@ -3196,7 +3323,10 @@ function usePayrollRun(input) {
3196
3323
  }
3197
3324
  };
3198
3325
  submitGate.current = true;
3199
- authorizeMutate(candidate, {
3326
+ authorizeMutate({
3327
+ candidate,
3328
+ ...attempt === void 0 ? {} : { options: attempt }
3329
+ }, {
3200
3330
  onSuccess: (run) => {
3201
3331
  if (candidate.scope === currentScope.current) onRun({
3202
3332
  id: run.id,
@@ -1,4 +1,4 @@
1
- import { n as CapxulOnboardingController, r as CapxulProvider, t as CapxulAuthenticationController } from "../controllers-BPEhjKX9.mjs";
1
+ import { n as CapxulOnboardingController, r as CapxulProvider, t as CapxulAuthenticationController } from "../controllers-DXmEHrnw.mjs";
2
2
  import "react";
3
3
  import { jsx } from "react/jsx-runtime";
4
4
  import { createCapxulTestClient } from "@capxul/sdk/testing";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capxul/sdk-react",
3
- "version": "4.2.0",
3
+ "version": "4.20.0-beta.2",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/Xelmar-tech/infrastructure.git",
@@ -26,7 +26,7 @@
26
26
  "access": "public"
27
27
  },
28
28
  "dependencies": {
29
- "@capxul/sdk": "4.2.0"
29
+ "@capxul/sdk": "4.20.0-beta.2"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@tanstack/react-query": "^5.66.9",
@@ -44,8 +44,8 @@
44
44
  "vite-plus": "0.3.0",
45
45
  "vitest": "4.1.11",
46
46
  "@capxul/errors": "0.3.0",
47
- "@capxul/types": "0.3.0",
48
- "@capxul/typescript-config": "0.0.0"
47
+ "@capxul/typescript-config": "0.0.0",
48
+ "@capxul/types": "0.3.0"
49
49
  },
50
50
  "peerDependencies": {
51
51
  "@tanstack/react-query": "^5.66.9",