@farthershore/farthershore-js 0.26.0 → 0.28.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.
Files changed (108) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/README.md +1 -1
  3. package/dist/cache.d.ts +1 -1
  4. package/dist/cache.js +1 -1
  5. package/dist/component-policy.d.ts +25 -1
  6. package/dist/component-policy.d.ts.map +1 -1
  7. package/dist/component-policy.js +59 -4
  8. package/dist/component-policy.js.map +1 -1
  9. package/dist/components/access-control.d.ts.map +1 -1
  10. package/dist/components/access-control.js +73 -7
  11. package/dist/components/access-control.js.map +1 -1
  12. package/dist/components/api-keys-manager.d.ts +7 -0
  13. package/dist/components/api-keys-manager.d.ts.map +1 -0
  14. package/dist/components/api-keys-manager.js +238 -0
  15. package/dist/components/api-keys-manager.js.map +1 -0
  16. package/dist/components/audit-log-table.d.ts.map +1 -1
  17. package/dist/components/audit-log-table.js +24 -3
  18. package/dist/components/audit-log-table.js.map +1 -1
  19. package/dist/components/bill-preview-card.d.ts +6 -7
  20. package/dist/components/bill-preview-card.d.ts.map +1 -1
  21. package/dist/components/bill-preview-card.js +93 -24
  22. package/dist/components/bill-preview-card.js.map +1 -1
  23. package/dist/components/billing.d.ts.map +1 -1
  24. package/dist/components/billing.js +9 -2
  25. package/dist/components/billing.js.map +1 -1
  26. package/dist/components/cancel-subscription.d.ts +4 -6
  27. package/dist/components/cancel-subscription.d.ts.map +1 -1
  28. package/dist/components/cancel-subscription.js +113 -40
  29. package/dist/components/cancel-subscription.js.map +1 -1
  30. package/dist/components/component-access-panel.d.ts.map +1 -1
  31. package/dist/components/component-access-panel.js +10 -3
  32. package/dist/components/component-access-panel.js.map +1 -1
  33. package/dist/components/index.d.ts +12 -4
  34. package/dist/components/index.d.ts.map +1 -1
  35. package/dist/components/index.js +13 -2
  36. package/dist/components/index.js.map +1 -1
  37. package/dist/components/keys.d.ts.map +1 -1
  38. package/dist/components/keys.js +7 -1
  39. package/dist/components/keys.js.map +1 -1
  40. package/dist/components/notification-preferences.d.ts +7 -0
  41. package/dist/components/notification-preferences.d.ts.map +1 -0
  42. package/dist/components/notification-preferences.js +119 -0
  43. package/dist/components/notification-preferences.js.map +1 -0
  44. package/dist/components/onboarding-plan-rail.d.ts +22 -0
  45. package/dist/components/onboarding-plan-rail.d.ts.map +1 -0
  46. package/dist/components/onboarding-plan-rail.js +103 -0
  47. package/dist/components/onboarding-plan-rail.js.map +1 -0
  48. package/dist/components/org-switcher.d.ts +11 -6
  49. package/dist/components/org-switcher.d.ts.map +1 -1
  50. package/dist/components/org-switcher.js +88 -22
  51. package/dist/components/org-switcher.js.map +1 -1
  52. package/dist/components/payment-required-gate.d.ts.map +1 -1
  53. package/dist/components/payment-required-gate.js +13 -5
  54. package/dist/components/payment-required-gate.js.map +1 -1
  55. package/dist/components/permission-gate.d.ts.map +1 -1
  56. package/dist/components/permission-gate.js +26 -10
  57. package/dist/components/permission-gate.js.map +1 -1
  58. package/dist/components/pricing-plan-card.d.ts +11 -0
  59. package/dist/components/pricing-plan-card.d.ts.map +1 -0
  60. package/dist/components/pricing-plan-card.js +15 -0
  61. package/dist/components/pricing-plan-card.js.map +1 -0
  62. package/dist/components/team.d.ts +92 -0
  63. package/dist/components/team.d.ts.map +1 -0
  64. package/dist/components/team.js +104 -0
  65. package/dist/components/team.js.map +1 -0
  66. package/dist/components/usage-limits.d.ts +1 -1
  67. package/dist/components/usage-limits.d.ts.map +1 -1
  68. package/dist/components/usage-limits.js +26 -3
  69. package/dist/components/usage-limits.js.map +1 -1
  70. package/dist/components/usage-surfaces.d.ts +43 -0
  71. package/dist/components/usage-surfaces.d.ts.map +1 -0
  72. package/dist/components/usage-surfaces.js +70 -0
  73. package/dist/components/usage-surfaces.js.map +1 -0
  74. package/dist/format/catalog-display.d.ts +8 -1
  75. package/dist/format/catalog-display.d.ts.map +1 -1
  76. package/dist/format/catalog-display.js +28 -8
  77. package/dist/format/catalog-display.js.map +1 -1
  78. package/dist/format/index.d.ts +1 -1
  79. package/dist/format/index.d.ts.map +1 -1
  80. package/dist/format/index.js +4 -1
  81. package/dist/format/index.js.map +1 -1
  82. package/dist/generated/version.d.ts +2 -2
  83. package/dist/generated/version.js +2 -2
  84. package/dist/index.d.ts +1 -1
  85. package/dist/index.d.ts.map +1 -1
  86. package/dist/index.js +1 -1
  87. package/dist/index.js.map +1 -1
  88. package/dist/mock/transport.d.ts.map +1 -1
  89. package/dist/mock/transport.js +0 -4
  90. package/dist/mock/transport.js.map +1 -1
  91. package/dist/react/hooks.d.ts +0 -9
  92. package/dist/react/hooks.d.ts.map +1 -1
  93. package/dist/react/hooks.js +0 -6
  94. package/dist/react/hooks.js.map +1 -1
  95. package/dist/resources/billing.d.ts +0 -12
  96. package/dist/resources/billing.d.ts.map +1 -1
  97. package/dist/resources/billing.js +0 -14
  98. package/dist/resources/billing.js.map +1 -1
  99. package/dist/test-utils/mock-client.d.ts.map +1 -1
  100. package/dist/test-utils/mock-client.js +0 -6
  101. package/dist/test-utils/mock-client.js.map +1 -1
  102. package/dist/types.d.ts +8 -0
  103. package/dist/types.d.ts.map +1 -1
  104. package/package.json +1 -1
  105. package/dist/components/move-to-latest.d.ts +0 -16
  106. package/dist/components/move-to-latest.d.ts.map +0 -1
  107. package/dist/components/move-to-latest.js +0 -68
  108. package/dist/components/move-to-latest.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -3,6 +3,77 @@
