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,137 @@
1
+ /**
2
+ * `cortena-ui/agent-chat` — the extension's own agent, as a chat pop-up.
3
+ *
4
+ * §17 of how-to-create-a-cortena-extension, audit rule P-25. `AgentChatPopup`
5
+ * mounts in `AppShell`'s `agentSlot`; `AgentChat` is the surface it wraps, and
6
+ * is what cortenaweb adopts so there is one chat rather than two.
7
+ *
8
+ * Its own entry, and deliberately not re-exported from the root barrel, for the
9
+ * reason `sortable-list` has one: `createAguiAgentChatClient` imports
10
+ * `@ag-ui/client`, which brings rxjs, zod 3, uuid and protobuf. An extension
11
+ * that draws a button should not pay for a run stream it never opens, and the
12
+ * only guarantee that it does not is an import it has to ask for.
13
+ * `test/bundle-probe.test.mjs` measures it.
14
+ *
15
+ * ## `@ag-ui/client` is an OPTIONAL PEER, not a dependency
16
+ *
17
+ * ```jsonc
18
+ * "peerDependencies": { "@ag-ui/client": "^0.0.59" },
19
+ * "peerDependenciesMeta": { "@ag-ui/client": { "optional": true } }
20
+ * ```
21
+ *
22
+ * An extension that uses this entry installs `@ag-ui/client` itself, at a
23
+ * version its own package.json names. As a dependency of cortena-ui it was
24
+ * pinned to one exact build for the whole fleet, and an app that also talks
25
+ * AG-UI directly — which is every app with a second agent surface — resolved
26
+ * two copies. Two copies of an rxjs-based client is two event pipelines and
27
+ * two zod registries, and the symptoms are subscriptions that never fire.
28
+ *
29
+ * Optional, because it is only needed by `createAguiAgentChatClient`. Nothing
30
+ * else in this entry imports it: `AgentChat`, `AgentChatPopup` and
31
+ * `useAgentChat` are written against the `AgentChatClient` interface, so an
32
+ * extension on a different transport installs nothing and gets no warning.
33
+ *
34
+ * `createAguiAgentChatClient` loads it with a dynamic import, so this entry
35
+ * evaluates without it. It used to be a static import, which made "optional"
36
+ * untrue in the only way that matters: an extension that had not installed
37
+ * `@ag-ui/client` could not import `AgentChatPopup` either, and the error
38
+ * named a package it does not use. Now the peer is needed by one operation,
39
+ * and when it is absent the failure arrives in the chat, as a failed run,
40
+ * saying what to install.
41
+ *
42
+ * ```tsx
43
+ * import { AgentChatPopup, createAguiAgentChatClient } from "cortena-ui/agent-chat";
44
+ *
45
+ * const client = createAguiAgentChatClient({
46
+ * baseUrl: "/api/agent", // this extension's gateway, not cortenacore
47
+ * getToken: () => jwt, // the USER's JWT, never a service token
48
+ * agentId: "tasks", // = the AgentTemplate slug = the extension id
49
+ * });
50
+ *
51
+ * <AppShell extension={ext} user={user} agentSlot={<AgentChatPopup extension={ext} client={client} />}>
52
+ * ```
53
+ */
54
+
55
+ export type { A2UIChatBlock } from "@/agent-chat/a2ui-block";
56
+ export { extractA2UIBlocks, foldA2UIBlocks, readA2UIBlock } from "@/agent-chat/a2ui-block";
57
+ export type { AguiIncomingEvent, AgentChatBridgeOptions } from "@/agent-chat/bridge";
58
+ export { AGUI_CUSTOM_NAMES, AgentChatBridge } from "@/agent-chat/bridge";
59
+ export type { CreateAguiAgentChatClientOptions } from "@/agent-chat/agui-client";
60
+ export { createAguiAgentChatClient } from "@/agent-chat/agui-client";
61
+ export {
62
+ isHiddenMessage,
63
+ isProvisionalSessionKey,
64
+ isSessionOfAgent,
65
+ mintSessionKey,
66
+ parseHistoryMessages,
67
+ readStoredSessionKey,
68
+ readStoredSteps,
69
+ serialiseSteps,
70
+ sessionStorageKey,
71
+ STEP_LIMIT,
72
+ stepsStorageKey,
73
+ viewStorageKey,
74
+ wouldShrinkHistory,
75
+ writeStoredSessionKey,
76
+ writeStoredSteps,
77
+ } from "@/agent-chat/session";
78
+ export type { AgentStepPhase } from "@/agent-chat/step-label";
79
+ export {
80
+ confirmationRequiredLabel,
81
+ genericLabel,
82
+ humaniseId,
83
+ isGenericLabel,
84
+ stepLabel,
85
+ truncate,
86
+ } from "@/agent-chat/step-label";
87
+ export type {
88
+ AgentChatState,
89
+ AgentToolEntry,
90
+ UseAgentChatOptions,
91
+ UseAgentChatResult,
92
+ } from "@/agent-chat/store";
93
+ export { readResultMetadata, useAgentChat } from "@/agent-chat/store";
94
+ export type {
95
+ AgentApprovalDecision,
96
+ AgentApprovalEvent,
97
+ AgentApprovalRequest,
98
+ AgentChatClient,
99
+ AgentChatEvent,
100
+ AgentChatHistoryResponse,
101
+ AgentChatMessage,
102
+ AgentChatSendInput,
103
+ AgentChatStep,
104
+ AgentSession,
105
+ AgentSessionsListResponse,
106
+ AgentToolEvent,
107
+ AgentToolResultMeta,
108
+ AgentToolResultMetadata,
109
+ } from "@/agent-chat/types";
110
+ export { sessionTitle } from "@/agent-chat/types";
111
+ export type {
112
+ AgentA2UIBlocksProps,
113
+ AgentApprovalCardProps,
114
+ AgentChatProps,
115
+ AgentMessageBubbleProps,
116
+ AgentSessionDrawerProps,
117
+ AgentStepsProps,
118
+ AgentStreamingBubbleProps,
119
+ AgentThinkingProps,
120
+ AgentToolCardProps,
121
+ AgentToolRenderContext,
122
+ } from "@/components/agent-chat";
123
+ export {
124
+ AgentA2UIBlocks,
125
+ AgentApprovalCard,
126
+ AgentChat,
127
+ AgentMessageBubble,
128
+ AgentSessionDrawer,
129
+ AgentSteps,
130
+ AgentStreamingBubble,
131
+ AgentThinking,
132
+ AgentToolCard,
133
+ readMcpAppUri,
134
+ shouldStickToBottom,
135
+ } from "@/components/agent-chat";
136
+ export type { AgentChatPopupExtension, AgentChatPopupProps, AgentChatPopupState } from "@/components/agent-chat-popup";
137
+ export { AgentChatPopup } from "@/components/agent-chat-popup";
@@ -12,6 +12,8 @@
12
12
  */
