@stapel/workspaces-react 0.15.1 → 0.17.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/CHANGELOG.md +54 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/model/mandate.d.ts +40 -0
- package/dist/model/mandate.d.ts.map +1 -0
- package/dist/model/mandate.js +98 -0
- package/dist/model/mandate.js.map +1 -0
- package/llms.txt +1 -1
- package/manifest.json +3 -1
- package/package.json +5 -5
- package/src/analytics/generated/events.json +1 -1
- package/src/index.ts +9 -0
- package/src/model/mandate.ts +108 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,59 @@
|
|
|
1
1
|
# @stapel/workspaces-react
|
|
2
2
|
|
|
3
|
+
## 0.17.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- ace82db: `useMandateSource()` — this module's derivation in the shape core's
|
|
8
|
+
`<MandateProvider>` takes. A screen reads the axis with `useMandate()` from
|
|
9
|
+
`@stapel/core` and no longer imports this package to do it, which is what
|
|
10
|
+
lets the same screen work on a public surface that has no workspace list at
|
|
11
|
+
all. `useMandateState()` and its behaviour are unchanged; the `@stapel/core`
|
|
12
|
+
floor rises to the release that carries the seam.
|
|
13
|
+
|
|
14
|
+
## 0.16.0
|
|
15
|
+
|
|
16
|
+
### Minor Changes
|
|
17
|
+
|
|
18
|
+
- e25e9a6: `useMandateState()`, and a `resolveNav` that consumes the surface axis.
|
|
19
|
+
|
|
20
|
+
`is_guest` has ridden the workspace-list response since stapel-workspaces
|
|
21
|
+
0.19 and had **zero readers**. `useMandateState()` is the first one: the
|
|
22
|
+
single point of truth for "does this person hold a mandate anywhere",
|
|
23
|
+
computed from two answers that already existed — the active session's status
|
|
24
|
+
(which settles anonymous and no-session without asking anyone) and the
|
|
25
|
+
server's own `is_guest` predicate. No new endpoint, and no extra request:
|
|
26
|
+
it reads the same `useWorkspaces()` a screen is already running.
|
|
27
|
+
|
|
28
|
+
The server evaluates the predicate; the hook does not re-derive it. A caller
|
|
29
|
+
can hold membership rows that grant no mandate, so `workspaces.length` is not
|
|
30
|
+
the question — it is consulted only against a backend too old to answer.
|
|
31
|
+
|
|
32
|
+
The unresolved case is the reason the hook is shaped the way it is. A list in
|
|
33
|
+
flight and a list that 502'd both resolve to `unresolved` with a reason, and
|
|
34
|
+
neither ever resolves to `guest`. The one-liner this forecloses —
|
|
35
|
+
`data?.is_guest ?? true` — turns every backend hiccup into "you are a guest",
|
|
36
|
+
locks members out of their own product, and explains nothing; there is no
|
|
37
|
+
expression of that shape available, because the pending and failed states
|
|
38
|
+
carry no principal to read. Render it with `matchMandate`: a wait, or the
|
|
39
|
+
outage stated out loud.
|
|
40
|
+
|
|
41
|
+
`resolveNav` now takes an optional `{ audience }` and every `ResolvedNavEntry`
|
|
42
|
+
carries its resolved `surface`. Omit the audience and nothing changes — the
|
|
43
|
+
scaffold-codegen call site keeps baking every route, and so does every
|
|
44
|
+
existing runtime caller. Pass one and a screen closed to that principal is
|
|
45
|
+
dropped, menu entry and route together, which is the fix: the tree a host
|
|
46
|
+
mounts from is the tree the axis filtered. A project's override file can flip
|
|
47
|
+
`menuVisible` and `order`; it deliberately cannot flip this, because a
|
|
48
|
+
per-project preference must not put a screen that will refuse the caller back
|
|
49
|
+
in front of them.
|
|
50
|
+
|
|
51
|
+
`audience` is a `MandatePrincipal`, so `"unresolved"` cannot be passed. A host
|
|
52
|
+
whose mandate has not settled has to render the wait or the error rather than
|
|
53
|
+
resolve a nav for it — the alternative is a menu that quietly empties itself
|
|
54
|
+
whenever the backend hiccups, which is "we could not ask" rendered as "you
|
|
55
|
+
may not".
|
|
56
|
+
|
|
3
57
|
## 0.15.1
|
|
4
58
|
|
|
5
59
|
### Patch Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -26,6 +26,7 @@ export { WorkspaceSelectionProvider, useWorkspaceSelection, } from "./model/sele
|
|
|
26
26
|
export type { WorkspaceSelection, WorkspaceSelectionProviderProps, WorkspaceSelectionSource, WorkspaceSelectionUrlBinding, } from "./model/selection.js";
|
|
27
27
|
export { useWorkspaces, useWorkspace, useMembers, useAudit, useCanCreateWorkspace, useInvitations, useInfiniteInvitations, useRoles, useInstanceShape, useInvitationPreview, useCapabilities, useCapabilityGate, } from "./model/queries.js";
|
|
28
28
|
export type { CapabilitiesResult, CapabilityGate } from "./model/queries.js";
|
|
29
|
+
export { useMandateState, useMandateSource } from "./model/mandate.js";
|
|
29
30
|
export { capabilityMatches, hasCapability } from "./model/capabilities.js";
|
|
30
31
|
export { BUILTIN_CAPABILITY_LEVELS, SENSITIVE_SCOPE, capabilityLevel, readVerificationEnrollment, } from "./model/stepUp.js";
|
|
31
32
|
export type { CapabilityLevel, VerificationEnrollment, } from "./model/stepUp.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7D,YAAY,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5D,YAAY,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAK9C,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AACvE,YAAY,EACV,WAAW,EACX,kBAAkB,EAClB,aAAa,EACb,SAAS,GACV,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,YAAY,EACV,gBAAgB,IAAI,0BAA0B,EAC9C,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACtE,YAAY,EACV,gBAAgB,EAChB,kBAAkB,EAClB,YAAY,GACb,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,YAAY,EACV,iBAAiB,EACjB,8BAA8B,GAC/B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,wBAAwB,EACxB,oBAAoB,EACpB,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAG3D,OAAO,EACL,0BAA0B,EAC1B,qBAAqB,GACtB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EACV,kBAAkB,EAClB,+BAA+B,EAC/B,wBAAwB,EACxB,4BAA4B,GAC7B,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EACL,aAAa,EACb,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,qBAAqB,EACrB,cAAc,EACd,sBAAsB,EACtB,QAAQ,EACR,gBAAgB,EAChB,oBAAoB,EACpB,eAAe,EACf,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -25,6 +25,14 @@ export { workspacesQueryKeys } from "./model/queryKeys.js";
|
|
|
25
25
|
export { WorkspaceSelectionProvider, useWorkspaceSelection, } from "./model/selection.js";
|
|
26
26
|
// ── model (read hooks) ───────────────────────────────────────────────────────
|
|
27
27
|
export { useWorkspaces, useWorkspace, useMembers, useAudit, useCanCreateWorkspace, useInvitations, useInfiniteInvitations, useRoles, useInstanceShape, useInvitationPreview, useCapabilities, useCapabilityGate, } from "./model/queries.js";
|
|
28
|
+
// ── model (the mandate axis: anonymous / guest / member / unresolved) ────────
|
|
29
|
+
// The one reader of the wire's `is_guest`. `unresolved` is a wait or an
|
|
30
|
+
// explained error, never a hide — render it with core's `matchMandate`.
|
|
31
|
+
// `useMandateSource` is the same derivation in the shape core's
|
|
32
|
+
// `<MandateProvider>` takes: a surface reads the axis through `useMandate()`
|
|
33
|
+
// without importing this package, which is what lets a public storefront
|
|
34
|
+
// have a mandate at all.
|
|
35
|
+
export { useMandateState, useMandateSource } from "./model/mandate.js";
|
|
28
36
|
// ── model (capability matcher + email-mask ports — backend-synced utils) ─────
|
|
29
37
|
export { capabilityMatches, hasCapability } from "./model/capabilities.js";
|
|
30
38
|
export { BUILTIN_CAPABILITY_LEVELS, SENSITIVE_SCOPE, capabilityLevel, readVerificationEnrollment, } from "./model/stepUp.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,gFAAgF;AAChF,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAI7D,gFAAgF;AAChF,4EAA4E;AAC5E,4EAA4E;AAC5E,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAOvE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAMrE,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAOtE,gFAAgF;AAChF,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAK7D,OAAO,EACL,wBAAwB,EACxB,oBAAoB,EACpB,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAE3D,gFAAgF;AAChF,OAAO,EACL,0BAA0B,EAC1B,qBAAqB,GACtB,MAAM,sBAAsB,CAAC;AAQ9B,gFAAgF;AAChF,OAAO,EACL,aAAa,EACb,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,qBAAqB,EACrB,cAAc,EACd,sBAAsB,EACtB,QAAQ,EACR,gBAAgB,EAChB,oBAAoB,EACpB,eAAe,EACf,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAG5B,gFAAgF;AAChF,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC3E,OAAO,EACL,yBAAyB,EACzB,eAAe,EACf,eAAe,EACf,0BAA0B,GAC3B,MAAM,mBAAmB,CAAC;AAK3B,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAEnE,gFAAgF;AAChF,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,EAChB,mBAAmB,EACnB,eAAe,EACf,eAAe,EACf,mBAAmB,EACnB,mBAAmB,EACnB,kBAAkB,EAClB,oBAAoB,EACpB,mBAAmB,EACnB,mBAAmB,EACnB,sBAAsB,EACtB,yBAAyB,EACzB,wBAAwB,EACxB,0BAA0B,GAC3B,MAAM,sBAAsB,CAAC;AA4C9B,gFAAgF;AAChF,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AACtE,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAE5D,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAEhD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAElE,OAAO,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAExC,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAEtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAGlE,gFAAgF;AAChF,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,gBAAgB,CAAC;AAGxB,gFAAgF;AAChF,OAAO,EACL,iBAAiB,EACjB,sBAAsB,EACtB,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,gFAAgF;AAChF,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAI7D,gFAAgF;AAChF,4EAA4E;AAC5E,4EAA4E;AAC5E,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAOvE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAMrE,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAOtE,gFAAgF;AAChF,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAK7D,OAAO,EACL,wBAAwB,EACxB,oBAAoB,EACpB,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAE3D,gFAAgF;AAChF,OAAO,EACL,0BAA0B,EAC1B,qBAAqB,GACtB,MAAM,sBAAsB,CAAC;AAQ9B,gFAAgF;AAChF,OAAO,EACL,aAAa,EACb,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,qBAAqB,EACrB,cAAc,EACd,sBAAsB,EACtB,QAAQ,EACR,gBAAgB,EAChB,oBAAoB,EACpB,eAAe,EACf,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAG5B,gFAAgF;AAChF,wEAAwE;AACxE,wEAAwE;AACxE,gEAAgE;AAChE,6EAA6E;AAC7E,yEAAyE;AACzE,yBAAyB;AACzB,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEvE,gFAAgF;AAChF,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC3E,OAAO,EACL,yBAAyB,EACzB,eAAe,EACf,eAAe,EACf,0BAA0B,GAC3B,MAAM,mBAAmB,CAAC;AAK3B,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAEnE,gFAAgF;AAChF,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,EAChB,mBAAmB,EACnB,eAAe,EACf,eAAe,EACf,mBAAmB,EACnB,mBAAmB,EACnB,kBAAkB,EAClB,oBAAoB,EACpB,mBAAmB,EACnB,mBAAmB,EACnB,sBAAsB,EACtB,yBAAyB,EACzB,wBAAwB,EACxB,0BAA0B,GAC3B,MAAM,sBAAsB,CAAC;AA4C9B,gFAAgF;AAChF,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AACtE,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAE5D,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAEhD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAElE,OAAO,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAExC,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAEtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAGlE,gFAAgF;AAChF,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,gBAAgB,CAAC;AAGxB,gFAAgF;AAChF,OAAO,EACL,iBAAiB,EACjB,sBAAsB,EACtB,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { MandateSource, MandateState } from "@stapel/core";
|
|
2
|
+
/**
|
|
3
|
+
* The caller's mandate: `anonymous` / `guest` / `member`, or `unresolved`
|
|
4
|
+
* with the reason it could not be obtained.
|
|
5
|
+
*
|
|
6
|
+
* **`unresolved` is a wait or an explained error, never a hide and never a
|
|
7
|
+
* refusal.** A host renders it with `matchMandate`, whose five required arms
|
|
8
|
+
* make that hard to get wrong. The tempting one-liner this replaces —
|
|
9
|
+
* `data?.is_guest ?? true` — turns every backend hiccup into "you are a
|
|
10
|
+
* guest", which locks members out of their own product and tells them
|
|
11
|
+
* nothing; there is no expression of that shape available here, because the
|
|
12
|
+
* failed and pending states carry no principal to read.
|
|
13
|
+
*
|
|
14
|
+
* The list read is the same `useWorkspaces()` a screen already runs, so the
|
|
15
|
+
* axis costs no extra request: TanStack serves both from one query.
|
|
16
|
+
*/
|
|
17
|
+
export declare function useMandateState(): MandateState;
|
|
18
|
+
/**
|
|
19
|
+
* This module's derivation, as core's {@link MandateSource} — what a tenant
|
|
20
|
+
* app hands `<MandateProvider>`.
|
|
21
|
+
*
|
|
22
|
+
* The seam exists so that reading the axis and deriving it stop being the
|
|
23
|
+
* same dependency: a public storefront has no workspace list to ask and must
|
|
24
|
+
* not import this package to render a header, while an app that DOES have
|
|
25
|
+
* one wires it here, once, at the root. Nothing about the derivation changes
|
|
26
|
+
* — this is `useMandateState()` in the shape the provider takes.
|
|
27
|
+
*
|
|
28
|
+
* The wrapper is deliberately not memoised: `useMandateState` recomputes from
|
|
29
|
+
* a query result that is a new object on every render, so a memo here would
|
|
30
|
+
* buy nothing. `MandateProvider` compares the answer itself before
|
|
31
|
+
* republishing, which is the guarantee that actually holds.
|
|
32
|
+
*
|
|
33
|
+
* ```tsx
|
|
34
|
+
* function Root({ children }: { children: ReactNode }) {
|
|
35
|
+
* return <MandateProvider source={useMandateSource()}>{children}</MandateProvider>;
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export declare function useMandateSource(): MandateSource;
|
|
40
|
+
//# sourceMappingURL=mandate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mandate.d.ts","sourceRoot":"","sources":["../../src/model/mandate.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAWhE;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,IAAI,YAAY,CA0B9C;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,gBAAgB,IAAI,aAAa,CAEhD"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The mandate axis, wired to this module's wire — the single point of truth
|
|
3
|
+
* for "does this person hold a mandate anywhere".
|
|
4
|
+
*
|
|
5
|
+
* The vocabulary and the "unresolved is not a verdict" discipline live in
|
|
6
|
+
* `@stapel/core` (`mandate.ts`); this file is the only thing that knows
|
|
7
|
+
* WHERE the answer comes from, and it comes from two sources that already
|
|
8
|
+
* exist and needed no new endpoint:
|
|
9
|
+
*
|
|
10
|
+
* - the ACTIVE session's status (`@stapel/core`), which settles anonymous
|
|
11
|
+
* and no-session without asking anyone;
|
|
12
|
+
* - `is_guest` on the workspace list, which stapel-workspaces has sent
|
|
13
|
+
* since 0.19 — the wire form of the backend's own `permissions.is_guest`
|
|
14
|
+
* predicate — and which nothing on the frontend had ever read. That is
|
|
15
|
+
* why a registered, mandate-less person was handed every module's nav
|
|
16
|
+
* entry and mounted every screen, each of which then answered 403.
|
|
17
|
+
*
|
|
18
|
+
* The server evaluates the predicate; this does not re-derive it. A caller
|
|
19
|
+
* can hold membership ROWS that grant no mandate (every one suspended), so
|
|
20
|
+
* `workspaces.length` is not the question and is consulted only when talking
|
|
21
|
+
* to a backend too old to answer it.
|
|
22
|
+
*/
|
|
23
|
+
import { useMemo } from "react";
|
|
24
|
+
import { loadStateFromQuery, mandateAsking, mandateResolved, mandateUnavailable, useActiveSessionStatus, } from "@stapel/core";
|
|
25
|
+
import { useWorkspaces } from "./queries.js";
|
|
26
|
+
/** Guest per the server, falling back to "no memberships" only against a
|
|
27
|
+
* backend that predates `is_guest` (optional on the wire). Reached only with
|
|
28
|
+
* a READY response in hand — an absent list is never an empty one. */
|
|
29
|
+
function listSaysGuest(list) {
|
|
30
|
+
return list.is_guest ?? (list.workspaces ?? []).length === 0;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The caller's mandate: `anonymous` / `guest` / `member`, or `unresolved`
|
|
34
|
+
* with the reason it could not be obtained.
|
|
35
|
+
*
|
|
36
|
+
* **`unresolved` is a wait or an explained error, never a hide and never a
|
|
37
|
+
* refusal.** A host renders it with `matchMandate`, whose five required arms
|
|
38
|
+
* make that hard to get wrong. The tempting one-liner this replaces —
|
|
39
|
+
* `data?.is_guest ?? true` — turns every backend hiccup into "you are a
|
|
40
|
+
* guest", which locks members out of their own product and tells them
|
|
41
|
+
* nothing; there is no expression of that shape available here, because the
|
|
42
|
+
* failed and pending states carry no principal to read.
|
|
43
|
+
*
|
|
44
|
+
* The list read is the same `useWorkspaces()` a screen already runs, so the
|
|
45
|
+
* axis costs no extra request: TanStack serves both from one query.
|
|
46
|
+
*/
|
|
47
|
+
export function useMandateState() {
|
|
48
|
+
const sessionStatus = useActiveSessionStatus();
|
|
49
|
+
const query = useWorkspaces();
|
|
50
|
+
const list = loadStateFromQuery(query);
|
|
51
|
+
return useMemo(() => {
|
|
52
|
+
// "We have not checked yet" — the session's own fourth state, and the
|
|
53
|
+
// same reason `useWorkspaces` is gated: answering here would be a guess.
|
|
54
|
+
if (sessionStatus === "initializing")
|
|
55
|
+
return mandateAsking();
|
|
56
|
+
// Neither carries an identity that could hold a membership, and asking
|
|
57
|
+
// the server would only spend a request to be told so.
|
|
58
|
+
if (sessionStatus === "anonymous" || sessionStatus === "unauthenticated") {
|
|
59
|
+
return mandateResolved("anonymous");
|
|
60
|
+
}
|
|
61
|
+
// Authenticated — or no session-owning module registered at all, in
|
|
62
|
+
// which case the server's answer about its own caller is the only
|
|
63
|
+
// evidence there is, and it is a better one than a local guess.
|
|
64
|
+
switch (list.status) {
|
|
65
|
+
case "loading":
|
|
66
|
+
return mandateAsking();
|
|
67
|
+
case "failed":
|
|
68
|
+
return mandateUnavailable(list.error);
|
|
69
|
+
case "ready":
|
|
70
|
+
return mandateResolved(listSaysGuest(list.data) ? "guest" : "member");
|
|
71
|
+
}
|
|
72
|
+
}, [sessionStatus, list]);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* This module's derivation, as core's {@link MandateSource} — what a tenant
|
|
76
|
+
* app hands `<MandateProvider>`.
|
|
77
|
+
*
|
|
78
|
+
* The seam exists so that reading the axis and deriving it stop being the
|
|
79
|
+
* same dependency: a public storefront has no workspace list to ask and must
|
|
80
|
+
* not import this package to render a header, while an app that DOES have
|
|
81
|
+
* one wires it here, once, at the root. Nothing about the derivation changes
|
|
82
|
+
* — this is `useMandateState()` in the shape the provider takes.
|
|
83
|
+
*
|
|
84
|
+
* The wrapper is deliberately not memoised: `useMandateState` recomputes from
|
|
85
|
+
* a query result that is a new object on every render, so a memo here would
|
|
86
|
+
* buy nothing. `MandateProvider` compares the answer itself before
|
|
87
|
+
* republishing, which is the guarantee that actually holds.
|
|
88
|
+
*
|
|
89
|
+
* ```tsx
|
|
90
|
+
* function Root({ children }: { children: ReactNode }) {
|
|
91
|
+
* return <MandateProvider source={useMandateSource()}>{children}</MandateProvider>;
|
|
92
|
+
* }
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
export function useMandateSource() {
|
|
96
|
+
return { state: useMandateState() };
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=mandate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mandate.js","sourceRoot":"","sources":["../../src/model/mandate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAChC,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,eAAe,EACf,kBAAkB,EAClB,sBAAsB,GACvB,MAAM,cAAc,CAAC;AAGtB,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C;;sEAEsE;AACtE,SAAS,aAAa,CAAC,IAAmB;IACxC,OAAO,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC;AAC/D,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,eAAe;IAC7B,MAAM,aAAa,GAAG,sBAAsB,EAAE,CAAC;IAC/C,MAAM,KAAK,GAAG,aAAa,EAAE,CAAC;IAC9B,MAAM,IAAI,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;IAEvC,OAAO,OAAO,CAAC,GAAiB,EAAE;QAChC,sEAAsE;QACtE,yEAAyE;QACzE,IAAI,aAAa,KAAK,cAAc;YAAE,OAAO,aAAa,EAAE,CAAC;QAC7D,uEAAuE;QACvE,uDAAuD;QACvD,IAAI,aAAa,KAAK,WAAW,IAAI,aAAa,KAAK,iBAAiB,EAAE,CAAC;YACzE,OAAO,eAAe,CAAC,WAAW,CAAC,CAAC;QACtC,CAAC;QACD,oEAAoE;QACpE,kEAAkE;QAClE,gEAAgE;QAChE,QAAQ,IAAI,CAAC,MAAM,EAAE,CAAC;YACpB,KAAK,SAAS;gBACZ,OAAO,aAAa,EAAE,CAAC;YACzB,KAAK,QAAQ;gBACX,OAAO,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACxC,KAAK,OAAO;gBACV,OAAO,eAAe,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QAC1E,CAAC;IACH,CAAC,EAAE,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC,CAAC;AAC5B,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,gBAAgB;IAC9B,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,EAAE,CAAC;AACtC,CAAC"}
|
package/llms.txt
CHANGED
package/manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$generated": "by scripts/gen-manifest.mjs — do not edit; drift-gated (pnpm gen:manifest:check)",
|
|
3
3
|
"package": "@stapel/workspaces-react",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.17.0",
|
|
5
5
|
"backend": {
|
|
6
6
|
"module": "stapel-workspaces",
|
|
7
7
|
"contract": ">=0.24 <0.25"
|
|
@@ -1178,6 +1178,8 @@
|
|
|
1178
1178
|
"useInvitationPreview",
|
|
1179
1179
|
"useInvitations",
|
|
1180
1180
|
"useInviteMembers",
|
|
1181
|
+
"useMandateSource",
|
|
1182
|
+
"useMandateState",
|
|
1181
1183
|
"useMembers",
|
|
1182
1184
|
"useRemoveMember",
|
|
1183
1185
|
"useRenameInvitation",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stapel/workspaces-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "Headless React flow pair for stapel-workspaces: typed API client, TanStack Query hooks, flow machines, headless components, and i18n keys. Zero visual opinion.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
}
|
|
68
68
|
],
|
|
69
69
|
"peerDependencies": {
|
|
70
|
-
"@stapel/core": ">=0.
|
|
70
|
+
"@stapel/core": ">=0.15.0 <1.0.0",
|
|
71
71
|
"@tanstack/react-query": "^5.0.0",
|
|
72
72
|
"antd": ">=5.20.0 <7",
|
|
73
73
|
"react": ">=19"
|
|
@@ -91,9 +91,9 @@
|
|
|
91
91
|
"size-limit": "^11.2.0",
|
|
92
92
|
"typescript": "^5.8.3",
|
|
93
93
|
"vitest": "^3.2.4",
|
|
94
|
-
"@stapel/core": "^0.
|
|
95
|
-
"@stapel/
|
|
96
|
-
"@stapel/
|
|
94
|
+
"@stapel/core": "^0.15.0",
|
|
95
|
+
"@stapel/tokens": "^0.5.0",
|
|
96
|
+
"@stapel/showcase": "^0.2.0"
|
|
97
97
|
},
|
|
98
98
|
"engines": {
|
|
99
99
|
"node": ">=22"
|
package/src/index.ts
CHANGED
|
@@ -81,6 +81,15 @@ export {
|
|
|
81
81
|
} from "./model/queries.js";
|
|
82
82
|
export type { CapabilitiesResult, CapabilityGate } from "./model/queries.js";
|
|
83
83
|
|
|
84
|
+
// ── model (the mandate axis: anonymous / guest / member / unresolved) ────────
|
|
85
|
+
// The one reader of the wire's `is_guest`. `unresolved` is a wait or an
|
|
86
|
+
// explained error, never a hide — render it with core's `matchMandate`.
|
|
87
|
+
// `useMandateSource` is the same derivation in the shape core's
|
|
88
|
+
// `<MandateProvider>` takes: a surface reads the axis through `useMandate()`
|
|
89
|
+
// without importing this package, which is what lets a public storefront
|
|
90
|
+
// have a mandate at all.
|
|
91
|
+
export { useMandateState, useMandateSource } from "./model/mandate.js";
|
|
92
|
+
|
|
84
93
|
// ── model (capability matcher + email-mask ports — backend-synced utils) ─────
|
|
85
94
|
export { capabilityMatches, hasCapability } from "./model/capabilities.js";
|
|
86
95
|
export {
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The mandate axis, wired to this module's wire — the single point of truth
|
|
3
|
+
* for "does this person hold a mandate anywhere".
|
|
4
|
+
*
|
|
5
|
+
* The vocabulary and the "unresolved is not a verdict" discipline live in
|
|
6
|
+
* `@stapel/core` (`mandate.ts`); this file is the only thing that knows
|
|
7
|
+
* WHERE the answer comes from, and it comes from two sources that already
|
|
8
|
+
* exist and needed no new endpoint:
|
|
9
|
+
*
|
|
10
|
+
* - the ACTIVE session's status (`@stapel/core`), which settles anonymous
|
|
11
|
+
* and no-session without asking anyone;
|
|
12
|
+
* - `is_guest` on the workspace list, which stapel-workspaces has sent
|
|
13
|
+
* since 0.19 — the wire form of the backend's own `permissions.is_guest`
|
|
14
|
+
* predicate — and which nothing on the frontend had ever read. That is
|
|
15
|
+
* why a registered, mandate-less person was handed every module's nav
|
|
16
|
+
* entry and mounted every screen, each of which then answered 403.
|
|
17
|
+
*
|
|
18
|
+
* The server evaluates the predicate; this does not re-derive it. A caller
|
|
19
|
+
* can hold membership ROWS that grant no mandate (every one suspended), so
|
|
20
|
+
* `workspaces.length` is not the question and is consulted only when talking
|
|
21
|
+
* to a backend too old to answer it.
|
|
22
|
+
*/
|
|
23
|
+
import { useMemo } from "react";
|
|
24
|
+
import {
|
|
25
|
+
loadStateFromQuery,
|
|
26
|
+
mandateAsking,
|
|
27
|
+
mandateResolved,
|
|
28
|
+
mandateUnavailable,
|
|
29
|
+
useActiveSessionStatus,
|
|
30
|
+
} from "@stapel/core";
|
|
31
|
+
import type { MandateSource, MandateState } from "@stapel/core";
|
|
32
|
+
import type { WorkspaceList } from "../api/types.js";
|
|
33
|
+
import { useWorkspaces } from "./queries.js";
|
|
34
|
+
|
|
35
|
+
/** Guest per the server, falling back to "no memberships" only against a
|
|
36
|
+
* backend that predates `is_guest` (optional on the wire). Reached only with
|
|
37
|
+
* a READY response in hand — an absent list is never an empty one. */
|
|
38
|
+
function listSaysGuest(list: WorkspaceList): boolean {
|
|
39
|
+
return list.is_guest ?? (list.workspaces ?? []).length === 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The caller's mandate: `anonymous` / `guest` / `member`, or `unresolved`
|
|
44
|
+
* with the reason it could not be obtained.
|
|
45
|
+
*
|
|
46
|
+
* **`unresolved` is a wait or an explained error, never a hide and never a
|
|
47
|
+
* refusal.** A host renders it with `matchMandate`, whose five required arms
|
|
48
|
+
* make that hard to get wrong. The tempting one-liner this replaces —
|
|
49
|
+
* `data?.is_guest ?? true` — turns every backend hiccup into "you are a
|
|
50
|
+
* guest", which locks members out of their own product and tells them
|
|
51
|
+
* nothing; there is no expression of that shape available here, because the
|
|
52
|
+
* failed and pending states carry no principal to read.
|
|
53
|
+
*
|
|
54
|
+
* The list read is the same `useWorkspaces()` a screen already runs, so the
|
|
55
|
+
* axis costs no extra request: TanStack serves both from one query.
|
|
56
|
+
*/
|
|
57
|
+
export function useMandateState(): MandateState {
|
|
58
|
+
const sessionStatus = useActiveSessionStatus();
|
|
59
|
+
const query = useWorkspaces();
|
|
60
|
+
const list = loadStateFromQuery(query);
|
|
61
|
+
|
|
62
|
+
return useMemo((): MandateState => {
|
|
63
|
+
// "We have not checked yet" — the session's own fourth state, and the
|
|
64
|
+
// same reason `useWorkspaces` is gated: answering here would be a guess.
|
|
65
|
+
if (sessionStatus === "initializing") return mandateAsking();
|
|
66
|
+
// Neither carries an identity that could hold a membership, and asking
|
|
67
|
+
// the server would only spend a request to be told so.
|
|
68
|
+
if (sessionStatus === "anonymous" || sessionStatus === "unauthenticated") {
|
|
69
|
+
return mandateResolved("anonymous");
|
|
70
|
+
}
|
|
71
|
+
// Authenticated — or no session-owning module registered at all, in
|
|
72
|
+
// which case the server's answer about its own caller is the only
|
|
73
|
+
// evidence there is, and it is a better one than a local guess.
|
|
74
|
+
switch (list.status) {
|
|
75
|
+
case "loading":
|
|
76
|
+
return mandateAsking();
|
|
77
|
+
case "failed":
|
|
78
|
+
return mandateUnavailable(list.error);
|
|
79
|
+
case "ready":
|
|
80
|
+
return mandateResolved(listSaysGuest(list.data) ? "guest" : "member");
|
|
81
|
+
}
|
|
82
|
+
}, [sessionStatus, list]);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* This module's derivation, as core's {@link MandateSource} — what a tenant
|
|
87
|
+
* app hands `<MandateProvider>`.
|
|
88
|
+
*
|
|
89
|
+
* The seam exists so that reading the axis and deriving it stop being the
|
|
90
|
+
* same dependency: a public storefront has no workspace list to ask and must
|
|
91
|
+
* not import this package to render a header, while an app that DOES have
|
|
92
|
+
* one wires it here, once, at the root. Nothing about the derivation changes
|
|
93
|
+
* — this is `useMandateState()` in the shape the provider takes.
|
|
94
|
+
*
|
|
95
|
+
* The wrapper is deliberately not memoised: `useMandateState` recomputes from
|
|
96
|
+
* a query result that is a new object on every render, so a memo here would
|
|
97
|
+
* buy nothing. `MandateProvider` compares the answer itself before
|
|
98
|
+
* republishing, which is the guarantee that actually holds.
|
|
99
|
+
*
|
|
100
|
+
* ```tsx
|
|
101
|
+
* function Root({ children }: { children: ReactNode }) {
|
|
102
|
+
* return <MandateProvider source={useMandateSource()}>{children}</MandateProvider>;
|
|
103
|
+
* }
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
export function useMandateSource(): MandateSource {
|
|
107
|
+
return { state: useMandateState() };
|
|
108
|
+
}
|