3
3
  All notable changes to `@farthershore/farthershore-js` are documented here.
4
4
  This project adheres to Semantic Versioning (pre-1.0: minors may break).
5
5
 
6
+ ## [Unreleased]
7
+
8
+ ## [0.28.0] - 2026-09-03
9
+
10
+ ### Changed — `FsUsageLimits` is now render-gated on `usage_limit:read` (BREAKING)
11
+
12
+ `FsUsageLimits` joined the managed component map as `usage_limits` and is
13
+ wrapped in `withGate`: a viewer WITHOUT `usage_limit:read` no longer mounts
14
+ the component (previously it mounted anywhere and attempted the load, then
15
+ surfaced the API error). A consumer mounting it outside an already-gated
16
+ route now sees the resolved deny rendering (`AccessDenied` by default, or
17
+ nothing/disabled per the org's configured mode) instead of the old
18
+ error-loading card. The three mutating controls keep their own
19
+ per-control `usePermissionAction` gates. Additionally, a viewer whose
20
+ policy resolves a MOUNTING deny mode (`readOnly` / `disable`) but who
21
+ cannot read the backing data renders `AccessDenied` — the panel's list
22
+ fetch requires `usage_limit:read` server-side, so a mounted-but-unfetchable
23
+ view would be an error card, not a read-only view.
24
+
25
+ ### Changed — unconfigured component denies render an AccessDenied panel (BREAKING, visual)
26
+
27
+ The default gate mode for a component with NO configured mode (no builder
28
+ `registerComponent` gateMode, no subscriber-org override) is now **per-key**
29
+ via the new `defaultGateModeFor(componentKey)`: `"denied"` for most
30
+ components, `"hide"` only for the security-sensitive set (`audit_log`,
31
+ `api_keys_panel`, `team_panel`), whose existence is the confidential fact.
32
+ Previously every unconfigured deny was `"hide"`, which made a permission miss
33
+ look like a rendering bug.
34
+
35
+ What changes visually: an embedded data component (`UsageCard`,
36
+ `BillingSummary`, a `withGate`-wrapped custom component, …) shown to a member
37
+ without its permission now renders the explicit "you don't have access" panel
38
+ instead of vanishing. The dev-portal template's component-backed route gates
39
+ stop forcing `mode="denied"` and follow the resolver instead: most denied
40
+ route visits still render the AccessDenied panel (now via the per-key
41
+ default), but the SENSITIVE routes (`/dashboard/keys`, `/audit-log`) render
42
+ NOTHING for a denied member unless the org configured a mode — their
43
+ existence is the confidential fact, and the old forced mode leaked it. A
44
+ bare `<PermissionGate permission="…">` (no `component`) still defaults to
45
+ `hide` — hiding an affordance a member cannot use stays correct.
46
+
47
+ Also: `SENSITIVE_COMPONENT_KEYS`, `isSensitiveComponentKey`, and
48
+ `defaultGateModeFor` are exported publicly, and `registerComponent` /
49
+ `unregisterComponent` / `ComponentRegistration` are re-exported from
50
+ `@farthershore/farthershore-js/components`.
51
+
52
+ ## [0.27.0] - 2026-08-31
53
+
54
+ ### Removed — subscriber-facing "move to latest plan" (BREAKING)
55
+
56
+ The billing redesign decoupled meters from plans, which removed the reason this
57
+ control existed. A release's `measurement_schema` is built from the
58
+ business-wide commerce manifest (every measure, meter, and route binding), so
59
+ every plan in the release cohort sees a newly declared meter — and new measures
60
+ adopt the current published rate. A subscriber whose plan spec is unchanged
61
+ stays in the new release's cohort and picks the new meter up automatically. The
62
+ old failure mode this control worked around — "the feature I want reports a
63
+ meter my pinned plan's dimension map doesn't have, so I must move plans" — no
64
+ longer exists.
65
+
66
+ - **REMOVED `MoveToLatestPlan`** and its `MoveToLatestPlanProps`.
67
+ - **REMOVED `fs.billing.migrateToLatest()`** and **`useBilling().migrateToLatest`**.
68
+ - The backing `POST /portal/businesses/:businessId/me/migrate-latest` route is
69
+ removed from core in the same change, along with the unused
70
+ `POST /consumer/subscriptions/:subscriptionId/migrate-latest`.
71
+
72
+ Converging a subscriber whose plan's own economics changed (price, included
73
+ allowance, funding buckets, limits) is unchanged and remains an OPERATOR action:
74
+ the builder's Users tab, or `farthershore consumer migrate-latest`. Subscribers
75
+ still change plans with `changePlan()`.
76
+
6
77
  ## [0.26.0] - 2026-08-30
7
78
 