13
13
 
14
14
  export type { AlertProps } from "@/components/alert";
15
+ export type { AppShellExtension, AppShellHelp, AppShellProps, AppShellUser, BottomRightClusterProps, BrandMarkProps, HelpButtonProps, HelpPanelSlotProps, ThemeToggleProps } from "@/components/app-shell";
16
+ export type { AuthScreenBrand, AuthScreenProps, LoginExtension, LoginMethod, LoginScreenProps, PasswordCredentials } from "@/components/login-screen";
15
17
  export type { BadgeProps } from "@/components/badge";
16
18
  export type { BreadcrumbLinkProps } from "@/components/breadcrumb";
17
19
  export type { ButtonLinkProps } from "@/components/button-link";
@@ -21,6 +23,7 @@ export type { CheckboxProps } from "@/components/checkbox";
21
23
  export type { ChipProps } from "@/components/chip";
22
24
  export type { ComboboxChipProps, ComboboxChipsProps, ComboboxContentProps, ComboboxInputProps } from "@/components/combobox";
23
25
  export type { CommandDialogProps, CommandItemProps } from "@/components/command";
26
+ export type { ConsentClient, ConsentScope, ConsentScreenProps, ConsentUser } from "@/components/consent-screen";
24
27
  export type { CortenaTheme, CortenaThemeMessage, ResolvedCortenaTheme, UseCortenaThemeOptions, UseCortenaThemeResult } from "@/hooks/use-cortena-theme";
