@cosmoops/connect-sdk 0.7.0 → 0.15.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 (154) hide show
  1. package/CHANGELOG.md +662 -0
  2. package/README.md +376 -124
  3. package/dist/cjs/cli/doctor.js +3 -3
  4. package/dist/cjs/cli/init.js +3 -3
  5. package/dist/cjs/client/components/access-gate.js +59 -5
  6. package/dist/cjs/client/components/ai-format.js +135 -0
  7. package/dist/cjs/client/components/ai-widgets.js +232 -0
  8. package/dist/cjs/client/components/app-shell.js +80 -45
  9. package/dist/cjs/client/components/apps-grid.js +3 -2
  10. package/dist/cjs/client/components/ask-ai.js +353 -0
  11. package/dist/cjs/client/components/confirm-dialog.js +40 -0
  12. package/dist/cjs/client/components/cursor-pagination.js +22 -0
  13. package/dist/cjs/client/components/filter-tabs.js +24 -0
  14. package/dist/cjs/client/components/grievance-notice.js +30 -0
  15. package/dist/cjs/client/components/link-button.js +17 -10
  16. package/dist/cjs/client/components/logo.js +4 -3
  17. package/dist/cjs/client/components/nav.js +42 -1
  18. package/dist/cjs/client/components/plan-pricing.js +27 -0
  19. package/dist/cjs/client/components/plans.js +1 -1
  20. package/dist/cjs/client/components/platform-gate.js +48 -0
  21. package/dist/cjs/client/components/shell-context.js +149 -0
  22. package/dist/cjs/client/components/side-nav.js +50 -24
  23. package/dist/cjs/client/components/sidebar.js +127 -0
  24. package/dist/cjs/client/components/subscribe-dialog.js +2 -2
  25. package/dist/cjs/client/components/subscription.js +1 -1
  26. package/dist/cjs/client/components/user-menu.js +122 -40
  27. package/dist/cjs/client/components/workspace-switcher.js +100 -0
  28. package/dist/cjs/client/components/workspace-type-gate.js +48 -0
  29. package/dist/cjs/client/index.js +98 -3
  30. package/dist/cjs/client/provider.js +100 -2
  31. package/dist/cjs/client/use-checkout.js +9 -5
  32. package/dist/cjs/errors.js +1 -1
  33. package/dist/cjs/index.js +11 -1
  34. package/dist/cjs/next/index.js +2 -2
  35. package/dist/cjs/server/client.js +25 -2
  36. package/dist/cjs/server/handler.js +237 -16
  37. package/dist/cjs/server/http.js +52 -26
  38. package/dist/cjs/server/resources/acting-as.js +16 -0
  39. package/dist/cjs/server/resources/ai.js +15 -2
  40. package/dist/cjs/server/resources/data.js +374 -0
  41. package/dist/cjs/server/resources/entitlements.js +41 -0
  42. package/dist/cjs/server/resources/identity.js +9 -3
  43. package/dist/cjs/server/resources/kv.js +84 -0
  44. package/dist/cjs/server/resources/platform.js +43 -0
  45. package/dist/cjs/server/resources/sessions.js +1 -1
  46. package/dist/cjs/server/resources/workspaces.js +74 -0
  47. package/dist/cjs/store.js +152 -0
  48. package/dist/cjs/ui/empty-state.js +28 -0
  49. package/dist/cjs/ui/index.js +44 -0
  50. package/dist/cjs/ui/page-header.js +27 -0
  51. package/dist/cjs/ui/page-shell.js +44 -0
  52. package/dist/cjs/ui/section.js +20 -0
  53. package/dist/cjs/ui/setting-row.js +14 -0
  54. package/dist/cjs/ui/status-chip.js +17 -0
  55. package/dist/esm/cli/doctor.js +3 -3
  56. package/dist/esm/cli/init.js +3 -3
  57. package/dist/esm/client/components/access-gate.js +60 -6
  58. package/dist/esm/client/components/ai-format.js +129 -0
  59. package/dist/esm/client/components/ai-widgets.js +223 -0
  60. package/dist/esm/client/components/app-shell.js +85 -50
  61. package/dist/esm/client/components/apps-grid.js +3 -2
  62. package/dist/esm/client/components/ask-ai.js +350 -0
  63. package/dist/esm/client/components/confirm-dialog.js +37 -0
  64. package/dist/esm/client/components/cursor-pagination.js +19 -0
  65. package/dist/esm/client/components/filter-tabs.js +21 -0
  66. package/dist/esm/client/components/grievance-notice.js +27 -0
  67. package/dist/esm/client/components/link-button.js +17 -10
  68. package/dist/esm/client/components/logo.js +4 -3
  69. package/dist/esm/client/components/nav.js +41 -1
  70. package/dist/esm/client/components/plan-pricing.js +25 -0
  71. package/dist/esm/client/components/plans.js +2 -2
  72. package/dist/esm/client/components/platform-gate.js +45 -0
  73. package/dist/esm/client/components/shell-context.js +141 -0
  74. package/dist/esm/client/components/side-nav.js +49 -22
  75. package/dist/esm/client/components/sidebar.js +120 -0
  76. package/dist/esm/client/components/subscribe-dialog.js +3 -3
  77. package/dist/esm/client/components/subscription.js +2 -2
  78. package/dist/esm/client/components/user-menu.js +121 -41
  79. package/dist/esm/client/components/workspace-switcher.js +97 -0
  80. package/dist/esm/client/components/workspace-type-gate.js +45 -0
  81. package/dist/esm/client/index.js +58 -5
  82. package/dist/esm/client/provider.js +94 -2
  83. package/dist/esm/client/use-checkout.js +9 -5
  84. package/dist/esm/errors.js +1 -1
  85. package/dist/esm/index.js +1 -0
  86. package/dist/esm/next/index.js +2 -2
  87. package/dist/esm/server/client.js +25 -2
  88. package/dist/esm/server/handler.js +237 -16
  89. package/dist/esm/server/http.js +52 -26
  90. package/dist/esm/server/resources/acting-as.js +13 -0
  91. package/dist/esm/server/resources/ai.js +15 -2
  92. package/dist/esm/server/resources/data.js +369 -0
  93. package/dist/esm/server/resources/entitlements.js +41 -0
  94. package/dist/esm/server/resources/identity.js +9 -3
  95. package/dist/esm/server/resources/kv.js +80 -0
  96. package/dist/esm/server/resources/platform.js +39 -0
  97. package/dist/esm/server/resources/sessions.js +1 -1
  98. package/dist/esm/server/resources/workspaces.js +70 -0
  99. package/dist/esm/store.js +141 -0
  100. package/dist/esm/ui/empty-state.js +25 -0
  101. package/dist/esm/ui/index.js +34 -0
  102. package/dist/esm/ui/page-header.js +24 -0
  103. package/dist/esm/ui/page-shell.js +41 -0
  104. package/dist/esm/ui/section.js +17 -0
  105. package/dist/esm/ui/setting-row.js +11 -0
  106. package/dist/esm/ui/status-chip.js +14 -0
  107. package/dist/theme.css +87 -0
  108. package/dist/types/client/components/access-gate.d.ts +17 -3
  109. package/dist/types/client/components/ai-format.d.ts +24 -0
  110. package/dist/types/client/components/ai-widgets.d.ts +42 -0
  111. package/dist/types/client/components/app-shell.d.ts +85 -31
  112. package/dist/types/client/components/apps-grid.d.ts +11 -2
  113. package/dist/types/client/components/ask-ai.d.ts +73 -0
  114. package/dist/types/client/components/confirm-dialog.d.ts +34 -0
  115. package/dist/types/client/components/cursor-pagination.d.ts +27 -0
  116. package/dist/types/client/components/filter-tabs.d.ts +26 -0
  117. package/dist/types/client/components/grievance-notice.d.ts +31 -0
  118. package/dist/types/client/components/link-button.d.ts +32 -9
  119. package/dist/types/client/components/logo.d.ts +18 -2
  120. package/dist/types/client/components/nav.d.ts +27 -3
  121. package/dist/types/client/components/plan-pricing.d.ts +10 -0
  122. package/dist/types/client/components/platform-gate.d.ts +53 -0
  123. package/dist/types/client/components/shell-context.d.ts +116 -0
  124. package/dist/types/client/components/side-nav.d.ts +28 -14
  125. package/dist/types/client/components/sidebar.d.ts +95 -0
  126. package/dist/types/client/components/user-menu.d.ts +70 -23
  127. package/dist/types/client/components/workspace-switcher.d.ts +49 -0
  128. package/dist/types/client/components/workspace-type-gate.d.ts +48 -0
  129. package/dist/types/client/index.d.ts +36 -7
  130. package/dist/types/client/provider.d.ts +149 -3
  131. package/dist/types/client/use-checkout.d.ts +8 -4
  132. package/dist/types/errors.d.ts +1 -1
  133. package/dist/types/index.d.ts +2 -0
  134. package/dist/types/next/index.d.ts +1 -1
  135. package/dist/types/server/client.d.ts +25 -2
  136. package/dist/types/server/http.d.ts +14 -3
  137. package/dist/types/server/index.d.ts +4 -0
  138. package/dist/types/server/resources/acting-as.d.ts +11 -0
  139. package/dist/types/server/resources/ai.d.ts +9 -1
  140. package/dist/types/server/resources/data.d.ts +258 -0
  141. package/dist/types/server/resources/entitlements.d.ts +33 -1
  142. package/dist/types/server/resources/kv.d.ts +63 -0
  143. package/dist/types/server/resources/platform.d.ts +32 -0
  144. package/dist/types/server/resources/workspaces.d.ts +55 -0
  145. package/dist/types/store.d.ts +96 -0
  146. package/dist/types/types.d.ts +491 -13
  147. package/dist/types/ui/empty-state.d.ts +34 -0
  148. package/dist/types/ui/index.d.ts +40 -0
  149. package/dist/types/ui/page-header.d.ts +34 -0
  150. package/dist/types/ui/page-shell.d.ts +39 -0
  151. package/dist/types/ui/section.d.ts +26 -0
  152. package/dist/types/ui/setting-row.d.ts +18 -0
  153. package/dist/types/ui/status-chip.d.ts +36 -0
  154. package/package.json +13 -3