8
79
  Three efforts ship together: the billing redesign (already on `main` but never
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  The **Farther Shore Frontend SDK** — the browser + React integration layer for
4
4
  your product's subscriber portal.
5
5
 
6
- > **Status: `0.26.0` (pre-1.0).** A MINOR bump may include breaking changes;
6
+ > **Status: `0.27.0` (pre-1.0).** A MINOR bump may include breaking changes;
7
7
  > patches are additive. Pin the version you build against and read
8
8
  > [`CHANGELOG.md`](./CHANGELOG.md) before upgrading.
9
9
 
package/dist/cache.d.ts CHANGED
@@ -25,7 +25,7 @@ export declare function bustReadCache(ctx: ClientContext): void;
25
25
  * `setSessionToken()` landing after a 401 left the payment gate open (its `/me`
26
26
  * read had FAILED, and failures evict rather than cache — so there was no entry
27
27
  * to drop, only a live reader to poke), and sibling billing surfaces kept the
28
- * old plan after `migrateToLatest()`.
28
+ * old plan after `changePlan()`.
29
29
  *
30
30
  * Listeners are woken unconditionally, never gated on the cache being
31
31
  * non-empty, precisely because the failed-read case leaves nothing cached.
package/dist/cache.js CHANGED
@@ -50,7 +50,7 @@ export function bustReadCache(ctx) {
50
50
  * `setSessionToken()` landing after a 401 left the payment gate open (its `/me`
51
51
  * read had FAILED, and failures evict rather than cache — so there was no entry
52
52
  * to drop, only a live reader to poke), and sibling billing surfaces kept the
53
- * old plan after `migrateToLatest()`.
53
+ * old plan after `changePlan()`.
54
54
  *
55
55
  * Listeners are woken unconditionally, never gated on the cache being
56
56
  * non-empty, precisely because the failed-read case leaves nothing cached.
@@ -16,12 +16,31 @@ export interface ComponentPermissionDefault {
16
16
  * (parity-tested in test/component-policy-parity.test.ts — the shipped SDK
17
17
  * bundle must stay contracts-free). */
18
18
  export declare const MANAGED_COMPONENT_DEFAULTS: Readonly<Record<string, ComponentPermissionDefault>>;
19
+ /** FAITHFUL COPY of contracts `DATA_FLOOR_COMPONENT_KEYS` (parity-tested):
20
+ * the managed default permission is a DATA floor — the panel's fetch is
21
+ * server-gated on it regardless of a subscriber-org re-gate, so it stays an
22
+ * AND requirement alongside any override (Codex #473). */
23
+ export declare const DATA_FLOOR_COMPONENT_KEYS: readonly string[];
24
+ /** The managed data-floor permission for `componentId`, or null when the id
25
+ * carries none — the AND requirement its server-gated fetch imposes
26
+ * regardless of overrides. */
27
+ export declare function dataFloorPermissionFor(componentId: string): string | null;
19
28
  /** FAITHFUL COPY of contracts/evolution `SENSITIVE_COMPONENT_KEYS`.
20
29
  * The SDK cannot import contracts at runtime, so parity is pinned in test/. */
21
30
  export declare const SENSITIVE_COMPONENT_KEYS: readonly ["audit_log", "api_keys_panel", "team_panel"];
31
+ /**
32
+ * FAITHFUL COPY of contracts `defaultGateModeFor` (Subscriber-RBAC R5) — the
33
+ * gate mode used when NOTHING configured one. Non-sensitive components render
34
+ * an explicit AccessDenied (`"denied"`) instead of silently vanishing; the
35
+ * sensitive set keeps `"hide"` (their existence is the confidential fact).
36
+ * Parity is pinned in test/component-policy-parity.test.ts.
37
+ */
38
+ export declare function defaultGateModeFor(componentId: string): "hide" | "denied";
22
39
  /** True iff `id` names a confidentiality-sensitive managed component. */
23
40
  export declare function isSensitiveComponentKey(id: string): boolean;
24
- /** Faithful copy of the contracts `CUSTOM_COMPONENT_KEY_PATTERN`. */
41
+ /** Faithful copy of the contracts `CUSTOM_COMPONENT_KEY_PATTERN` — the slug
42
+ * budget matches the custom-subject grammar (≤32) so the derived permission
43
+ * subject is always declarable. */
25
44
  export declare const CUSTOM_COMPONENT_KEY_PATTERN: RegExp;
26
45
  /** True iff `id` is a well-formed builder-custom component key. */
27
46
  export declare function isCustomComponentKey(id: string): boolean;
@@ -62,6 +81,11 @@ export interface ResolvedComponentPolicy {
62
81
  writePermission: string;
63
82
  /** How the component renders when the viewer lacks `permission`. */
64
83
  gateMode: ComponentGateMode;
84
+ /** Where `gateMode` came from: an explicit host registration, a
85
+ * subscriber-org overlay, or the platform default. Hosts that wrap a
86
+ * component-gated surface (e.g. a route) must not force their own mode
87
+ * when the source is not `"default"` — the configured choice wins. */
88
+ gateModeSource: "registration" | "overlay" | "default";
65
89
  /** Renders ungated (explicit flag only). */
66
90
  presentational: boolean;
67
91
  /** Whether the id is one of the contracts-owned sensitive components. */
@@ -1 +1 @@
1
- {"version":3,"file":"component-policy.d.ts","sourceRoot":"","sources":["../src/component-policy.ts"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAE3D;oEACoE;AACpE,eAAO,MAAM,oBAAoB,oDAKvB,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEtE,mEAAmE;AACnE,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,IAAI,iBAAiB,CAE3E;AAED;0CAC0C;AAC1C,MAAM,WAAW,0BAA0B;IACzC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,IAAI,CAAC;CACvB;AAED;;wCAEwC;AACxC,eAAO,MAAM,0BAA0B,EAAE,QAAQ,CAC/C,MAAM,CAAC,MAAM,EAAE,0BAA0B,CAAC,CAsB3C,CAAC;AAEF;gFACgF;AAChF,eAAO,MAAM,wBAAwB,wDAI3B,CAAC;AAEX,yEAAyE;AACzE,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAE3D;AAaD,qEAAqE;AACrE,eAAO,MAAM,4BAA4B,QAAmC,CAAC;AAE7E,mEAAmE;AACnE,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAExD;AAMD,MAAM,WAAW,qBAAqB;IACpC;wCACoC;IACpC,EAAE,EAAE,MAAM,CAAC;IACX;sBACkB;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2EAA2E;IAC3E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,iEAAiE;IACjE,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAID;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,qBAAqB,GAAG,IAAI,CAgB3E;AAED,qDAAqD;AACrD,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAEpD;AAED,4CAA4C;AAC5C,wBAAgB,wBAAwB,IAAI,IAAI,CAE/C;AAMD,qDAAqD;AACrD,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,+DAA+D;IAC/D,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;4EACwE;IACxE,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,2DAA2D;IAC3D,eAAe,EAAE,MAAM,CAAC;IACxB,oEAAoE;IACpE,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,4CAA4C;IAC5C,cAAc,EAAE,OAAO,CAAC;IACxB,yEAAyE;IACzE,SAAS,EAAE,OAAO,CAAC;IACnB;yDACqD;IACrD,OAAO,EAAE,OAAO,CAAC;CAClB;AAiBD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,MAAM,EACnB,QAAQ,CAAC,EAAE,SAAS,wBAAwB,EAAE,GAAG,IAAI,GACpD,uBAAuB,CAqFzB"}
1
+ {"version":3,"file":"component-policy.d.ts","sourceRoot":"","sources":["../src/component-policy.ts"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAE3D;oEACoE;AACpE,eAAO,MAAM,oBAAoB,oDAKvB,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEtE,mEAAmE;AACnE,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,IAAI,iBAAiB,CAE3E;AAED;0CAC0C;AAC1C,MAAM,WAAW,0BAA0B;IACzC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,IAAI,CAAC;CACvB;AAED;;wCAEwC;AACxC,eAAO,MAAM,0BAA0B,EAAE,QAAQ,CAC/C,MAAM,CAAC,MAAM,EAAE,0BAA0B,CAAC,CAuC3C,CAAC;AAEF;;;2DAG2D;AAC3D,eAAO,MAAM,yBAAyB,EAAE,SAAS,MAAM,EAAqB,CAAC;AAE7E;;+BAE+B;AAC/B,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGzE;AAED;gFACgF;AAChF,eAAO,MAAM,wBAAwB,wDAI3B,CAAC;AAEX;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,CAEzE;AAED,yEAAyE;AACzE,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAE3D;AAaD;;oCAEoC;AACpC,eAAO,MAAM,4BAA4B,QAAmC,CAAC;AAE7E,mEAAmE;AACnE,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAExD;AAMD,MAAM,WAAW,qBAAqB;IACpC;wCACoC;IACpC,EAAE,EAAE,MAAM,CAAC;IACX;sBACkB;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2EAA2E;IAC3E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,iEAAiE;IACjE,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAID;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,qBAAqB,GAAG,IAAI,CAgB3E;AAED,qDAAqD;AACrD,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAEpD;AAED,4CAA4C;AAC5C,wBAAgB,wBAAwB,IAAI,IAAI,CAE/C;AAMD,qDAAqD;AACrD,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,+DAA+D;IAC/D,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;4EACwE;IACxE,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,2DAA2D;IAC3D,eAAe,EAAE,MAAM,CAAC;IACxB,oEAAoE;IACpE,QAAQ,EAAE,iBAAiB,CAAC;IAC5B;;;2EAGuE;IACvE,cAAc,EAAE,cAAc,GAAG,SAAS,GAAG,SAAS,CAAC;IACvD,4CAA4C;IAC5C,cAAc,EAAE,OAAO,CAAC;IACxB,yEAAyE;IACzE,SAAS,EAAE,OAAO,CAAC;IACnB;yDACqD;IACrD,OAAO,EAAE,OAAO,CAAC;CAClB;AAiBD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,MAAM,EACnB,QAAQ,CAAC,EAAE,SAAS,wBAAwB,EAAE,GAAG,IAAI,GACpD,uBAAuB,CAoGzB"}
@@ -62,7 +62,37 @@ export const MANAGED_COMPONENT_DEFAULTS = {
62
62
  audit_log: { permission: "audit_log:read" },
63
63
  team_panel: { permission: "team:read", writePermission: "team:manage_rbac" },
64
64
  markdown: { permission: null, presentational: true },
65
+ usage_limits: {
66
+ permission: "usage_limit:read",
67
+ // Primary mutating verb (create); update/delete controls gate on their
68
+ // own concrete verbs at the call site (per-control > panel default —
69
+ // the api_keys_panel pattern). NOT sensitive: a member reading the
70
+ // limits set on them is a product feature, and the destructive verb
71
+ // (usage_limit:delete) is already escalatory / grant-by-exact-name.
72
+ writePermission: "usage_limit:create",
73
+ },
74
+ notification_preferences: {
75
+ // notification:read is the concrete self-scoped inbox verb, and it
76
+ // includes updating the current member's OWN channel opt-outs — reading
77
+ // and muting your own notifications are one capability, so read and
78
+ // write share the verb.
79
+ permission: "notification:read",
80
+ writePermission: "notification:read",
81
+ },
65
82
  };
83
+ /** FAITHFUL COPY of contracts `DATA_FLOOR_COMPONENT_KEYS` (parity-tested):
84
+ * the managed default permission is a DATA floor — the panel's fetch is
85
+ * server-gated on it regardless of a subscriber-org re-gate, so it stays an
86
+ * AND requirement alongside any override (Codex #473). */
87
+ export const DATA_FLOOR_COMPONENT_KEYS = ["usage_limits"];
88
+ /** The managed data-floor permission for `componentId`, or null when the id
89
+ * carries none — the AND requirement its server-gated fetch imposes
90
+ * regardless of overrides. */
91
+ export function dataFloorPermissionFor(componentId) {
92
+ if (!DATA_FLOOR_COMPONENT_KEYS.includes(componentId))
93
+ return null;
94
+ return MANAGED_COMPONENT_DEFAULTS[componentId]?.permission ?? null;
95
+ }
66
96
  /** FAITHFUL COPY of contracts/evolution `SENSITIVE_COMPONENT_KEYS`.
67
97
  * The SDK cannot import contracts at runtime, so parity is pinned in test/. */
68
98
  export const SENSITIVE_COMPONENT_KEYS = [
@@ -70,6 +100,16 @@ export const SENSITIVE_COMPONENT_KEYS = [
70
100
  "api_keys_panel",
71
101
  "team_panel",
72
102
  ];
103
+ /**
104
+ * FAITHFUL COPY of contracts `defaultGateModeFor` (Subscriber-RBAC R5) — the
105
+ * gate mode used when NOTHING configured one. Non-sensitive components render
106
+ * an explicit AccessDenied (`"denied"`) instead of silently vanishing; the
107
+ * sensitive set keeps `"hide"` (their existence is the confidential fact).
108
+ * Parity is pinned in test/component-policy-parity.test.ts.
109
+ */
110
+ export function defaultGateModeFor(componentId) {
111
+ return isSensitiveComponentKey(componentId) ? "hide" : "denied";
112
+ }
73
113
  /** True iff `id` names a confidentiality-sensitive managed component. */
74
114
  export function isSensitiveComponentKey(id) {
75
115
  return SENSITIVE_COMPONENT_KEYS.includes(id);
@@ -79,8 +119,10 @@ function managedDefault(componentId) {
79
119
  ? MANAGED_COMPONENT_DEFAULTS[componentId]
80
120
  : undefined;
81
121
  }
82
- /** Faithful copy of the contracts `CUSTOM_COMPONENT_KEY_PATTERN`. */
83
- export const CUSTOM_COMPONENT_KEY_PATTERN = /^custom:[a-z][a-z0-9_-]{1,47}$/;
122
+ /** Faithful copy of the contracts `CUSTOM_COMPONENT_KEY_PATTERN` — the slug
123
+ * budget matches the custom-subject grammar (≤32) so the derived permission
124
+ * subject is always declarable. */
125
+ export const CUSTOM_COMPONENT_KEY_PATTERN = /^custom:[a-z][a-z0-9_-]{1,31}$/;
84
126
  /** True iff `id` is a well-formed builder-custom component key. */
85
127
  export function isCustomComponentKey(id) {
86
128
  return CUSTOM_COMPONENT_KEY_PATTERN.test(id);
@@ -148,6 +190,7 @@ export function resolveComponentPolicy(componentId, overlays) {
148
190
  requiredPermissions: [],
149
191
  writePermission: `${derivedSubject(componentId)}:write`,
150
192
  gateMode: "denied",
193
+ gateModeSource: "default",
151
194
  presentational: false,
152
195
  sensitive: false,
153
196
  unknown: true,
@@ -169,7 +212,14 @@ export function resolveComponentPolicy(componentId, overlays) {
169
212
  const overlayGateMode = overlay?.gateMode != null && isComponentGateMode(overlay.gateMode)
170
213
  ? overlay.gateMode
171
214
  : undefined;
172
- const requestedGateMode = registration?.gateMode ?? overlayGateMode ?? "hide";
215
+ const requestedGateMode = registration?.gateMode ??
216
+ overlayGateMode ??
217
+ defaultGateModeFor(componentId);
218
+ const gateModeSource = registration?.gateMode !== undefined
219
+ ? "registration"
220
+ : overlayGateMode !== undefined
221
+ ? "overlay"
222
+ : "default";
173
223
  // `disable` and `readOnly` mount denied children. A sensitive component's
174
224
  // managed hide default is a confidentiality floor, so only non-mounting
175
225
  // deny modes may survive an overlay/registration.
@@ -180,7 +230,8 @@ export function resolveComponentPolicy(componentId, overlays) {
180
230
  const permissionCandidates = presentational
181
231
  ? []
182
232
  : [
183
- ...(sensitive && managed?.permission != null
233
+ ...((sensitive || DATA_FLOOR_COMPONENT_KEYS.includes(componentId)) &&
234
+ managed?.permission != null
184
235
  ? [managed.permission]
185
236
  : []),
186
237
  ...(permission != null ? [permission] : []),
@@ -202,6 +253,10 @@ export function resolveComponentPolicy(componentId, overlays) {
202
253
  managed?.writePermission ??
203
254
  `${subject}:write`,
204
255
  gateMode: denyByDefault ? "denied" : gateMode,
256
+ // A denyByDefault (malformed policy) forces "denied" regardless of the
257
+ // configured mode, so the source degrades to "default" — a wrapper must
258
+ // not treat a broken configuration as an explicit choice.
259
+ gateModeSource: denyByDefault ? "default" : gateModeSource,
205
260
  presentational,
206
261
  sensitive,
207
262
  unknown: denyByDefault,
@@ -1 +1 @@
1
- {"version":3,"file":"component-policy.js","sourceRoot":"","sources":["../src/component-policy.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"component-policy.js","sourceRoot":"","sources":["../src/component-policy.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"access-control.d.ts","sourceRoot":"","sources":["../../src/components/access-control.tsx"],"names":[],"mappings":"AA8CA;;8DAE8D;AAC9D,eAAO,MAAM,sBAAsB,qBAAqB,CAAC;AAiEzD,MAAM,WAAW,iBAAiB;IAChC,iCAAiC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;6EAGyE;IACzE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;oDACgD;IAChD,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;CAC7B;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,EAC3B,SAAS,EACT,SAAS,EACT,cAAc,GACf,GAAE,iBAAsB,sCA2XxB;AAID,MAAM,WAAW,kBAAkB;IACjC,iCAAiC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;+DAC2D;IAC3D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,qEAAqE;IACrE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;uDAEmD;IACnD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,EAC5B,SAAS,EACT,SAAS,EACT,SAAS,EACT,KAAsB,EACtB,aAAa,GACd,GAAE,kBAAuB,+BA0PzB;AAMD,MAAM,WAAW,oBAAoB;IACnC,iCAAiC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;sCAEkC;IAClC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;0DACsD;IACtD,cAAc,CAAC,EAAE,SAAS;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC/D;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,EAC9B,SAAS,EACT,SAAiB,EACjB,cAAmB,GACpB,GAAE,oBAAyB,sCAgM3B;AAID,MAAM,WAAW,mBAAmB;IAClC,iCAAiC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;iEAC6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,EAC7B,SAAS,EACT,KAAK,EAAE,SAAS,EAChB,UAAU,GACX,GAAE,mBAAwB,+BA+D1B;AAID,MAAM,WAAW,oBAAoB;IACnC,iCAAiC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,EAAE,SAAS,EAAE,GAAE,oBAAyB,sCAqJvE;AAID,MAAM,WAAW,0BAA0B;IACzC,iCAAiC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,SAAS,EACT,SAAiB,GAClB,GAAE,0BAA+B,sCAoGjC;AAID,MAAM,WAAW,oBAAoB;IACnC,+BAA+B;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;wDAGoD;IACpD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,qEAAqE;IACrE,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,EAC9B,SAAS,EACT,SAAS,EACT,SAAS,GACV,GAAE,oBAAyB,+BAyC3B"}
1
+ {"version":3,"file":"access-control.d.ts","sourceRoot":"","sources":["../../src/components/access-control.tsx"],"names":[],"mappings":"AA8CA;;8DAE8D;AAC9D,eAAO,MAAM,sBAAsB,qBAAqB,CAAC;AAyEzD,MAAM,WAAW,iBAAiB;IAChC,iCAAiC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;6EAGyE;IACzE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;oDACgD;IAChD,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;CAC7B;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,EAC3B,SAAS,EACT,SAAS,EACT,cAAc,GACf,GAAE,iBAAsB,sCAuexB;AAID,MAAM,WAAW,kBAAkB;IACjC,iCAAiC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;+DAC2D;IAC3D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,qEAAqE;IACrE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;uDAEmD;IACnD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,EAC5B,SAAS,EACT,SAAS,EACT,SAAS,EACT,KAAsB,EACtB,aAAa,GACd,GAAE,kBAAuB,+BA0PzB;AAMD,MAAM,WAAW,oBAAoB;IACnC,iCAAiC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;sCAEkC;IAClC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;0DACsD;IACtD,cAAc,CAAC,EAAE,SAAS;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC/D;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,EAC9B,SAAS,EACT,SAAiB,EACjB,cAAmB,GACpB,GAAE,oBAAyB,sCAgM3B;AAID,MAAM,WAAW,mBAAmB;IAClC,iCAAiC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;iEAC6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,EAC7B,SAAS,EACT,KAAK,EAAE,SAAS,EAChB,UAAU,GACX,GAAE,mBAAwB,+BA+D1B;AAID,MAAM,WAAW,oBAAoB;IACnC,iCAAiC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,EAAE,SAAS,EAAE,GAAE,oBAAyB,sCAqJvE;AAID,MAAM,WAAW,0BAA0B;IACzC,iCAAiC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,SAAS,EACT,SAAiB,GAClB,GAAE,0BAA+B,sCAoGjC;AAID,MAAM,WAAW,oBAAoB;IACnC,+BAA+B;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;wDAGoD;IACpD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,qEAAqE;IACrE,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,EAC9B,SAAS,EACT,SAAS,EACT,SAAS,GACV,GAAE,oBAAyB,+BAyC3B"}
@@ -75,6 +75,8 @@ function draftFromRole(role) {
75
75
  name: role.name,
76
76
  permissions: new Set(role.permissions.filter((p) => p !== "*")),
77
77
  fullAccess: role.permissions.includes("*"),
78
+ isAccountRole: role.kind === "ACCOUNT",
79
+ vocabulary: role.vocabulary,
78
80
  };
79
81
  }
80
82
  function draftPermissions(draft) {
@@ -90,12 +92,24 @@ function draftPermissions(draft) {
90
92
  export function FsRoleEditor({ className, canManage, onRolesChanged, } = {}) {
91
93
  const fs = useFartherShore();
92
94
  const auth = useOptionalFsAuth();
93
- const resolvedCanManage = canManage ?? auth?.hasPermission(MANAGE_RBAC_PERMISSION) ?? false;
95
+ // The /me claim is PRODUCT-plane (ACCOUNT rows deliberately excluded), so
96
+ // an ADMIN whose team:manage_rbac comes from account_admin has no claim —
97
+ // yet every read this editor performs is server-gated on that very
98
+ // permission. A successful load IS the live subscriber-plane capability
99
+ // check, so it backs the affordance (Codex #472 round 23). An explicit
100
+ // canManage prop still overrides.
101
+ const [serverCanManage, setServerCanManage] = useState(false);
102
+ const resolvedCanManage = canManage ??
103
+ ((auth?.hasPermission(MANAGE_RBAC_PERMISSION) ?? false) || serverCanManage);
94
104
  const [status, setStatus] = useState("loading");
95
105
  const [settings, setSettings] = useState({ enabled: false });
96
106
  const [roles, setRoles] = useState([]);
97
107
  const [catalog, setCatalog] = useState([]);
98
108
  const [draft, setDraft] = useState(null);
109
+ // ACCOUNT rows enforce for every business, product Managed-RBAC or not —
110
+ // when the settings/catalog surface is product-gated the editor degrades
111
+ // to an account-only view instead of hiding (Codex #472 round 14).
112
+ const [accountOnly, setAccountOnly] = useState(false);
99
113
  const [busy, setBusy] = useState(false);
100
114
  const [error, setError] = useState(null);
101
115
  const loadAll = useCallback(async () => {
@@ -111,10 +125,40 @@ export function FsRoleEditor({ className, canManage, onRolesChanged, } = {}) {
111
125
  setSettings(s);
112
126
  setCatalog(entries);
113
127
  setRoles(roleList);
128
+ // A remount-free client swap (different product) must not inherit a
129
+ // previous account-only degradation (Codex #472 round 19).
130
+ setAccountOnly(false);
131
+ // The settings/catalog/roles reads are server-gated on
132
+ // team:manage_rbac — reaching here proves the live capability.
133
+ setServerCanManage(true);
114
134
  setStatus("ready");
115
135
  }
116
136
  catch (err) {
117
137
  const code = apiErrorCode(err);
138
+ if (code === "RBAC_NOT_ENABLED_BY_PRODUCT") {
139
+ // The role LIST stays reachable: ACCOUNT rows are live enforcement
140
+ // for every business, so surface them in an account-only editor
141
+ // (no enable toggle, no default role, no create/delete).
142
+ try {
143
+ const roleList = await fs.rbac.roles.list();
144
+ const accountRows = roleList.filter((role) => role.kind === "ACCOUNT");
145
+ if (accountRows.length > 0) {
146
+ setSettings({ enabled: false });
147
+ setCatalog([]);
148
+ setRoles(accountRows);
149
+ setAccountOnly(true);
150
+ // The role list is server-gated on team:manage_rbac too.
151
+ setServerCanManage(true);
152
+ setStatus("ready");
153
+ return;
154
+ }
155
+ }
156
+ catch {
157
+ // fall through to hidden
158
+ }
159
+ setStatus("hidden");
160
+ return;
161
+ }
118
162
  if ((code && RBAC_HIDDEN_CODES.has(code)) ||
119
163
  (err instanceof FartherShoreApiError && err.status === 403)) {
120
164
  setStatus("hidden");
@@ -156,6 +200,12 @@ export function FsRoleEditor({ className, canManage, onRolesChanged, } = {}) {
156
200
  permissions: draftPermissions(draft),
157
201
  });
158
202
  }
203
+ else if (draft.isAccountRole) {
204
+ // ACCOUNT rows: permissions only — a rename is refused server-side.
205
+ await fs.rbac.roles.update(draft.roleKey, {
206
+ permissions: draftPermissions(draft),
207
+ });
208
+ }
159
209
  else {
160
210
  await fs.rbac.roles.update(draft.roleKey, {
161
211
  name: draft.name.trim(),
@@ -212,22 +262,38 @@ export function FsRoleEditor({ className, canManage, onRolesChanged, } = {}) {
212
262
  if (status === "error") {
213
263
  return (_jsx("section", { className: cx("fs-card", className), children: _jsx("p", { className: "fs-error", children: "Access control settings could not be loaded. Refresh to retry." }) }));
214
264
  }
265
+ // ACCOUNT rows are live enforcement whether or not the org's product-role
266
+ // toggle is on — they stay listed and editable with enforcement OFF, while
267
+ // the product-role machinery (default picker, create, CUSTOM rows) shows
268
+ // only when the toggle is ON (Codex #472 rounds 14+17).
269
+ const showFullEditor = settings.enabled && !accountOnly;
270
+ const visibleRoles = showFullEditor
271
+ ? roles
272
+ : roles.filter((role) => role.kind === "ACCOUNT");
215
273
  const draftValid = draft !== null &&
216
274
  draft.name.trim().length > 0 &&
217
275
  (draft.mode === "edit" ||
218
276
  /^[a-z0-9][a-z0-9_-]{0,63}$/.test(draft.roleKey)) &&
219
- (draft.fullAccess || draft.permissions.size > 0);
220
- return (_jsxs("section", { className: cx("fs-card", "fs-rbac", className), "aria-busy": busy, children: [_jsxs("div", { className: "fs-card__head", children: [_jsxs("div", { children: [_jsx("h3", { className: "fs-card__title", children: "Access control" }), _jsx("p", { className: "fs-card__note fs-muted", children: "Role-based access for this subscription's members. Roles limit which product routes a member's API access can call \u2014 enforced at the gateway, configured here." })] }), resolvedCanManage && (_jsx("button", { type: "button", className: cx("fs-btn fs-btn--sm", settings.enabled ? "fs-btn--outline" : "fs-btn--cta"), disabled: busy, onClick: () => void putSettings({
277
+ // ACCOUNT rows may be narrowed to NOTHING — the PATCH path accepts
278
+ // permissions: [] (Codex #472 round 17); a CUSTOM role still needs at
279
+ // least one grant (or the "*" claim) to mean anything.
280
+ (draft.fullAccess ||
281
+ draft.isAccountRole === true ||
282
+ draft.permissions.size > 0);
283
+ return (_jsxs("section", { className: cx("fs-card", "fs-rbac", className), "aria-busy": busy, children: [_jsxs("div", { className: "fs-card__head", children: [_jsxs("div", { children: [_jsx("h3", { className: "fs-card__title", children: "Access control" }), _jsx("p", { className: "fs-card__note fs-muted", children: "Role-based access for this subscription's members. Roles limit which product routes a member's API access can call \u2014 enforced at the gateway, configured here." })] }), resolvedCanManage && !accountOnly && (_jsx("button", { type: "button", className: cx("fs-btn fs-btn--sm", settings.enabled ? "fs-btn--outline" : "fs-btn--cta"), disabled: busy, onClick: () => void putSettings({
221
284
  enabled: !settings.enabled,
222
285
  defaultRoleKey: settings.defaultRoleKey ?? null,
223
- }), children: settings.enabled ? "Disable" : "Enable role-based access" }))] }), error && (_jsx("p", { className: "fs-error fs-card__note", "aria-live": "polite", children: error })), settings.enabled && (_jsxs("div", { className: "fs-rbac__body", children: [_jsxs("div", { className: "fs-rbac__block", children: [_jsx("label", { className: "fs-label", htmlFor: "fs-rbac-default", children: "Default role for unassigned members" }), _jsxs("select", { id: "fs-rbac-default", className: "fs-select", value: settings.defaultRoleKey ?? "", disabled: !resolvedCanManage || busy, onChange: (e) => void putSettings({
286
+ }), children: settings.enabled ? "Disable" : "Enable role-based access" }))] }), error && (_jsx("p", { className: "fs-error fs-card__note", "aria-live": "polite", children: error })), (settings.enabled || visibleRoles.length > 0) && (_jsxs("div", { className: "fs-rbac__body", children: [showFullEditor && (_jsxs("div", { className: "fs-rbac__block", children: [_jsx("label", { className: "fs-label", htmlFor: "fs-rbac-default", children: "Default role for unassigned members" }), _jsxs("select", { id: "fs-rbac-default", className: "fs-select", value: settings.defaultRoleKey ?? "", disabled: !resolvedCanManage || busy, onChange: (e) => void putSettings({
224
287
  enabled: true,
225
288
  defaultRoleKey: e.target.value || null,
226
- }), children: [_jsx("option", { value: "", children: "No default (unassigned members locked out)" }), roles.map((role) => (_jsx("option", { value: role.roleKey, children: role.name }, role.roleKey)))] })] }), _jsxs("div", { className: "fs-rbac__block", children: [_jsxs("div", { className: "fs-card__head", children: [_jsx("h4", { className: "fs-rbac__subtitle", children: "Roles" }), resolvedCanManage && !draft && (_jsx("button", { type: "button", className: "fs-btn fs-btn--outline fs-btn--sm", disabled: busy, onClick: () => setDraft(emptyDraft()), children: "New role" }))] }), roles.length === 0 ? (_jsx("p", { className: "fs-muted", children: "No roles yet." })) : (_jsx("ul", { className: "fs-rows", children: roles.map((role) => (_jsxs("li", { className: "fs-row", children: [_jsxs("span", { className: "fs-row__main", children: [role.name, " ", _jsx("code", { className: "fs-code-inline", children: role.roleKey }), _jsx("span", { className: "fs-muted fs-row__label", children: role.permissions.includes("*")
289
+ }), children: [_jsx("option", { value: "", children: "No default (unassigned members locked out)" }), roles
290
+ .filter((role) => role.kind !== "ACCOUNT")
291
+ .map((role) => (_jsx("option", { value: role.roleKey, children: role.name }, role.roleKey)))] })] })), _jsxs("div", { className: "fs-rbac__block", children: [_jsxs("div", { className: "fs-card__head", children: [_jsx("h4", { className: "fs-rbac__subtitle", children: "Roles" }), resolvedCanManage && !draft && showFullEditor && (_jsx("button", { type: "button", className: "fs-btn fs-btn--outline fs-btn--sm", disabled: busy, onClick: () => setDraft(emptyDraft()), children: "New role" }))] }), visibleRoles.length === 0 ? (_jsx("p", { className: "fs-muted", children: "No roles yet." })) : (_jsx("ul", { className: "fs-rows", children: visibleRoles.map((role) => (_jsxs("li", { className: "fs-row", children: [_jsxs("span", { className: "fs-row__main", children: [role.name, " ", _jsx("code", { className: "fs-code-inline", children: role.roleKey }), _jsx("span", { className: "fs-muted fs-row__label", children: role.permissions.includes("*")
227
292
  ? "Full access"
228
- : role.permissions.join(", ") || "—" })] }), role.roleKey === "owner" ? (_jsx("span", { className: "fs-muted fs-row__label", children: "Owner \u2014 always full access, not editable" })) : (resolvedCanManage && (_jsxs("span", { className: "fs-row__actions", children: [_jsx("button", { type: "button", className: "fs-btn fs-btn--outline fs-btn--sm", disabled: busy, onClick: () => setDraft(draftFromRole(role)), children: "Edit" }), _jsx("button", { type: "button", className: "fs-linkbtn fs-linkbtn--danger", disabled: busy, onClick: () => void deleteRole(role.roleKey), children: "Delete" })] })))] }, role.roleKey))) }))] }), resolvedCanManage && draft && (_jsxs("div", { className: "fs-rbac__block fs-rbac__editor", children: [_jsx("h4", { className: "fs-rbac__subtitle", children: draft.mode === "create"
293
+ : role.permissions.join(", ") || "—" })] }), role.roleKey === "owner" ? (_jsx("span", { className: "fs-muted fs-row__label", children: "Owner \u2014 always full access, not editable" })) : (resolvedCanManage && (_jsxs("span", { className: "fs-row__actions", children: [role.kind === "ACCOUNT" && (_jsx("span", { className: "fs-muted fs-row__label", children: "Account role" })), _jsx("button", { type: "button", className: "fs-btn fs-btn--outline fs-btn--sm", disabled: busy, onClick: () => setDraft(draftFromRole(role)), children: "Edit" }), role.kind !== "ACCOUNT" && (_jsx("button", { type: "button", className: "fs-linkbtn fs-linkbtn--danger", disabled: busy, onClick: () => void deleteRole(role.roleKey), children: "Delete" }))] })))] }, role.roleKey))) }))] }), resolvedCanManage && draft && (_jsxs("div", { className: "fs-rbac__block fs-rbac__editor", children: [_jsx("h4", { className: "fs-rbac__subtitle", children: draft.mode === "create"
229
294
  ? "New role"
230
- : `Edit role: ${draft.roleKey}` }), _jsxs("div", { className: "fs-card__actions", children: [draft.mode === "create" && (_jsxs("div", { className: "fs-rbac__field", children: [_jsx("label", { className: "fs-label", htmlFor: "fs-rbac-rolekey", children: "Role key" }), _jsx("input", { id: "fs-rbac-rolekey", className: "fs-input", placeholder: "e.g. analyst", value: draft.roleKey, onChange: (e) => setDraft({ ...draft, roleKey: e.target.value }) }), _jsx("p", { className: "fs-muted", children: "Lowercase letters, digits, hyphens, underscores." })] })), _jsxs("div", { className: "fs-rbac__field", children: [_jsx("label", { className: "fs-label", htmlFor: "fs-rbac-name", children: "Display name" }), _jsx("input", { id: "fs-rbac-name", className: "fs-input", placeholder: "e.g. Analyst", value: draft.name, onChange: (e) => setDraft({ ...draft, name: e.target.value }) })] })] }), _jsxs("label", { className: "fs-rbac__fullaccess", children: [_jsx("input", { type: "checkbox", checked: draft.fullAccess, onChange: (e) => setDraft({ ...draft, fullAccess: e.target.checked }) }), " ", "Full access (every route, including future ones)"] }), !draft.fullAccess &&
295
+ : `Edit role: ${draft.roleKey}` }), _jsxs("div", { className: "fs-card__actions", children: [draft.mode === "create" && (_jsxs("div", { className: "fs-rbac__field", children: [_jsx("label", { className: "fs-label", htmlFor: "fs-rbac-rolekey", children: "Role key" }), _jsx("input", { id: "fs-rbac-rolekey", className: "fs-input", placeholder: "e.g. analyst", value: draft.roleKey, onChange: (e) => setDraft({ ...draft, roleKey: e.target.value }) }), _jsx("p", { className: "fs-muted", children: "Lowercase letters, digits, hyphens, underscores." })] })), _jsxs("div", { className: "fs-rbac__field", children: [_jsx("label", { className: "fs-label", htmlFor: "fs-rbac-name", children: "Display name" }), _jsx("input", { id: "fs-rbac-name", className: "fs-input", placeholder: "e.g. Analyst", value: draft.name, disabled: draft.isAccountRole, onChange: (e) => setDraft({ ...draft, name: e.target.value }) }), draft.isAccountRole && (_jsx("p", { className: "fs-muted", children: "Account roles keep their name \u2014 only their permissions can be narrowed or restored." }))] })] }), !draft.isAccountRole && (_jsxs("label", { className: "fs-rbac__fullaccess", children: [_jsx("input", { type: "checkbox", checked: draft.fullAccess, onChange: (e) => setDraft({ ...draft, fullAccess: e.target.checked }) }), " ", "Full access (every route, including future ones)"] })), draft.isAccountRole && (_jsxs("fieldset", { className: "fs-rbac__group", children: [_jsx("legend", { className: "fs-label", children: "Account permissions" }), (draft.vocabulary ?? [...draft.permissions].sort()).map((permission) => (_jsxs("label", { className: "fs-rbac__perm", children: [_jsx("input", { type: "checkbox", checked: draft.permissions.has(permission), onChange: () => toggleDraftPermission(permission) }), " ", permission] }, permission)))] })), !draft.fullAccess &&
296
+ !draft.isAccountRole &&
231
297
  catalog.map((entry) => (_jsxs("fieldset", { className: "fs-keygrant__group", children: [_jsx("legend", { className: "fs-keygrant__legend", children: entry.title ?? entry.subject }), _jsx("div", { className: "fs-keygrant__opts", children: entry.permissions.map((permission) => (_jsxs("label", { className: "fs-keygrant__opt", children: [_jsx("input", { type: "checkbox", checked: draft.permissions.has(permission), onChange: () => toggleDraftPermission(permission) }), " ", _jsx("span", { className: "fs-code-inline", children: permission })] }, permission))) })] }, entry.subject))), _jsxs("div", { className: "fs-card__actions", children: [_jsx("button", { type: "button", className: "fs-btn fs-btn--cta fs-btn--sm", disabled: busy || !draftValid, onClick: () => void saveDraft(), children: busy
232
298
  ? "Saving…"
233
299
  : draft.mode === "create"