25
28
  export type { DialogContentProps } from "@/components/dialog";
26
29
  export type { DropdownMenuContentProps, DropdownMenuItemProps, DropdownMenuLabelProps, DropdownMenuSubTriggerProps } from "@/components/dropdown-menu";
@@ -39,6 +42,7 @@ export type { SectionCardProps } from "@/components/section-card";
39
42
  export type { SegmentedOption, SegmentedProps } from "@/components/segmented";
40
43
  export type { SelectContentProps, SelectProps, SelectTriggerProps } from "@/components/select";
41
44
  export type { SeparatorProps } from "@/components/separator";
45
+ export type { SessionClaims, SessionExpiryDialogProps, SessionGuardMessage, SessionGuardProps, SessionPolicy, SessionSignOutReason, SessionState, SessionTimers, SessionToken, UseSessionGuardOptions, UseSessionGuardResult } from "@/components/session-guard";
42
46
  export type { SheetContentProps, SheetProps } from "@/components/sheet";
43
47
  export type { SpinnerProps } from "@/components/spinner";
44
48
  export type { StatusDotProps, StatusDotTone } from "@/components/status-dot";
@@ -49,6 +53,8 @@ export type { ToolbarProps } from "@/components/toolbar";
49
53
  export type { TooltipContentProps } from "@/components/tooltip";
