cortena-ui 1.4.2 → 1.6.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 (138) hide show
  1. package/CHANGELOG.md +107 -0
  2. package/LICENSE +7 -0
  3. package/README.md +235 -3
  4. package/dist/a2ui/views.js +2 -2
  5. package/dist/agent-chat/a2ui-block.d.ts +60 -0
  6. package/dist/agent-chat/a2ui-block.js +69 -0
  7. package/dist/agent-chat/a2ui-block.js.map +1 -0
  8. package/dist/agent-chat/agui-client.d.ts +40 -0
  9. package/dist/agent-chat/agui-client.js +251 -0
  10. package/dist/agent-chat/agui-client.js.map +1 -0
  11. package/dist/agent-chat/bridge.d.ts +109 -0
  12. package/dist/agent-chat/bridge.js +353 -0
  13. package/dist/agent-chat/bridge.js.map +1 -0
  14. package/dist/agent-chat/session.d.ts +79 -0
  15. package/dist/agent-chat/session.js +391 -0
  16. package/dist/agent-chat/session.js.map +1 -0
  17. package/dist/agent-chat/step-label.d.ts +99 -0
  18. package/dist/agent-chat/step-label.js +116 -0
  19. package/dist/agent-chat/step-label.js.map +1 -0
  20. package/dist/agent-chat/store.d.ts +102 -0
  21. package/dist/agent-chat/store.js +876 -0
  22. package/dist/agent-chat/store.js.map +1 -0
  23. package/dist/agent-chat/types.d.ts +277 -0
  24. package/dist/agent-chat/types.js +17 -0
  25. package/dist/agent-chat/types.js.map +1 -0
  26. package/dist/agent-chat.d.ts +11 -0
  27. package/dist/agent-chat.js +11 -0
  28. package/dist/components/admin-permissions/admin-permissions.d.ts +66 -0
  29. package/dist/components/admin-permissions/admin-permissions.js +101 -0
  30. package/dist/components/admin-permissions/admin-permissions.js.map +1 -0
  31. package/dist/components/admin-permissions/context.d.ts +70 -0
  32. package/dist/components/admin-permissions/context.js +258 -0
  33. package/dist/components/admin-permissions/context.js.map +1 -0
  34. package/dist/components/admin-permissions/index.d.ts +10 -0
  35. package/dist/components/admin-permissions/licence.d.ts +15 -0
  36. package/dist/components/admin-permissions/licence.js +78 -0
  37. package/dist/components/admin-permissions/licence.js.map +1 -0
  38. package/dist/components/admin-permissions/matrix.d.ts +20 -0
  39. package/dist/components/admin-permissions/matrix.js +191 -0
  40. package/dist/components/admin-permissions/matrix.js.map +1 -0
  41. package/dist/components/admin-permissions/members.d.ts +18 -0
  42. package/dist/components/admin-permissions/members.js +185 -0
  43. package/dist/components/admin-permissions/members.js.map +1 -0
  44. package/dist/components/admin-permissions/role-assignment.d.ts +35 -0
  45. package/dist/components/admin-permissions/role-assignment.js +174 -0
  46. package/dist/components/admin-permissions/role-assignment.js.map +1 -0
  47. package/dist/components/admin-permissions/roles.d.ts +25 -0
  48. package/dist/components/admin-permissions/roles.js +168 -0
  49. package/dist/components/admin-permissions/roles.js.map +1 -0
  50. package/dist/components/admin-permissions/types.d.ts +152 -0
  51. package/dist/components/admin-permissions/types.js +63 -0
  52. package/dist/components/admin-permissions/types.js.map +1 -0
  53. package/dist/components/agent-chat-popup.d.ts +29 -0
  54. package/dist/components/agent-chat-popup.js +188 -0
  55. package/dist/components/agent-chat-popup.js.map +1 -0
  56. package/dist/components/agent-chat.d.ts +163 -0
  57. package/dist/components/agent-chat.js +673 -0
  58. package/dist/components/agent-chat.js.map +1 -0
  59. package/dist/components/app-shell.d.ts +126 -0
  60. package/dist/components/app-shell.js +297 -0
  61. package/dist/components/app-shell.js.map +1 -0
  62. package/dist/components/badge.d.ts +1 -1
  63. package/dist/components/button-link.js +1 -1
  64. package/dist/components/button.d.ts +1 -1
  65. package/dist/components/checkbox.d.ts +1 -1
  66. package/dist/components/combobox.d.ts +1 -1
  67. package/dist/components/combobox.js +1 -1
  68. package/dist/components/consent-screen.d.ts +65 -0
  69. package/dist/components/consent-screen.js +123 -0
  70. package/dist/components/consent-screen.js.map +1 -0
  71. package/dist/components/data-table/data-table.d.ts +15 -1
  72. package/dist/components/data-table/data-table.js +18 -4
  73. package/dist/components/data-table/data-table.js.map +1 -1
  74. package/dist/components/data-table/index.d.ts +4 -4
  75. package/dist/components/data-table/parts.d.ts +27 -3
  76. package/dist/components/data-table/parts.js +175 -55
  77. package/dist/components/data-table/parts.js.map +1 -1
  78. package/dist/components/data-table/types.d.ts +61 -0
  79. package/dist/components/data-table/use-data-table.js +91 -6
  80. package/dist/components/data-table/use-data-table.js.map +1 -1
  81. package/dist/components/data-table/use-server-source.js +119 -28
  82. package/dist/components/data-table/use-server-source.js.map +1 -1
  83. package/dist/components/help-panel.d.ts +131 -0
  84. package/dist/components/help-panel.js +545 -0
  85. package/dist/components/help-panel.js.map +1 -0
  86. package/dist/components/login-screen.d.ts +127 -0
  87. package/dist/components/login-screen.js +339 -0
  88. package/dist/components/login-screen.js.map +1 -0
  89. package/dist/components/session-guard.d.ts +268 -0
  90. package/dist/components/session-guard.js +632 -0
  91. package/dist/components/session-guard.js.map +1 -0
  92. package/dist/components/toast.d.ts +1 -1
  93. package/dist/core.d.ts +5 -1
  94. package/dist/core.js +11 -7
  95. package/dist/data-table.d.ts +13 -4
  96. package/dist/data-table.js +10 -2
  97. package/dist/hooks/use-cortena-theme.js +49 -3
  98. package/dist/hooks/use-cortena-theme.js.map +1 -1
  99. package/dist/index.d.ts +17 -4
  100. package/dist/index.js +21 -8
  101. package/dist/markdown.d.ts +2 -1
  102. package/dist/markdown.js +2 -1
  103. package/package.json +18 -5
  104. package/src/agent-chat/a2ui-block.ts +118 -0
  105. package/src/agent-chat/agui-client.ts +405 -0
  106. package/src/agent-chat/bridge.ts +445 -0
  107. package/src/agent-chat/session.ts +549 -0
  108. package/src/agent-chat/step-label.ts +177 -0
  109. package/src/agent-chat/store.ts +1234 -0
  110. package/src/agent-chat/types.ts +308 -0
  111. package/src/components/admin-permissions/admin-permissions.tsx +130 -0
  112. package/src/components/admin-permissions/context.tsx +376 -0
  113. package/src/components/admin-permissions/index.tsx +32 -0
  114. package/src/components/admin-permissions/licence.tsx +84 -0
  115. package/src/components/admin-permissions/matrix.tsx +257 -0
  116. package/src/components/admin-permissions/members.tsx +204 -0
  117. package/src/components/admin-permissions/role-assignment.tsx +239 -0
  118. package/src/components/admin-permissions/roles.tsx +169 -0
  119. package/src/components/admin-permissions/types.ts +231 -0
  120. package/src/components/agent-chat-popup.tsx +289 -0
  121. package/src/components/agent-chat.tsx +1006 -0
  122. package/src/components/app-shell.tsx +502 -0
  123. package/src/components/consent-screen.tsx +239 -0
  124. package/src/components/data-table/data-table.tsx +36 -0
  125. package/src/components/data-table/index.tsx +6 -1
  126. package/src/components/data-table/parts.tsx +223 -47
  127. package/src/components/data-table/types.ts +68 -0
  128. package/src/components/data-table/use-data-table.ts +152 -4
  129. package/src/components/data-table/use-server-source.ts +150 -12
  130. package/src/components/help-panel.tsx +765 -0
  131. package/src/components/login-screen.tsx +479 -0
  132. package/src/components/session-guard.tsx +1071 -0
  133. package/src/entries/agent-chat.ts +137 -0
  134. package/src/entries/core.ts +8 -0
  135. package/src/entries/data-table.ts +41 -0
  136. package/src/entries/markdown.ts +25 -0
  137. package/src/hooks/use-cortena-theme.ts +63 -4
  138. package/src/index.ts +6 -0