@@ -1,7 +1,8 @@
1
1
  import { type ReactNode } from "react";
2
+ import { type ConnectAskAiProps } from "./ask-ai.js";
2
3
  import { type ConnectLinkRenderer, type ConnectNav } from "./nav.js";
3
4
  import type { ThemeMode } from "../../types.js";
4
- export interface ConnectAppShellProps {
5
+ export interface AppShellProps {
5
6
  /** This application's name, in the `cosmo·ops VOICE` lockup. See `ConnectLogo`. */
6
7
  name: string;
7
8
  /** This application's glyph, drawn on the CosmoOps badge. Falls back to the Connect rings. */
@@ -10,34 +11,51 @@ export interface ConnectAppShellProps {
10
11
  homeHref?: string;
11
12
  /** The sidebar's destinations - items, sections, or a mix. See `ConnectNav`. */
12
13
  nav: ConnectNav;
13
- /** Current path. Pass `usePathname()` in Next; see `ConnectSideNavProps.activePath`. */
14
+ /**
15
+ * A second nav group, pinned to the foot of the rail above the account row.
16
+ *
17
+ * For the routes a product has but does not want competing with its own:
18
+ * settings, help, changelog, status. They are the same `ConnectNav` shape
19
+ * and get the same active resolution and audience filtering - the only
20
+ * difference is where they sit, which is the difference between "what this
21
+ * product is for" and "everything else".
22
+ *
23
+ * A route belongs up top if a user goes there to *do the job*, and down here
24
+ * if they go there to change how the job works. Putting Settings in the
25
+ * primary list is what makes a nine-item rail out of a five-item product.
26
+ */
27
+ secondaryNav?: ConnectNav;
28
+ /** Current path. Pass `usePathname()` in Next; see `ConnectSidebarNavProps.activePath`. */
14
29
  activePath?: string;
15
30
  /** See `ConnectLinkRenderer` - how this app renders a link. Defaults to a plain `<a>`. */
16
31
  renderLink?: ConnectLinkRenderer;
32
+ /** Extra rows pinned to the bottom of the sidebar, above the account row. See `ConnectSidebarRow`. */
33
+ sidebarFooter?: ReactNode;
17
34
  /**
18
- * Page heading in the top bar. A string renders as the page's `<h1>`, so
19
- * don't also put one at the top of the page; pass a node for breadcrumbs or
20
- * anything else.
35
+ * The workspace switcher under the brand. On by default, and it hides itself
36
+ * for an account with only one workspace - so the reason to pass `false` is
37
+ * not "most users have one" (already handled) but "this application renders
38
+ * its own".
21
39
  */
22
- title?: ReactNode;
23
- /** Your own controls in the top bar, left of the Connect ones. */
24
- headerActions?: ReactNode;
25
- /** Extra content pinned to the bottom of the sidebar, above the fold-away. */
26
- sidebarFooter?: ReactNode;
40
+ workspaceSwitcher?: false;
27
41
  /** Rendered below `children`, inside the scrolling content pane. */
28
42
  footer?: ReactNode;
29
- /** Start the rail collapsed. Ignored when `collapsed` is supplied. */
43
+ /**
44
+ * Start the rail collapsed. **Defaults to `true`** - see the component's
45
+ * own doc comment. Ignored when `collapsed` is supplied, and overridden by
46
+ * a stored preference from the user's last visit.
47
+ */
30
48
  defaultCollapsed?: boolean;
31
49
  /** Drive the rail yourself. Supply `onCollapsedChange` with it. */
32
50
  collapsed?: boolean;
33
51
  onCollapsedChange?: (collapsed: boolean) => void;
34
52
  /** Remove the collapse control and pin the rail expanded. */
35
53
  collapsible?: boolean;
36
- /** The app launcher in the top bar. On by default. */
54
+ /** The app launcher, in the sidebar footer. On by default. */
37
55
  showAppsGrid?: boolean;
38
- /** "Continue with Connect" / "Create account" in the top bar when signed out. On by default. */
56
+ /** "Continue with Connect" / "Create account" in the sidebar footer when signed out. On by default. */
39
57
  showAuthButtons?: boolean;
40
- /** Passed to `ConnectUserMenu` - the SDK has no theme provider, so this is host-controlled. */
58
+ /** Passed to `ConnectUserButton` - the SDK has no theme provider, so this is host-controlled. */
41
59
  theme?: ThemeMode;
42
60
  onThemeChange?: (theme: ThemeMode) => void;
43
61
  /**
@@ -47,7 +65,7 @@ export interface ConnectAppShellProps {
47
65
  * gate asks for, and an app that mounts a shell and forgets the gate is an
48
66
  * app that lets a suspended workspace keep working.
49
67
  *
50
- * The chrome is never gated: the sidebar, the lockup, the user menu and
68
+ * The chrome is never gated: the sidebar, the lockup, the account row and
51
69
  * sign-out stay reachable in every blocked state, so a blocked customer can
52
70
  * always get to the page that unblocks them.
53
71
  *
@@ -61,55 +79,91 @@ export interface ConnectAppShellProps {
61
79
  * say. Prefix-matched, same as the nav's active resolution.
62
80
  */
63
81
  gateExemptRoutes?: string[];
82
+ /**
83
+ * Mount `ConnectAskAi` - a chat panel grounded in the signed-in workspace's
84
+ * Context Index, streamed through your own bridge. Pass an object to
85
+ * override its heading, copy or markdown plugins.
86
+ *
87
+ * **On by default**, like `gate`, and for the same reason: a family of
88
+ * application platforms should answer questions the same way, and a feature
89
+ * every partner has to remember to switch on is a feature most of them do
90
+ * not have. It stays inert until it can actually work - the trigger renders
91
+ * nothing for a signed-out visitor, and a workspace whose Context Index is
92
+ * off gets a message in the panel rather than a broken answer.
93
+ *
94
+ * Set `false` for an application that should not offer it at all.
95
+ */
96
+ askAi?: boolean | ConnectAskAiProps;
64
97
  className?: string;
65
98
  /** Classes on the scrolling content pane - override the default max-width and padding here. */
66
99
  contentClassName?: string;
67
100
  children: ReactNode;
68
101
  }
69
102
  /**
70
- * The application frame every CosmoOps application platform runs inside: a
71
- * collapsible sidebar, a top bar carrying the app launcher and the account
72
- * menu, and your pages in the middle.
103
+ * The application frame every CosmoOps application platform runs inside: one
104
+ * rail down the side holding the brand, the workspace, the navigation and the
105
+ * account, and your pages in the rest.
73
106
  *
74
107
  * This is the piece each partner was otherwise writing by hand, and writing
75
- * slightly differently - which is how a family of applications stops looking like
76
- * one product. Everything that should be identical across platforms (the
77
- * lockup, the rail geometry, the active-route treatment, the launcher, the
78
- * account menu, the access gate) lives here; everything that genuinely
79
- * differs is a prop:
108
+ * slightly differently - which is how a family of applications stops looking
109
+ * like one product. Everything that should be identical across platforms
110
+ * lives here; everything that genuinely differs is a prop:
80
111
  *
81
112
  * ```tsx
82
113
  * "use client";
83
114
  * import Link from "next/link";
84
115
  * import { usePathname } from "next/navigation";
85
- * import { ConnectAppShell } from "@cosmoops/connect-sdk/client";
86
- * import { LayoutDashboard, Truck } from "lucide-react";
116
+ * import { AppShell } from "@cosmoops/connect-sdk/client";
117
+ * import { LayoutDashboard, Settings, Truck } from "lucide-react";
87
118
  *
88
119
  * export function Shell({ children }: { children: React.ReactNode }) {
89
120
  * return (
90
- * <ConnectAppShell
121
+ * <AppShell
91
122
  * name="Dispatch"
92
123
  * icon={<Truck />}
93
124
  * activePath={usePathname()}
94
125
  * renderLink={(props) => <Link {...props} />}
95
126
  * nav={[
96
127
  * { label: "Overview", href: "/", icon: <LayoutDashboard className="size-5" /> },
97
- * { label: "Plans", href: "/plans", icon: <Truck className="size-5" />, gateExempt: true },
128
+ * { label: "Shipments", href: "/shipments", icon: <Truck className="size-5" /> },
129
+ * ]}
130
+ * secondaryNav={[
131
+ * { label: "Settings", href: "/settings", icon: <Settings className="size-5" /> },
98
132
  * ]}
99
133
  * >
100
134
  * {children}
101
- * </ConnectAppShell>
135
+ * </AppShell>
102
136
  * );
103
137
  * }
104
138
  * ```
105
139
  *
140
+ * **There is no top bar.** There used to be one, carrying a page title, the
141
+ * launcher and the account menu - and it was a second, half-width strip of
142
+ * chrome saying things the rail was better placed to say. The launcher and
143
+ * the account row moved into the rail's footer, where they are one press from
144
+ * wherever the pointer already is; the page title moved into the page, where
145
+ * `ConnectPageShell` renders it next to that page's own actions and
146
+ * breadcrumbs. What is left is a single column of chrome and a full-height
147
+ * content pane, which is what every application this shell is modelled on
148
+ * settled at.
149
+ *
150
+ * The one exception is below `md`, where a slim bar carries the drawer
151
+ * trigger. A phone has no rail to put it in, and a navigation you cannot open
152
+ * is not a smaller navigation.
153
+ *
154
+ * **The rail starts collapsed.** `defaultCollapsed` is `true`: the icon rail
155
+ * is the resting state, and expanding is the deliberate act. That is the right
156
+ * default because the rail's job is to be *there*, not to be read - a person
157
+ * learns five glyphs in a day and then wants the 200px back for their data.
158
+ * A stored preference from the user's last visit always wins over it.
159
+ *
106
160
  * There is no `isSignedIn` branch anywhere in here, and there should be none
107
- * in yours: the auth buttons render nothing once a session exists, the user
108
- * menu renders nothing without one, and the launcher renders nothing when
161
+ * in yours: the auth buttons render nothing once a session exists, the account
162
+ * row renders nothing without one, and the launcher renders nothing when
109
163
  * signed out. The swap happens inside the components.
110
164
  *
111
165
  * It renders no provider of its own - mount `ConnectProvider` above it, once,
112
166
  * so a page with a shell, a plan table and a usage meter still makes one
113
167
  * session read.
114
168
  */
115
- export declare function ConnectAppShell({ name, icon, homeHref, nav, activePath, renderLink, title, headerActions, sidebarFooter, footer, defaultCollapsed, collapsed: controlledCollapsed, onCollapsedChange, collapsible, showAppsGrid, showAuthButtons, theme, onThemeChange, gate, gateExemptRoutes, className, contentClassName, children, }: ConnectAppShellProps): import("react").JSX.Element;
169
+ export declare function AppShell({ name, icon, homeHref, nav, secondaryNav, activePath, renderLink, sidebarFooter, workspaceSwitcher, footer, defaultCollapsed, collapsed: controlledCollapsed, onCollapsedChange, collapsible, showAppsGrid, showAuthButtons, theme, onThemeChange, gate, gateExemptRoutes, askAi, className, contentClassName, children, }: AppShellProps): import("react").JSX.Element;
@@ -1,8 +1,17 @@
1
1
  export interface ConnectAppsGridProps {
2
2
  /** Extra classes on the trigger button. */
3
3
  className?: string;
4
- /** Trigger label for screen readers. */
4
+ /** Trigger label for screen readers, and the collapsed rail's tooltip. */
5
5
  label?: string;
6
+ /**
7
+ * How the trigger is drawn.
8
+ *
9
+ * - `"row"` - a full-width sidebar row, glyph on the rail's shared 32px
10
+ * centre. The default, since the launcher lives in the sidebar footer now
11
+ * that there is no top bar to hold it.
12
+ * - `"icon"` - the square accent button, for a bar of your own.
13
+ */
14
+ variant?: "row" | "icon";
6
15
  }
7
16
  /**
8
17
  * The Google-style app launcher: a grid icon in your top bar that opens a
@@ -17,4 +26,4 @@ export interface ConnectAppsGridProps {
17
26
  * Renders nothing when signed out: a launcher with nothing to launch is just a
18
27
  * button that opens an empty box.
19
28
  */
20
- export declare function ConnectAppsGrid({ className, label }: ConnectAppsGridProps): import("react").JSX.Element | null;
29
+ export declare function ConnectAppsGrid({ className, label, variant }: ConnectAppsGridProps): import("react").JSX.Element | null;
@@ -0,0 +1,73 @@
1
+ import { type StreamdownProps } from "streamdown";
2
+ export interface ConnectAskAiProps {
3
+ /** Panel heading. Defaults to `"Ask AI"`. */
4
+ title?: string;
5
+ /** Byline over each assistant turn. Defaults to `"Assistant"`. */
6
+ assistantName?: string;
7
+ /** Composer placeholder. */
8
+ placeholder?: string;
9
+ /** Shown once, above an empty conversation. */
10
+ hint?: string;
11
+ /** Start open. The panel owns its state after that. */
12
+ defaultOpen?: boolean;
13
+ /**
14
+ * Streamdown plugins - syntax highlighting, KaTeX, Mermaid, CJK.
15
+ *
16
+ * Off unless you pass them, because each is a package of its own and several
17
+ * are large (Mermaid alone dwarfs this SDK). Prose, GFM tables, task lists
18
+ * and plain code fences all render without any of them.
19
+ *
20
+ * ```tsx
21
+ * import { code } from "@streamdown/code";
22
+ * import { mermaid } from "@streamdown/mermaid";
23
+ *
24
+ * <ConnectAskAi markdownPlugins={{ code, mermaid }} />
25
+ * ```
26
+ *
27
+ * Hoist the object to module scope: Streamdown compares this prop by
28
+ * reference, so an inline literal defeats its block memoisation.
29
+ */
30
+ markdownPlugins?: StreamdownProps["plugins"];
31
+ }
32
+ /**
33
+ * Ask AI - a chat panel grounded in the signed-in workspace's Context Index,
34
+ * streamed through your own bridge.
35
+ *
36
+ * Answers are not only prose. Connect's model can call a small set of
37
+ * presentation tools - `showTable`, `showChart`, `showMetrics`, `showTimeline`,
38
+ * `showDetails`, `showSteps`, `showSources` - and this panel renders each one
39
+ * natively: a sortable table with a CSV download, a chart, a row of metric
40
+ * tiles. Values arrive raw with a format tag, so they are formatted here rather
41
+ * than by the model, which is what lets a column sort numerically.
42
+ *
43
+ * Nothing here holds a credential: it POSTs to `${basePath}/ai/chat` on your
44
+ * own origin, and `createConnectHandler` is what reaches Connect. The workspace
45
+ * is resolved from the caller's session there, never sent from the browser - so
46
+ * a user can only ever ask about the workspace they are signed into.
47
+ *
48
+ * ```tsx
49
+ * <AppShell name="Dispatch" nav={nav}>
50
+ * {children}
51
+ * </AppShell>
52
+ * ```
53
+ *
54
+ * `AppShell` mounts it by default. Mount it directly only when you are not
55
+ * using the shell: it renders a fixed corner trigger plus a panel that is an
56
+ * *in-flow flex sibling* at `lg` and up, so opening it pushes your content
57
+ * aside rather than covering it. That requires it to be the last child of a
58
+ * `flex` row owning the viewport height - which is exactly what `AppShell` is.
59
+ * Below `lg` it is a full-screen sheet with a backdrop instead; there is no
60
+ * room to push anything on a phone.
61
+ *
62
+ * **Host setup:** markdown rendering needs `streamdown` installed, one Tailwind
63
+ * `@source "…/node_modules/streamdown/dist/*.js"` line so its utility classes
64
+ * are generated, and `import "streamdown/styles.css"` for the streaming
65
+ * animation. The colour tokens Streamdown expects are already mapped in this
66
+ * SDK's `theme.css`.
67
+ *
68
+ * Answers require the workspace's Context Index to be turned on (a workspace
69
+ * admin toggle in Connect) and your API key to carry the `ai:chat` scope.
70
+ * Neither is something the browser can discover, so both surface as a message
71
+ * in the panel rather than as a hidden button.
72
+ */
73
+ export declare function ConnectAskAi({ title, assistantName, placeholder, hint, defaultOpen, markdownPlugins, }: ConnectAskAiProps): import("react").JSX.Element | null;
@@ -0,0 +1,34 @@
1
+ import { type ReactNode } from "react";
2
+ export interface ConnectConfirmDialogProps {
3
+ /** The control that opens it. Rendered inside `Modal.Trigger`. */
4
+ trigger: ReactNode;
5
+ title: ReactNode;
6
+ /** What will happen, in the user's words. Name the thing being acted on. */
7
+ description?: ReactNode;
8
+ confirmLabel?: string;
9
+ cancelLabel?: string;
10
+ /** `danger` for anything that destroys data. */
11
+ tone?: "danger" | "primary";
12
+ onConfirm: () => void | Promise<void>;
13
+ }
14
+ /**
15
+ * The confirmation in front of a destructive action.
16
+ *
17
+ * **It closes on confirm, not on response.** Holding the modal open until the
18
+ * server answers makes every delete feel like a form submission, and the error
19
+ * path already has somewhere to go - a toast, or the row reappearing. A dialog
20
+ * that stays open to report failure is right only where the failure is
21
+ * *expected* and correctable in place (a seat cap, a last-admin invariant),
22
+ * and that is a blocking form, not this.
23
+ *
24
+ * `onConfirm` may return a promise. It is not awaited before closing, but a
25
+ * rejection is caught here rather than escaping - an error thrown out of an
26
+ * async transition reaches the nearest error boundary and takes the page down.
27
+ *
28
+ * The anatomy is HeroUI's: `Modal.Backdrop` owns the open state,
29
+ * `Modal.Container` the sizing, `Modal.Dialog` the focus trap. The trigger
30
+ * goes inside `Modal.Trigger`, which *is* a React Aria button - so pass a
31
+ * plain element, never a `<Button>`, or the nested button swallows the press
32
+ * and the dialog silently never opens.
33
+ */
34
+ export declare function ConnectConfirmDialog({ trigger, title, description, confirmLabel, cancelLabel, tone, onConfirm, }: Readonly<ConnectConfirmDialogProps>): import("react").JSX.Element;
@@ -0,0 +1,27 @@
1
+ export interface ConnectCursorPaginationProps {
2
+ /** How many rows the current page is showing. Used for the live-region count. */
3
+ shown: number;
4
+ /** The cursor for the next page, or `null` when this is the last one. */
5
+ nextCursor: string | null;
6
+ /** Whether a previous page exists - the caller owns the cursor history. */
7
+ hasPrevious: boolean;
8
+ onNext: (cursor: string) => void;
9
+ onPrevious: () => void;
10
+ isPending?: boolean;
11
+ /** Singular/plural noun for the count, e.g. `["contact", "contacts"]`. */
12
+ noun?: readonly [string, string];
13
+ }
14
+ /**
15
+ * Next / previous for a cursor-paginated list.
16
+ *
17
+ * Cursor rather than page numbers, and the component shape follows from that:
18
+ * there is no "page 7 of 42" because a keyset-paginated list does not know how
19
+ * many pages there are without a second count query over the same filter -
20
+ * which is a billed index scan for a number nobody acts on. What a user does
21
+ * act on is "more" and "back", and those are exact.
22
+ *
23
+ * The count is announced in a live region so a screen-reader user learns that
24
+ * filtering or paging changed the result set. Without it the table silently
25
+ * swaps its contents.
26
+ */
27
+ export declare function ConnectCursorPagination({ shown, nextCursor, hasPrevious, onNext, onPrevious, isPending, noun, }: Readonly<ConnectCursorPaginationProps>): import("react").JSX.Element;
@@ -0,0 +1,26 @@
1
+ export interface ConnectFilterTabsOption<TValue extends string> {
2
+ value: TValue | null;
3
+ label: string;
4
+ /** Shown as a count beside the label. Omit where a count would need its own query. */
5
+ count?: number;
6
+ }
7
+ export interface ConnectFilterTabsProps<TValue extends string> {
8
+ options: ReadonlyArray<ConnectFilterTabsOption<TValue>>;
9
+ value: TValue | null;
10
+ onChange: (value: TValue | null) => void;
11
+ /** Names the group for assistive technology - "Filter contacts by lifecycle". */
12
+ label: string;
13
+ }
14
+ /**
15
+ * A single-select filter strip - the "All / Open / Won / Lost" row above a
16
+ * list.
17
+ *
18
+ * Real radio semantics rather than a row of buttons: it is a single choice
19
+ * from a closed set, so arrow keys should move between options and the
20
+ * selected one should be announced as selected. A group of `<button>`s gets
21
+ * neither, and that is the usual hand-rolled shape.
22
+ *
23
+ * Scrolls horizontally rather than wrapping on a narrow viewport - a filter
24
+ * strip that wraps to three lines pushes the list itself off the screen.
25
+ */
26
+ export declare function ConnectFilterTabs<TValue extends string>({ options, value, onChange, label, }: Readonly<ConnectFilterTabsProps<TValue>>): import("react").JSX.Element;
@@ -0,0 +1,31 @@
1
+ import type { ReactNode } from "react";
2
+ export interface ConnectGrievanceNoticeProps {
3
+ /**
4
+ * Heading text. `null` renders none, for embedding inside a page that
5
+ * already has one.
6
+ */
7
+ title?: ReactNode;
8
+ /**
9
+ * What this application calls itself in the sentence naming the processor
10
+ * relationship. Omit to leave that sentence out entirely.
11
+ */
12
+ applicationName?: string;
13
+ className?: string;
14
+ }
15
+ /**
16
+ * The DPDP s.13 grievance channel, rendered.
17
+ *
18
+ * Every application on the platform is a Data Processor under one notice, so
19
+ * the wording lives here rather than being retyped per application - a user
20
+ * comparing two applications' privacy pages should not find two different
21
+ * accounts of who their Data Fiduciary is.
22
+ *
23
+ * Renders nothing at all while loading and nothing on failure, rather than a
24
+ * skeleton or an error. That is unusual here and deliberate: this component
25
+ * is almost always inside a larger privacy page that has its own content, and
26
+ * a red error box in the middle of a legal notice reads as the notice itself
27
+ * being broken. The address is also on the public web either way. Pass
28
+ * `useConnectCompanyProfile()` directly to your own layout if you want to
29
+ * handle those states yourself.
30
+ */
31
+ export declare function ConnectGrievanceNotice({ title, applicationName, className, }: Readonly<ConnectGrievanceNoticeProps>): import("react").JSX.Element | null;
@@ -1,20 +1,43 @@
1
1
  import { type ButtonProps } from "@heroui/react";
2
+ import type { ReactElement, ReactNode } from "react";
2
3
  export type ConnectLinkButtonProps = Omit<ButtonProps, "render"> & {
3
4
  href: string;
5
+ /**
6
+ * The host's router link, if it has one.
7
+ *
8
+ * Without it this renders a plain `<a href>`, which navigates under every
9
+ * router but costs a **full document load** - in an App Router app that
10
+ * throws away the client cache and re-runs the whole tree, which is very
11
+ * visible on a button sitting inside a page the user is working in.
12
+ *
13
+ * ```tsx
14
+ * <ConnectLinkButton href="/deals" renderLink={(props) => <Link {...props} />}>
15
+ * Open board
16
+ * </ConnectLinkButton>
17
+ * ```
18
+ *
19
+ * Same shape as `AppShell`'s `renderLink`, so a host declares its router
20
+ * once and passes the same function to both. This package imports nothing
21
+ * from `next` - see the `./next` entry's doc comment for why that is a hard
22
+ * rule - so handing the link component in is the only way it can be a client
23
+ * transition.
24
+ */
25
+ renderLink?: (props: {
26
+ href: string;
27
+ className?: string;
28
+ children: ReactNode;
29
+ }) => ReactElement;
4
30
  };
5
31
  /**
6
- * A HeroUI `Button` that renders a real `<a>`.
32
+ * A HeroUI `Button` that renders a link.
7
33
  *
8
- * A plain anchor, deliberately not a router link: this package imports nothing
9
- * from `next` (see the `./next` entry's doc comment for why that is a hard
10
- * rule), so it cannot know which router - if any - the host app uses. An
11
- * `<a href>` navigates under all of them, and a host that wants client-side
12
- * routing can pass its own element via `render` on the underlying `Button`.
34
+ * HeroUI v3's `Button` has no `as` prop, so `<Button as={Link}>` does not type
35
+ * check and does not work - this is the supported route, via `render`.
13
36
  *
14
37
  * The cast is the same one HeroUI's own "Render Function" example needs:
15
38
  * `render` types its argument as the props of the element `Button` would have
16
- * emitted, which is a `<button>` - including a `ref` typed to
39
+ * emitted, which is a `<button>`, including a `ref` typed to
17
40
  * `HTMLButtonElement`. Spreading that onto an anchor is sound at runtime and
18
- * unrepresentable in the type, which is exactly what a cast is for.
41
+ * unrepresentable in the type, which is what the cast is for.
19
42
  */
20
- export declare function ConnectLinkButton({ href, ...props }: Readonly<ConnectLinkButtonProps>): import("react").JSX.Element;
43
+ export declare function ConnectLinkButton({ href, renderLink, ...props }: Readonly<ConnectLinkButtonProps>): import("react").JSX.Element;
@@ -14,7 +14,23 @@ export interface ConnectLogoProps {
14
14
  * Falls back to the Connect rings when omitted.
15
15
  */
16
16
  icon?: ReactNode;
17
- /** Mark only, no wordmark - for a collapsed sidebar or a favicon-sized slot. */
17
+ /**
18
+ * Which halves of the lockup to draw.
19
+ *
20
+ * - `"full"` - badge and wordmark, the default.
21
+ * - `"mark"` - the badge alone, for a collapsed rail or a favicon-sized slot.
22
+ * - `"wordmark"` - the words alone, for a layout that already draws the
23
+ * badge somewhere fixed. The sidebar header uses this: the badge stays put
24
+ * while the words fade, and rendering a second badge only to hide it with
25
+ * a CSS child selector is how that kind of layout quietly rots.
26
+ */
27
+ variant?: "full" | "mark" | "wordmark";
28
+ /**
29
+ * Mark only, no wordmark.
30
+ *
31
+ * @deprecated Pass `variant="mark"`. Kept because it is the published 0.11
32
+ * API; `variant` wins when both are given.
33
+ */
18
34
  collapsed?: boolean;
19
35
  className?: string;
20
36
  }
@@ -32,4 +48,4 @@ export interface ConnectLogoProps {
32
48
  * as `text-sm`, with the badge, gap and tracking all in proportion. That is
33
49
  * why there is no `size` prop.
34
50
  */
35
- export declare function ConnectLogo({ name, icon, collapsed, className }: ConnectLogoProps): import("react").JSX.Element;
51
+ export declare function ConnectLogo({ name, icon, variant, collapsed, className }: ConnectLogoProps): import("react").JSX.Element;
@@ -28,11 +28,26 @@ export interface ConnectNavItem {
28
28
  items?: ConnectNavItem[];
29
29
  /**
30
30
  * Leave this route *outside* `ConnectAccessGate` - see
31
- * `ConnectAppShellProps.gate`. Declared here, next to the route it applies
31
+ * `AppShellProps.gate`. Declared here, next to the route it applies
32
32
  * to, rather than as a second list somewhere else that has to be kept in
33
33
  * step with this one.
34
34
  */
35
35
  gateExempt?: boolean;
36
+ /**
37
+ * Who this route is for. Omit - the default - and it renders for everyone,
38
+ * which is what every route on an ordinary application is.
39
+ *
40
+ * - `"staff"` shows it only to a Connect platform staff session; your
41
+ * operator console goes here.
42
+ * - `"member"` hides it *from* staff. Reach for it on anything that reads
43
+ * the workspace - billing, plans, usage, team - since a staff session has
44
+ * no workspace and those routes can only fail for it.
45
+ *
46
+ * Filtering here is presentation, not access: it decides which links a
47
+ * person sees, and a link is not a permission. The route itself must still
48
+ * check `staff` on your server - see `<ConnectPlatformGate>`.
49
+ */
50
+ audience?: "staff" | "member";
36
51
  }
37
52
  /** A titled group of items. Untitled groups render as a plain divider-free block. */
38
53
  export interface ConnectNavSection {
@@ -40,7 +55,7 @@ export interface ConnectNavSection {
40
55
  items: ConnectNavItem[];
41
56
  }
42
57
  /**
43
- * What `ConnectAppShell`/`ConnectSideNav` accept for `nav`: a flat list of
58
+ * What `AppShell`/`ConnectSideNav` accept for `nav`: a flat list of
44
59
  * items, a list of sections, or a mix. Consecutive bare items fold into one
45
60
  * untitled section, so the common case is just an array of items.
46
61
  */
@@ -106,5 +121,14 @@ export declare function resolveActivePath(pathname: string, nav: ConnectNav): st
106
121
  * lights up on every app's home route. External items are never active.
107
122
  */
108
123
  export declare function isNavItemActive(item: ConnectNavItem, activeHref: string | null): boolean;
109
- /** Hrefs declared `gateExempt` - the routes `ConnectAppShell` renders outside its `ConnectAccessGate`. */
124
+ /**
125
+ * `nav` with every item the current session should not see removed - see
126
+ * `ConnectNavItem.audience`.
127
+ *
128
+ * Applied to nested `items` too, and a parent whose children all disappear is
129
+ * dropped along with them: a section header pointing at nothing reads as a
130
+ * broken link rather than as a hidden one.
131
+ */
132
+ export declare function filterNavForAudience(nav: ConnectNav, isStaff: boolean): ConnectNav;
133
+ /** Hrefs declared `gateExempt` - the routes `AppShell` renders outside its `ConnectAccessGate`. */
110
134
  export declare function gateExemptPaths(nav: ConnectNav): string[];
@@ -24,6 +24,16 @@ export declare function formatPrice(paise: number): string;
24
24
  * plan reads as an upgrade over every monthly one.
25
25
  */
26
26
  export declare function monthlyOf(price: number, billingInterval: Plan["billingInterval"]): number;
27
+ export declare function intervalLabel(billingInterval: Plan["billingInterval"]): {
28
+ per: string;
29
+ short: string;
30
+ adverb: string;
31
+ };
32
+ /**
33
+ * `/month`, `/year` - and nothing at all for a one-time plan, where a price
34
+ * with no slash is the honest rendering.
35
+ */
36
+ export declare function pricePerLabel(billingInterval: Plan["billingInterval"]): string;
27
37
  export declare function discountLabel(discount: AppliedDiscount): string;
28
38
  /**
29
39
  * The minimum a plan-like object has to expose to be grouped or compared -
@@ -0,0 +1,53 @@
1
+ import type { ReactNode } from "react";
2
+ export interface ConnectPlatformGateProps {
3
+ /**
4
+ * Restrict to these platform roles. Omit to accept any staff session, which
5
+ * is the common case - your application already chose which roles it accepts
6
+ * when a platform admin turned staff access on for it, and repeating that
7
+ * list here is a second copy to keep in sync.
8
+ *
9
+ * Pass one only when your own console has tiers: a `developer` seeing the
10
+ * read-only half of what a `platform_admin` can edit.
11
+ */
12
+ roles?: string[];
13
+ children: ReactNode;
14
+ /**
15
+ * Rendered for an ordinary member session. Defaults to `null` - hiding the
16
+ * subtree, which is what this gate is normally for. An operator console is
17
+ * usually better hidden than explained.
18
+ */
19
+ fallback?: ReactNode;
20
+ /** Shown until the session read settles. Defaults to nothing. */
21
+ loading?: ReactNode;
22
+ }
23
+ /**
24
+ * Renders `children` only for a Connect platform staff session.
25
+ *
26
+ * ```tsx
27
+ * <ConnectPlatformGate>
28
+ * <LinkMasterConsole />
29
+ * </ConnectPlatformGate>
30
+ * ```
31
+ *
32
+ * Staff are Connect's own operators - `platform_admin`, `platform_manager`,
33
+ * `developer` - signing into *your* application. They belong to no workspace,
34
+ * so a staff session carries no `workspaceId`, no subscription and no member
35
+ * role; see `SessionAccess.staff`. Staff access is off by default and turned on
36
+ * per application by a Connect platform admin, so this gate renders nothing
37
+ * until that happens.
38
+ *
39
+ * **This is a UX gate, not a security boundary** - the same caveat every gate
40
+ * in this package carries, and it matters more here than anywhere else,
41
+ * because what is behind this one is usually an operator console. The browser
42
+ * can ignore this component entirely. Every route it reveals must re-check
43
+ * `staff` on your own server:
44
+ *
45
+ * ```ts
46
+ * const { staff, role } = await client.identity.getUser(sessionToken);
47
+ * if (!staff) return forbidden();
48
+ * ```
49
+ *
50
+ * Renders `loading` - not `fallback` - while the session is unknown, so an
51
+ * operator never flickers through the customer layout on the way in.
52
+ */
53
+ export declare function ConnectPlatformGate({ roles, children, fallback, loading, }: ConnectPlatformGateProps): import("react").JSX.Element;