50
54
  export { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/accordion";
51
55
  export { Alert, AlertDescription, AlertIcon, AlertTitle, alertVariants } from "@/components/alert";
56
+ export { AppShell, BottomRightCluster, BrandMark, HelpButton, ThemeToggle, documentTitle } from "@/components/app-shell";
57
+ export { AuthScreen, GoogleMark, LoginScreen, MicrosoftMark, httpsLogoUrl } from "@/components/login-screen";
52
58
  export { Avatar, AvatarFallback, AvatarImage } from "@/components/avatar";
53
59
  export { Badge, badgeVariants } from "@/components/badge";
54
60
  export { Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from "@/components/breadcrumb";
@@ -59,6 +65,8 @@ export { Checkbox } from "@/components/checkbox";
59
65
  export { Chip, chipVariants } from "@/components/chip";
60
66
  export { Combobox, ComboboxChip, ComboboxChips, ComboboxClear, ComboboxCollection, ComboboxContent, ComboboxEmpty, ComboboxGroup, ComboboxGroupLabel, ComboboxInput, ComboboxItem, ComboboxSeparator, ComboboxStatus, ComboboxTrigger, ComboboxValue, useComboboxFilter } from "@/components/combobox";
61
67
  export { Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandLoading, CommandSeparator, CommandShortcut } from "@/components/command";
68
+ export { ConsentScreen } from "@/components/consent-screen";
69
+ export { DEFAULT_SESSION_POLICY, SESSION_CHANNEL_NAME, SessionExpiryDialog, SessionGuard, decodeSessionClaims, formatCountdown, useSessionGuard } from "@/components/session-guard";
62
70
  export { DEFAULT_THEME_STORAGE_KEY, THEME_MESSAGE_TYPE, applyStoredTheme, applyTheme, getThemeInitScript, readStoredTheme, themeFromBackground, themeFromMessage, themeInitScript, useCortenaTheme } from "@/hooks/use-cortena-theme";
63
71
  export { Dialog, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger } from "@/components/dialog";
64
72
  export { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger } from "@/components/dropdown-menu";
@@ -7,3 +7,44 @@
7
7
  */
8
8
 
9
9
  export * from "@/components/data-table";
10
+
11
+ /**
12
+ * The extension's own admin area (§13, audit rule P-19): roles, the permission
13
+ * matrix, licence seats, and role assignment for users and groups. It lives
14
+ * here because its members screen is a `DataTable` on a server source, so it
15
+ * reaches `@tanstack/react-table` — an extension that draws a Button must not
16
+ * pay for a table engine to do it.
17
+ */
18
+ export type {
19
+ AdminEffectiveRole,
20
+ AdminErrorEnvelope,
21
+ AdminGroupRef,
22
+ AdminGroupRow,
23
+ AdminLicence,
24
+ AdminMemberRow,
25
+ AdminPermissionMatrix,
26
+ AdminPermissionsApi,
27
+ AdminPermissionsContextValue,
28
+ AdminPermissionsProps,
29
+ AdminPrincipal,
30
+ AdminRoleSource,
31
+ AdminRoute,
32
+ RoleAssignmentDialogProps,
33
+ } from "@/components/admin-permissions";
34
+ export {
35
+ ADMIN_ROUTES,
36
+ ADMIN_ROUTE_LABELS,
37
+ AdminLicenceScreen,
38
+ AdminMatrix,
39
+ AdminMembers,
40
+ AdminPermissions,
41
+ AdminPermissionsProvider,
42
+ AdminRoles,
43
+ DirectRoles,
44
+ EffectiveRoles,
45
+ RoleAssignmentDialog,
46
+ adminErrorMessage,
47
+ effectiveRolesOf,
48
+ sourceGroupOf,
49
+ useAdminPermissions,
50
+ } from "@/components/admin-permissions";
@@ -7,3 +7,28 @@
7
7
 
8
8
  export type { CodeRendererProps, MarkdownComponents, MarkdownProps } from "@/components/markdown";
9
9
  export { Markdown, markdownSanitizeSchema } from "@/components/markdown";
10
+
11
+ /**
12
+ * The in-extension help panel (§12, audit rule P-13). It lives here because it
13
+ * renders the extension's functional document as markdown, and an extension
14
+ * that draws a Button must not pay for react-markdown to do it.
15
+ */
16
+ export type {
17
+ HelpDocument,
18
+ HelpHotkeysOptions,
19
+ HelpHotkeysProps,
20
+ HelpPanelProps,
21
+ HelpPanelSlotConfig,
22
+ HelpRoute,
23
+ HelpSection,
24
+ HelpSource,
25
+ } from "@/components/help-panel";
26
+ export {
27
+ HelpHotkeys,
28
+ HelpPanel,
29
+ helpPanelSlot,
30
+ httpDocumentUrl,
31
+ parseHelpDocument,
32
+ resolveHelpTarget,
33
+ useHelpHotkeys,
34
+ } from "@/components/help-panel";
@@ -90,6 +90,39 @@ export function applyTheme(theme: CortenaTheme, root: HTMLElement = document.doc
90
90
 
91
91
  /* ── storage ─────────────────────────────────────────────────────────────── */
92
92
 
93
+ /**
94
+ * The mode already on `<html>`, if a host put one there.
95
+ *
96
+ * A Cortena extension is frequently mounted inside a shell that has ALREADY
97
+ * decided the theme — cortenaweb's iframe embed writes the attribute before
98
+ * the extension's bundle runs, and a server-rendered host writes it into the
99
+ * markup. "system" is this hook's word for "the user has not chosen", not for
100
+ * "remove whatever is there".
101
+ */
102
+ function documentTheme(): ResolvedCortenaTheme | null {
103
+ if (typeof document === "undefined") {
104
+ return null;
105
+ }
106
+ const value = document.documentElement.getAttribute("data-theme");
107
+ return value === "dark" || value === "light" ? value : null;
108
+ }
109
+
110
+ /** Exactly what `data-theme` says, `null` when it is absent, without judging it. */
111
+ function documentThemeAttribute(): string | null {
112
+ return typeof document === "undefined"
113
+ ? null
114
+ : document.documentElement.getAttribute("data-theme");
115
+ }
116
+
117
+ /** Whether a choice has actually been stored, as against defaulted to "system". */
118
+ function hasStoredTheme(storageKey: string): boolean {
119
+ try {
120
+ return isTheme(window.localStorage.getItem(storageKey));
121
+ } catch {
122
+ return false;
123
+ }
124
+ }
125
+
93
126
  export function readStoredTheme(storageKey: string = DEFAULT_THEME_STORAGE_KEY): CortenaTheme {
94
127
  try {
95
128
  const raw = window.localStorage.getItem(storageKey);
@@ -232,7 +265,10 @@ export function useCortenaTheme(options: UseCortenaThemeOptions = {}): UseCorten
232
265
 
233
266
  const resolvedTheme = useSyncExternalStore(
234
267
  subscribeScheme,
235
- () => (theme === "system" ? systemScheme() : theme),
268
+ // With no choice of the user's own, what is painted is what the host set,
269
+ // and only then the OS scheme. Reporting `systemScheme()` while <html>
270
+ // said `dark` made the toggle's icon and the page disagree.
271
+ () => (theme === "system" ? (documentTheme() ?? systemScheme()) : theme),
236
272
  () => "light" as const,
237
273
  );
238
274
 
@@ -245,10 +281,33 @@ export function useCortenaTheme(options: UseCortenaThemeOptions = {}): UseCorten
245
281
  [storageKey],
246
282
  );
247
283
 
248
- // Keep <html> in step with the store, including a change made in another tab.
284
+ /**
285
+ * Keep <html> in step with the store, including a change made in another tab.
286
+ *
287
+ * This is the whole of C4: with no stored choice, `theme` is "system", and
288
+ * writing that removes an attribute the host set — so an extension embedded
289
+ * in a dark cortenaweb flashed to light the instant its shell mounted, and
290
+ * stayed there. Nothing errors, nothing warns; the page is simply the wrong
291
+ * colour. "system" is this hook's word for "the user has not chosen", and
292
+ * only a stored choice licenses touching the attribute at all; `setTheme`
293
+ * writes storage first, so a deliberate "system" does still take it off.
294
+ *
295
+ * Decided fresh on every run, from two facts about the world rather than
296
+ * from a ref: whether a choice is stored, and what is on <html> right now.
297
+ * The ref this replaces made the first run special, and under StrictMode —
298
+ * or any remount — the *second* run was then no longer the first, so the
299
+ * host's attribute was removed a tick after mount by the very effect that
300
+ * exists to preserve it.
301
+ */
249
302
  useEffect(() => {
250
- applyTheme(theme);
251
- }, [theme]);
303
+ if (!hasStoredTheme(storageKey)) {
304
+ return;
305
+ }
306
+ const wanted = theme === "system" ? null : theme;
307
+ if (documentThemeAttribute() !== wanted) {
308
+ applyTheme(theme);
309
+ }
310
+ }, [storageKey, theme]);
252
311
 
253
312
  // Joined so a fresh array literal on every render does not re-subscribe.
254
313
  const originsKey = allowedOrigins?.join(" ") ?? "";
package/src/index.ts CHANGED
@@ -20,6 +20,12 @@
20
20
  * cortena-ui/a2ui the A2UI catalogue and renderer
21
21
  * cortena-ui/sortable-list SortableList (dnd-kit, which cannot be shaken)
22
22
  *
23
+ * One entry is not part of this union and never will be: `cortena-ui/agent-chat`
24
+ * imports `@ag-ui/client`, and the only guarantee that an extension drawing a
25
+ * Button does not download a run transport it never opens is an import it has to
26
+ * ask for. `test/bundle-probe.test.mjs` fails if `@ag-ui/*` reaches any other
27
+ * entry.
28
+ *
23
29
  * Each component has exactly one home, so these unions never collide. Adding a
24
30
  * component means adding it to its area entry, not here.
25
31
  */