@@ -0,0 +1,268 @@
1
+ "use client";
2
+ import { DialogContentProps } from "./dialog.js";
3
+ import * as React from "react";
4
+ //#region src/components/session-guard.d.ts
5
+ /**
6
+ * SessionGuard — the shared session-expiry behaviour every Cortena surface
7
+ * wears (how-to-create-a-cortena-extension §9.4, audit rule P-37, decision
8
+ * DESIGN-D18).
9
+ *
10
+ * A user is warned before their session ends — Continue or Log out — and is
11
+ * logged out if they do nothing. Identical in every extension, in cortenaweb
12
+ * and in the agent pop-up, because an idle timeout that differs in length,
13
+ * warning or cross-tab behaviour between surfaces is one a user cannot learn.
14
+ *
15
+ * ## Two clocks, and two authorities
16
+ *
17
+ * There are two clocks and they are not the same clock:
18
+ *
19
+ * the access token plumbing. One hour, refreshed proactively at 75% of its
20
+ * life while the user is active. The user never sees it.
21
+ * Refreshing lazily on a 401 is the failure this component
22
+ * exists to remove: the first click after any pause fails,
23
+ * and if the refresh token has itself gone the screen is
24
+ * left half-dead — panels rendered from cache, every
25
+ * action silently doing nothing, no message anywhere.
26
+ * the idle timeout the user's. Reset by the user's hands, never by a
27
+ * background refresh, and always announced before it fires.
28
+ *
29
+ * And two authorities. **This guard is the experience; cortena-auth is the
30
+ * truth.** The countdown, the modal and the cross-tab handshake are here so the
31
+ * screen behaves; the decision that a session has actually ended is the refresh
32
+ * endpoint's, which refuses with `session_idle_expired` or `session_expired`.
33
+ * A browser clock can be wrong, paused by a sleeping laptop or moved by the
34
+ * user, so nothing here is a security boundary. A refresh that fails, for any
35
+ * reason and at any moment, is a signed-out state.
36
+ *
37
+ * ## The JWT claim contract it consumes
38
+ *
39
+ * The guard reads three claims out of the access token's payload, and it does
40
+ * so **by decoding, not by verifying**: it splits on `.`, base64url-decodes the
41
+ * middle segment and parses the JSON. No signature is checked, and none can be
42
+ * — the browser has no key, and it has no need of one, because nothing in this
43
+ * component is a decision the server will honour. A forged token buys an
44
+ * attacker a longer countdown on their own screen and nothing else; every
45
+ * request it is attached to is still verified by cortena-auth.
46
+ *
47
+ * `iat` epoch seconds. When the token was minted. The origin for the
48
+ * proactive refresh and for the absolute cap.
49
+ * `exp` epoch seconds. When it expires. Refresh fires at
50
+ * `iat + 0.75 * (exp - iat)`.
51
+ * `idle_exp` epoch seconds. When cortena-auth will call the session idle.
52
+ * Seeds the idle clock at mount, so a page reload after a long
53
+ * pause does not hand the user a fresh eight hours.
54
+ * `session_exp` epoch seconds. The absolute cap, equal to the refresh
55
+ * token's lifetime. It does not move, however active the user.
56
+ *
57
+ * Anything malformed, absent or unparseable falls back to the policy defaults
58
+ * (8 h idle, 12 h absolute, a 2 min warning) rather than throwing: a token this
59
+ * component cannot read must not be what stops a user working.
60
+ *
61
+ * An explicit `policy` value **replaces** the matching claim outright. That is
62
+ * the one rule; it is what makes the guide's twelve-second session and the
63
+ * tests' deterministic windows possible without a fabricated token.
64
+ *
65
+ * ## What resets what
66
+ *
67
+ * pointer, keyboard, `visibilitychange` reset the idle clock, while the
68
+ * state is `active`. Not network traffic — a polling screen left open on
69
+ * a desk is not a user. Not while the warning is up either: a modal that
70
+ * a stray mouse movement dismisses has not asked the user anything.
71
+ * Continue records activity and refreshes the token. The only thing that
72
+ * clears the warning.
73
+ * a refresh moves the access-token clock and nothing else. If a background
74
+ * refresh reset the idle clock, the idle timeout would never fire.
75
+ *
76
+ * Everything is shared across tabs over `BroadcastChannel`, so activity in one
77
+ * tab keeps them all alive and **one Continue answers every open tab**. Without
78
+ * it a user with three tabs open gets three modals and can dismiss the wrong
79
+ * one.
80
+ *
81
+ * ```tsx
82
+ * <SessionGuard
83
+ * token={{ accessToken }}
84
+ * refresh={() => auth.refresh()} // POST /api/auth/refresh
85
+ * logout={() => auth.logout()} // POST /api/auth/logout
86
+ * onSignedOut={(reason) => setSignedOut(reason)}
87
+ * />
88
+ * ```
89
+ *
90
+ * `onSignedOut` is where the app renders `LoginScreen` with `signedOut` and a
91
+ * `returnTo` (§7.4). Never a broken screen, never a spinner that never
92
+ * resolves — which is why the guard hands the reason back rather than
93
+ * redirecting itself.
94
+ */
95
+ /** Why the session ended. Handed to `onSignedOut` so the app can word it. */
96
+ export type SessionSignOutReason = "idle" | "absolute" | "refresh_failed" | "user";
97
+ /** What the guard shows: counting quietly, counting out loud, or over. */
98
+ export type SessionState = "active" | "warning" | "signedOut";
99
+ export interface SessionToken {
100
+ /** The current access token. Decoded, never verified; see the note above. */
101
+ accessToken: string;
102
+ /**
103
+ * When it expires, for a token whose `exp` cannot be read — an opaque token,
104
+ * or one this guard failed to parse. Epoch **milliseconds**; a value below
105
+ * `1e11` is read as epoch seconds instead, since no millisecond timestamp has
106
+ * been that small since 1973 and every second-based one will be until 5138.
107
+ */
108
+ expiresAt?: number;
109
+ }
110
+ export interface SessionPolicy {
111
+ /** How long an unattended screen stays signed in. Default 8 hours. */
112
+ idleMs: number;
113
+ /** The cap, however active the user has been. Default 12 hours. */
114
+ absoluteMs: number;
115
+ /** How long before the end the modal appears. Default 2 minutes. */
116
+ warnMs: number;
117
+ }
118
+ /** The org policy's defaults, used when neither `policy` nor a claim says otherwise. */
119
+ export declare const DEFAULT_SESSION_POLICY: SessionPolicy;
120
+ /**
121
+ * `setTimeout`/`clearTimeout`, injectable so a test can drive a whole session
122
+ * in a millisecond without the flake a real clock brings.
123
+ */
124
+ export interface SessionTimers {
125
+ setTimeout: (handler: () => void, ms: number) => unknown;
126
+ clearTimeout: (handle: unknown) => void;
127
+ }
128
+ /** The default `BroadcastChannel` name. One channel per origin is the point. */
129
+ export declare const SESSION_CHANNEL_NAME = "cortena-session";
130
+ /** What tabs say to each other. Deliberately tiny: no token ever crosses it. */
131
+ export type SessionGuardMessage = {
132
+ type: "activity";
133
+ at: number;
134
+ } | {
135
+ type: "continue";
136
+ at: number;
137
+ } |
138
+ /**
139
+ * One tab rotated the credential. The token itself never crosses — only
140
+ * when the new one expires, which is all another tab needs to re-plan its
141
+ * own proactive refresh instead of racing to rotate again.
142
+ */
143
+ {
144
+ type: "refreshed";
145
+ expiresAt?: number;
146
+ } |
147
+ /**
148
+ * The fallback claim, for a browser with no `navigator.locks`. `nonce` is
149
+ * the ballot: the highest one wins the election and rotates, and every other
150
+ * tab waits for that tab's `refreshed`. A claim without one comes from a tab
151
+ * running an older build and loses every election it takes part in, which
152
+ * still leaves exactly one refresher.
153
+ */
154
+ {
155
+ type: "refresh-claim";
156
+ at: number;
157
+ nonce?: string;
158
+ } | {
159
+ type: "signed-out";
160
+ reason: SessionSignOutReason;
161
+ };
162
+ export interface SessionClaims {
163
+ /** `iat`, in epoch milliseconds. */
164
+ issuedAt?: number;
165
+ /** `exp`, in epoch milliseconds. */
166
+ expiresAt?: number;
167
+ /** `idle_exp`, in epoch milliseconds. */
168
+ idleExpiresAt?: number;
169
+ /** `session_exp`, in epoch milliseconds. */
170
+ sessionExpiresAt?: number;
171
+ }
172
+ /**
173
+ * The four session claims out of a JWT payload, **decoded and not verified**.
174
+ *
175
+ * Returns `{}` for anything that is not a readable JWT — an opaque token, a
176
+ * truncated one, a payload that is not JSON. It never throws: a token the guard
177
+ * cannot read falls back to the policy defaults, because a parse error must not
178
+ * be what ends a user's session.
179
+ */
180
+ export declare function decodeSessionClaims(accessToken: string | undefined): SessionClaims;
181
+ export interface UseSessionGuardOptions {
182
+ token: SessionToken;
183
+ /** `POST /api/auth/refresh`. Resolves with the rotated access token. */
184
+ refresh: () => Promise<{
185
+ accessToken: string;
186
+ }>;
187
+ /** `POST /api/auth/logout`. Revokes the refresh token and clears storage. */
188
+ logout: () => Promise<void>;
189
+ /**
190
+ * The session is over. Render `LoginScreen` with `signedOut` and a
191
+ * `returnTo`; the guard does not navigate.
192
+ */
193
+ onSignedOut: (reason: SessionSignOutReason) => void;
194
+ /** Org policy from `GET /api/orgs/:orgId/session-policy`. Replaces the claims. */
195
+ policy?: Partial<SessionPolicy>;
196
+ /** The `BroadcastChannel` name. One per origin unless two apps must not share. */
197
+ channelName?: string;
198
+ /** The clock. Injected by tests and by the guide; `Date.now` otherwise. */
199
+ now?: () => number;
200
+ /** The timers. Injected by tests and by the guide; `setTimeout` otherwise. */
201
+ timers?: SessionTimers;
202
+ }
203
+ export interface UseSessionGuardResult {
204
+ /** `active` while it counts quietly, `warning` while the modal is up. */
205
+ state: SessionState;
206
+ /** Milliseconds left until the session ends. `0` once it has. */
207
+ remainingMs: number;
208
+ /**
209
+ * Whether Continue can buy the user anything. False once the absolute cap is
210
+ * the binding deadline, because no amount of activity moves that one.
211
+ */
212
+ canContinue: boolean;
213
+ /** Set once the session has ended. */
214
+ reason?: SessionSignOutReason;
215
+ /** The idle deadline, epoch ms. Moves with the user's hands. */
216
+ idleDeadline: number;
217
+ /** The absolute cap, epoch ms. Does not move. */
218
+ absoluteDeadline: number;
219
+ /** Whichever of the two comes first. */
220
+ deadline: number;
221
+ /** Record activity and refresh the token. What Continue calls. */
222
+ continueSession: () => void;
223
+ /** End the session now. What Log out calls, with `"user"`. */
224
+ signOut: (reason?: SessionSignOutReason) => void;
225
+ /** Record activity by hand, for an app with an activity signal of its own. */
226
+ recordActivity: () => void;
227
+ }
228
+ export declare function useSessionGuard(options: UseSessionGuardOptions): UseSessionGuardResult;
229
+ /** `m:ss`, rounded up, so the countdown reaches 0:00 exactly as it ends. */
230
+ export declare function formatCountdown(ms: number): string;
231
+ export interface SessionExpiryDialogProps {
232
+ open: boolean;
233
+ /** Milliseconds left. Rendered as `m:ss`. */
234
+ remainingMs: number;
235
+ /** Whether Continue is offered. False under the absolute cap, which no activity moves. */
236
+ canContinue?: boolean;
237
+ onContinue?: () => void;
238
+ onLogout?: () => void;
239
+ /** Non-modal for the guide, where three theme frames must not fight over focus. */
240
+ modal?: boolean;
241
+ /** Where to portal. The guide frames it; the product does not pass this. */
242
+ container?: DialogContentProps["container"];
243
+ }
244
+ /**
245
+ * The countdown. `role="alertdialog"` rather than `dialog`, because it is not a
246
+ * surface the user opened — it interrupts them to ask a question with a
247
+ * deadline, and assistive technology announces it as such.
248
+ *
249
+ * It is deliberately undismissable: no Escape, no outside press, no close X.
250
+ * Both ways out are decisions — Continue or Log out — and a modal you can wave
251
+ * away without answering is one that ends a session by accident. Focus opens on
252
+ * Continue, which is the answer nearly every user wants.
253
+ */
254
+ export declare function SessionExpiryDialog({ open, remainingMs, canContinue, onContinue, onLogout, modal, container }: SessionExpiryDialogProps): React.JSX.Element;
255
+ export interface SessionGuardProps extends UseSessionGuardOptions {
256
+ /** Non-modal, for the guide. The product leaves this alone. */
257
+ modal?: boolean;
258
+ /** Where to portal the modal — an agent pop-up's own root, say. */
259
+ container?: DialogContentProps["container"];
260
+ }
261
+ /**
262
+ * Mount one per surface: the shell mounts it, and so does the agent pop-up
263
+ * (§17). A user whose extension warns them while the chat pop-up beside it does
264
+ * not has learned nothing about how long a Cortena session lasts.
265
+ */
266
+ export declare function SessionGuard({ modal, container, ...options }: SessionGuardProps): React.JSX.Element;
267
+ //#endregion
268
+ //# sourceMappingURL=session-guard.d.ts.map