@cosmoops/connect-sdk 0.9.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 (139) hide show
  1. package/CHANGELOG.md +497 -0
  2. package/README.md +111 -41
  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 +20 -1
  6. package/dist/cjs/client/components/app-shell.js +75 -41
  7. package/dist/cjs/client/components/apps-grid.js +3 -2
  8. package/dist/cjs/client/components/ask-ai.js +20 -1
  9. package/dist/cjs/client/components/confirm-dialog.js +40 -0
  10. package/dist/cjs/client/components/cursor-pagination.js +22 -0
  11. package/dist/cjs/client/components/filter-tabs.js +24 -0
  12. package/dist/cjs/client/components/grievance-notice.js +30 -0
  13. package/dist/cjs/client/components/link-button.js +17 -10
  14. package/dist/cjs/client/components/logo.js +4 -3
  15. package/dist/cjs/client/components/nav.js +41 -0
  16. package/dist/cjs/client/components/plan-pricing.js +27 -0
  17. package/dist/cjs/client/components/plans.js +1 -1
  18. package/dist/cjs/client/components/platform-gate.js +48 -0
  19. package/dist/cjs/client/components/shell-context.js +149 -0
  20. package/dist/cjs/client/components/side-nav.js +49 -23
  21. package/dist/cjs/client/components/sidebar.js +127 -0
  22. package/dist/cjs/client/components/subscribe-dialog.js +2 -2
  23. package/dist/cjs/client/components/subscription.js +1 -1
  24. package/dist/cjs/client/components/user-menu.js +96 -37
  25. package/dist/cjs/client/components/workspace-switcher.js +100 -0
  26. package/dist/cjs/client/index.js +69 -2
  27. package/dist/cjs/client/provider.js +57 -2
  28. package/dist/cjs/client/use-checkout.js +9 -5
  29. package/dist/cjs/errors.js +1 -1
  30. package/dist/cjs/index.js +11 -1
  31. package/dist/cjs/next/index.js +2 -2
  32. package/dist/cjs/server/client.js +25 -2
  33. package/dist/cjs/server/handler.js +196 -18
  34. package/dist/cjs/server/http.js +6 -2
  35. package/dist/cjs/server/resources/acting-as.js +16 -0
  36. package/dist/cjs/server/resources/ai.js +15 -2
  37. package/dist/cjs/server/resources/data.js +374 -0
  38. package/dist/cjs/server/resources/identity.js +7 -3
  39. package/dist/cjs/server/resources/kv.js +84 -0
  40. package/dist/cjs/server/resources/platform.js +43 -0
  41. package/dist/cjs/server/resources/sessions.js +1 -1
  42. package/dist/cjs/server/resources/workspaces.js +74 -0
  43. package/dist/cjs/store.js +152 -0
  44. package/dist/cjs/ui/empty-state.js +28 -0
  45. package/dist/cjs/ui/index.js +44 -0
  46. package/dist/cjs/ui/page-header.js +27 -0
  47. package/dist/cjs/ui/page-shell.js +44 -0
  48. package/dist/cjs/ui/section.js +20 -0
  49. package/dist/cjs/ui/setting-row.js +14 -0
  50. package/dist/cjs/ui/status-chip.js +17 -0
  51. package/dist/esm/cli/doctor.js +3 -3
  52. package/dist/esm/cli/init.js +3 -3
  53. package/dist/esm/client/components/access-gate.js +20 -1
  54. package/dist/esm/client/components/app-shell.js +81 -47
  55. package/dist/esm/client/components/apps-grid.js +3 -2
  56. package/dist/esm/client/components/ask-ai.js +20 -1
  57. package/dist/esm/client/components/confirm-dialog.js +37 -0
  58. package/dist/esm/client/components/cursor-pagination.js +19 -0
  59. package/dist/esm/client/components/filter-tabs.js +21 -0
  60. package/dist/esm/client/components/grievance-notice.js +27 -0
  61. package/dist/esm/client/components/link-button.js +17 -10
  62. package/dist/esm/client/components/logo.js +4 -3
  63. package/dist/esm/client/components/nav.js +40 -0
  64. package/dist/esm/client/components/plan-pricing.js +25 -0
  65. package/dist/esm/client/components/plans.js +2 -2
  66. package/dist/esm/client/components/platform-gate.js +45 -0
  67. package/dist/esm/client/components/shell-context.js +141 -0
  68. package/dist/esm/client/components/side-nav.js +48 -21
  69. package/dist/esm/client/components/sidebar.js +120 -0
  70. package/dist/esm/client/components/subscribe-dialog.js +3 -3
  71. package/dist/esm/client/components/subscription.js +2 -2
  72. package/dist/esm/client/components/user-menu.js +95 -38
  73. package/dist/esm/client/components/workspace-switcher.js +97 -0
  74. package/dist/esm/client/index.js +46 -4
  75. package/dist/esm/client/provider.js +53 -2
  76. package/dist/esm/client/use-checkout.js +9 -5
  77. package/dist/esm/errors.js +1 -1
  78. package/dist/esm/index.js +1 -0
  79. package/dist/esm/next/index.js +2 -2
  80. package/dist/esm/server/client.js +25 -2
  81. package/dist/esm/server/handler.js +196 -18
  82. package/dist/esm/server/http.js +6 -2
  83. package/dist/esm/server/resources/acting-as.js +13 -0
  84. package/dist/esm/server/resources/ai.js +15 -2
  85. package/dist/esm/server/resources/data.js +369 -0
  86. package/dist/esm/server/resources/identity.js +7 -3
  87. package/dist/esm/server/resources/kv.js +80 -0
  88. package/dist/esm/server/resources/platform.js +39 -0
  89. package/dist/esm/server/resources/sessions.js +1 -1
  90. package/dist/esm/server/resources/workspaces.js +70 -0
  91. package/dist/esm/store.js +141 -0
  92. package/dist/esm/ui/empty-state.js +25 -0
  93. package/dist/esm/ui/index.js +34 -0
  94. package/dist/esm/ui/page-header.js +24 -0
  95. package/dist/esm/ui/page-shell.js +41 -0
  96. package/dist/esm/ui/section.js +17 -0
  97. package/dist/esm/ui/setting-row.js +11 -0
  98. package/dist/esm/ui/status-chip.js +14 -0
  99. package/dist/types/client/components/app-shell.d.ts +65 -27
  100. package/dist/types/client/components/apps-grid.d.ts +11 -2
  101. package/dist/types/client/components/confirm-dialog.d.ts +34 -0
  102. package/dist/types/client/components/cursor-pagination.d.ts +27 -0
  103. package/dist/types/client/components/filter-tabs.d.ts +26 -0
  104. package/dist/types/client/components/grievance-notice.d.ts +31 -0
  105. package/dist/types/client/components/link-button.d.ts +32 -9
  106. package/dist/types/client/components/logo.d.ts +18 -2
  107. package/dist/types/client/components/nav.d.ts +24 -0
  108. package/dist/types/client/components/plan-pricing.d.ts +10 -0
  109. package/dist/types/client/components/platform-gate.d.ts +53 -0
  110. package/dist/types/client/components/shell-context.d.ts +116 -0
  111. package/dist/types/client/components/side-nav.d.ts +27 -13
  112. package/dist/types/client/components/sidebar.d.ts +95 -0
  113. package/dist/types/client/components/user-menu.d.ts +68 -22
  114. package/dist/types/client/components/workspace-switcher.d.ts +49 -0
  115. package/dist/types/client/index.d.ts +27 -5
  116. package/dist/types/client/provider.d.ts +109 -6
  117. package/dist/types/client/use-checkout.d.ts +8 -4
  118. package/dist/types/errors.d.ts +1 -1
  119. package/dist/types/index.d.ts +2 -0
  120. package/dist/types/next/index.d.ts +1 -1
  121. package/dist/types/server/client.d.ts +25 -2
  122. package/dist/types/server/http.d.ts +8 -2
  123. package/dist/types/server/index.d.ts +4 -0
  124. package/dist/types/server/resources/acting-as.d.ts +11 -0
  125. package/dist/types/server/resources/ai.d.ts +9 -1
  126. package/dist/types/server/resources/data.d.ts +258 -0
  127. package/dist/types/server/resources/kv.d.ts +63 -0
  128. package/dist/types/server/resources/platform.d.ts +32 -0
  129. package/dist/types/server/resources/workspaces.d.ts +55 -0
  130. package/dist/types/store.d.ts +96 -0
  131. package/dist/types/types.d.ts +319 -15
  132. package/dist/types/ui/empty-state.d.ts +34 -0
  133. package/dist/types/ui/index.d.ts +40 -0
  134. package/dist/types/ui/page-header.d.ts +34 -0
  135. package/dist/types/ui/page-shell.d.ts +39 -0
  136. package/dist/types/ui/section.d.ts +26 -0
  137. package/dist/types/ui/setting-row.d.ts +18 -0
  138. package/dist/types/ui/status-chip.d.ts +36 -0
  139. package/package.json +6 -2
@@ -0,0 +1,152 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.increment = increment;
4
+ exports.serverTimestamp = serverTimestamp;
5
+ exports.arrayUnion = arrayUnion;
6
+ exports.arrayRemove = arrayRemove;
7
+ exports.deleteField = deleteField;
8
+ exports.unchangedSince = unchangedSince;
9
+ exports.documentExists = documentExists;
10
+ exports.storeIndexFor = storeIndexFor;
11
+ exports.storeIndexSignature = storeIndexSignature;
12
+ /**
13
+ * Building blocks for `data.commit` - the transform sentinels, and the two
14
+ * ways to say "only if nothing has moved".
15
+ *
16
+ * Isomorphic and dependency-free, so a Server Component and a client component
17
+ * building the same write use the same constructors. They are functions rather
18
+ * than object literals you write by hand because the sentinel shape is an
19
+ * encoding, not an API: `{ __transform: "increment", by: 1 }` is what goes over
20
+ * the wire, and nothing outside this file should have to know that.
21
+ */
22
+ /**
23
+ * Add to a number, atomically.
24
+ *
25
+ * **The one operation you genuinely cannot do yourself.** Increments are
26
+ * commutative, so two concurrent `increment(500)` calls both land - where
27
+ * read-add-write from your own process loses one of them however carefully it
28
+ * retries, because the read and the write are two round trips with a gap in
29
+ * between. Use this for balances, counters and tallies, and reach for a
30
+ * precondition only when the new value depends on the old one in a way
31
+ * addition cannot express.
32
+ *
33
+ * Negative to subtract; there is no separate `decrement`.
34
+ */
35
+ function increment(by) {
36
+ return { __transform: "increment", by };
37
+ }
38
+ /** Connect's own write time, rather than a clock reading from your process. */
39
+ function serverTimestamp() {
40
+ return { __transform: "serverTimestamp" };
41
+ }
42
+ /** Adds the values not already in the array. Order is not preserved. */
43
+ function arrayUnion(...values) {
44
+ return { __transform: "arrayUnion", values };
45
+ }
46
+ /** Removes every occurrence of each value. */
47
+ function arrayRemove(...values) {
48
+ return { __transform: "arrayRemove", values };
49
+ }
50
+ /**
51
+ * Removes the field entirely.
52
+ *
53
+ * `merge` only - a `replace` already drops every field it does not list, so
54
+ * asking it to delete one is a contradiction rather than a narrowing.
55
+ */
56
+ function deleteField() {
57
+ return { __transform: "delete" };
58
+ }
59
+ /**
60
+ * "Only if this document has not changed since I read it."
61
+ *
62
+ * Pass the document a read returned, and the precondition is built from its
63
+ * `_updateTime`. Taking the document rather than the timestamp is deliberate:
64
+ * a caller who has to reach for `doc.data._updateTime` themselves is one typo
65
+ * away from a precondition that silently does nothing.
66
+ *
67
+ * Throws rather than degrading when the document carries no `_updateTime` -
68
+ * that only happens for a document you built yourself rather than read, and a
69
+ * precondition that quietly became "no precondition" is worse than a crash at
70
+ * the call site.
71
+ */
72
+ function unchangedSince(document) {
73
+ const updateTime = document.data._updateTime;
74
+ if (typeof updateTime !== "string") {
75
+ throw new Error(`Document "${document.id}" has no _updateTime, so there is no version to hold it to. Preconditions are built from documents you read back, not from ones you constructed.`);
76
+ }
77
+ return { updateTime };
78
+ }
79
+ /** "Only if this document exists" / "only if it does not." */
80
+ function documentExists(exists = true) {
81
+ return { exists };
82
+ }
83
+ /** Firestore treats these as a range scan, which constrains where they may sit. */
84
+ const RANGE_OPERATORS = new Set(["!=", "not-in", "<", "<=", ">", ">="]);
85
+ /** Served by an equality-shaped index entry - `in` is a disjunction of equalities. */
86
+ const EQUALITY_OPERATORS = new Set(["==", "in"]);
87
+ /**
88
+ * The composite index a query shape needs, or `null` when Firestore's
89
+ * automatic single-field indexes already serve it.
90
+ *
91
+ * Use it to generate your Store tab declarations from the queries your code
92
+ * actually makes, rather than transcribing them by hand and discovering the
93
+ * gap in production:
94
+ *
95
+ * ```ts
96
+ * const needed = MY_QUERY_SHAPES.map(storeIndexFor).filter(Boolean);
97
+ * ```
98
+ *
99
+ * **Field order is Firestore's, not yours.** Equality fields first (sorted, so
100
+ * the order you passed them in cannot produce two different declarations for
101
+ * one query), then the `array-contains` field, then the field the query orders
102
+ * by. Declaring the same shape twice in two orders builds two indexes, one of
103
+ * which is dead weight on every document write.
104
+ *
105
+ * Connect runs the same derivation server-side and is the authority: a query
106
+ * with no matching declared index is refused with the index it needs spelled
107
+ * out, so a disagreement here surfaces on the first call rather than silently.
108
+ *
109
+ * Throws for a query Firestore cannot serve at all - two `array-contains`
110
+ * filters, ranges over two fields, or a range ordered by a different field.
111
+ */
112
+ function storeIndexFor(query) {
113
+ const filters = query.filters ?? [];
114
+ const orderBy = query.orderBy ?? null;
115
+ const equalities = filters.filter((filter) => EQUALITY_OPERATORS.has(filter.op));
116
+ const arrayContains = filters.filter((filter) => filter.op === "array-contains");
117
+ const rangeFields = [
118
+ ...new Set(filters.filter((filter) => RANGE_OPERATORS.has(filter.op)).map((f) => f.field)),
119
+ ];
120
+ if (arrayContains.length > 1) {
121
+ throw new Error(`A query may use "array-contains" once; this one uses it ${arrayContains.length} times.`);
122
+ }
123
+ if (rangeFields.length > 1) {
124
+ throw new Error(`A query may range over one field; this one ranges over ${rangeFields.join(", ")}.`);
125
+ }
126
+ const rangeField = rangeFields[0] ?? null;
127
+ if (rangeField && orderBy && orderBy.field !== rangeField) {
128
+ throw new Error(`A range filter on "${rangeField}" must be ordered by "${rangeField}" - Firestore cannot order a range scan by "${orderBy.field}".`);
129
+ }
130
+ const ordered = orderBy?.field ?? rangeField;
131
+ const distinct = new Set([...filters.map((filter) => filter.field), ...(ordered ? [ordered] : [])]);
132
+ if (filters.length <= 1 && distinct.size <= 1)
133
+ return null;
134
+ const fields = [
135
+ ...[...new Set(equalities.map((filter) => filter.field))]
136
+ .sort()
137
+ .map((field) => ({ field, direction: "asc" })),
138
+ ...arrayContains.map((filter) => ({ field: filter.field, direction: "array-contains" })),
139
+ ];
140
+ if (ordered)
141
+ fields.push({ field: ordered, direction: orderBy?.direction ?? "asc" });
142
+ return fields;
143
+ }
144
+ /**
145
+ * An index as one comparable string - `"status:asc,createdAt:desc"`.
146
+ *
147
+ * Deduplicate a generated list on this before declaring it: a dozen query
148
+ * shapes across a screen usually collapse to two or three distinct indexes.
149
+ */
150
+ function storeIndexSignature(fields) {
151
+ return fields.map((entry) => `${entry.field}:${entry.direction}`).join(",");
152
+ }
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConnectEmptyState = ConnectEmptyState;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("@heroui/react");
6
+ /**
7
+ * What a list, grid or table shows when it has nothing to show.
8
+ *
9
+ * **The two empty states are different facts and the copy must say so.** "You
10
+ * have no contacts" and "no contacts match this filter" call for different
11
+ * words and a different action - create the thing, versus undo the filter
12
+ * hiding it. Both render through here; the caller decides which by what it
13
+ * passes as `action`. Getting this wrong costs a user real time: an
14
+ * unfiltered-looking empty state in front of a filtered list reads as data
15
+ * loss.
16
+ *
17
+ * `variant` chooses the container by what the state stands *in*: `card` (the
18
+ * default), `dashed` for a region that will be filled in place - a file grid,
19
+ * a board column - where the outline is what says "things appear here", and
20
+ * `plain` for a slot already inside a card that would otherwise nest two
21
+ * borders.
22
+ */
23
+ function ConnectEmptyState({ icon, title, description, action, variant = "card", className, }) {
24
+ const body = ((0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col items-center text-center", children: [icon && ((0, jsx_runtime_1.jsx)("span", { "aria-hidden": true, className: "mb-3 text-muted [&>svg]:size-8", children: icon })), (0, jsx_runtime_1.jsx)("p", { className: "text-sm font-medium text-foreground", children: title }), description && (0, jsx_runtime_1.jsx)("p", { className: "mt-1 max-w-md text-sm text-muted", children: description }), action && (0, jsx_runtime_1.jsx)("div", { className: "mt-3", children: action })] }));
25
+ if (variant === "plain")
26
+ return (0, jsx_runtime_1.jsx)("div", { className: className, children: body });
27
+ return ((0, jsx_runtime_1.jsx)(react_1.Card, { className: `p-5 sm:p-8 ${variant === "dashed" ? "border-dashed" : ""} ${className ?? ""}`, children: body }));
28
+ }
@@ -0,0 +1,44 @@
1
+ "use client";
2
+ "use strict";
3
+ /**
4
+ * Page-composition primitives - the layer between HeroUI and your features.
5
+ *
6
+ * Deliberately **not** `"use client"`. Everything here is presentational, with
7
+ * no hooks and no state, so it renders in a Server Component and stays out of
8
+ * the browser bundle. That is the whole reason this is a separate entry point
9
+ * from `@cosmoops/connect-sdk/client`: that module's barrel carries a
10
+ * `"use client"` directive, so re-exporting a `<Card>` wrapper through it
11
+ * would turn every page shell in your app into a client boundary for no gain.
12
+ *
13
+ * The interactive counterparts - a confirm dialog, cursor pagination, filter
14
+ * tabs - hold state and live in `/client` alongside the rest of the browser
15
+ * half.
16
+ *
17
+ * ```tsx
18
+ * import { ConnectPageShell, ConnectSection } from "@cosmoops/connect-sdk/ui";
19
+ *
20
+ * export default function ContactsPage() {
21
+ * return (
22
+ * <ConnectPageShell title="Contacts" description="Everyone you do business with.">
23
+ * <ConnectPageShell.Async fallback={<TableSkeleton />}>
24
+ * <ContactsTable />
25
+ * </ConnectPageShell.Async>
26
+ * </ConnectPageShell>
27
+ * );
28
+ * }
29
+ * ```
30
+ */
31
+ Object.defineProperty(exports, "__esModule", { value: true });
32
+ exports.ConnectStatusChip = exports.ConnectSettingRow = exports.ConnectEmptyState = exports.ConnectSection = exports.ConnectPageHeader = exports.ConnectPageShell = void 0;
33
+ var page_shell_js_1 = require("./page-shell.js");
34
+ Object.defineProperty(exports, "ConnectPageShell", { enumerable: true, get: function () { return page_shell_js_1.ConnectPageShell; } });
35
+ var page_header_js_1 = require("./page-header.js");
36
+ Object.defineProperty(exports, "ConnectPageHeader", { enumerable: true, get: function () { return page_header_js_1.ConnectPageHeader; } });
37
+ var section_js_1 = require("./section.js");
38
+ Object.defineProperty(exports, "ConnectSection", { enumerable: true, get: function () { return section_js_1.ConnectSection; } });
39
+ var empty_state_js_1 = require("./empty-state.js");
40
+ Object.defineProperty(exports, "ConnectEmptyState", { enumerable: true, get: function () { return empty_state_js_1.ConnectEmptyState; } });
41
+ var setting_row_js_1 = require("./setting-row.js");
42
+ Object.defineProperty(exports, "ConnectSettingRow", { enumerable: true, get: function () { return setting_row_js_1.ConnectSettingRow; } });
43
+ var status_chip_js_1 = require("./status-chip.js");
44
+ Object.defineProperty(exports, "ConnectStatusChip", { enumerable: true, get: function () { return status_chip_js_1.ConnectStatusChip; } });
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConnectPageHeader = ConnectPageHeader;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ /**
6
+ * The title / description / action block a page opens with.
7
+ *
8
+ * The structure is the point, not the styling. The action is a peer of the
9
+ * **title**, not of the block, so nothing below the first line can move it -
10
+ * the common hand-rolled shape puts the action after the description, which
11
+ * makes the vertical position of a page's primary control a function of how
12
+ * long someone wrote the paragraph above it. On a phone that pushes it most of
13
+ * a thumb's reach down the screen.
14
+ *
15
+ * Mobile-first, and the two axes are separate decisions: the title row is a
16
+ * `justify-between` at every width (title left, action right, where a thumb
17
+ * expects it), while the description is clamped to two lines on a phone and
18
+ * released from `sm` up.
19
+ *
20
+ * An `h2`, because the document's `h1` is the application itself.
21
+ *
22
+ * No hooks and no state, so this renders from a Server Component - which is
23
+ * why it lives in `@cosmoops/connect-sdk/ui` rather than `/client`.
24
+ */
25
+ function ConnectPageHeader({ title, description, action, clamp = true, className, }) {
26
+ return ((0, jsx_runtime_1.jsxs)("header", { className: `flex items-start justify-between gap-3 ${className ?? ""}`, children: [(0, jsx_runtime_1.jsxs)("div", { className: "min-w-0 flex-1", children: [(0, jsx_runtime_1.jsx)("h2", { className: "flex items-center gap-2 text-lg font-semibold", children: title }), description && ((0, jsx_runtime_1.jsx)("p", { className: `mt-2 text-sm text-muted ${clamp ? "line-clamp-2 sm:line-clamp-none" : ""}`, children: description }))] }), action && (0, jsx_runtime_1.jsx)("div", { className: "shrink-0", children: action })] }));
27
+ }
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConnectPageShell = ConnectPageShell;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const page_header_js_1 = require("./page-header.js");
7
+ /**
8
+ * The frame a page opens with: its vertical rhythm, its header, and the
9
+ * suspended regions beneath.
10
+ *
11
+ * **The header is a prop, so it renders in the static shell** - above and
12
+ * outside every `<Suspense>` boundary. That is the one bug this component
13
+ * exists to make unrepresentable: rendering the header inside the async child
14
+ * makes a page's own title wait on a data read, and under Next's partial
15
+ * prefetching the route's shared App Shell loses it as well. There is no
16
+ * arrangement of `children` that reaches the header slot.
17
+ *
18
+ * A detail route is the honest exception, and it has its own door:
19
+ * `ConnectPageShell.Header` renders the identical header from inside an async
20
+ * child, for a page whose title is data it does not have yet. Pair it with a
21
+ * skeleton header in the fallback. The rule is that a *static* title must
22
+ * never be made to wait, not that every title can be static.
23
+ *
24
+ * `space-y-5` is the page gap, owned here so it has exactly one definition.
25
+ */
26
+ function ConnectPageShell({ title, description, action, clamp, above, children, }) {
27
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "space-y-5", children: [above, title !== undefined && ((0, jsx_runtime_1.jsx)(page_header_js_1.ConnectPageHeader, { title: title, description: description, action: action, clamp: clamp })), children] }));
28
+ }
29
+ /**
30
+ * The page header, for a detail route that can only name itself once its data
31
+ * has loaded. The same component the shell renders from its props, so the two
32
+ * cannot drift apart.
33
+ */
34
+ ConnectPageShell.Header = page_header_js_1.ConnectPageHeader;
35
+ /**
36
+ * A suspended region of the page.
37
+ *
38
+ * The donut: a static shell with async children behind their own boundaries,
39
+ * so a slow read costs its own section rather than the whole page. Give each
40
+ * independent region its own `Async` and they stream separately.
41
+ */
42
+ ConnectPageShell.Async = function ConnectPageShellAsync({ fallback = null, children, }) {
43
+ return (0, jsx_runtime_1.jsx)(react_1.Suspense, { fallback: fallback, children: children });
44
+ };
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConnectSection = ConnectSection;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("@heroui/react");
6
+ /**
7
+ * One titled block inside a page: a card, its heading, what it is for, and the
8
+ * one control that acts on it.
9
+ *
10
+ * The heading is an `h3` - `ConnectPageHeader` renders the page title as an
11
+ * `h2`, so a block *inside* a page is a level below it. Hand-rolled sections
12
+ * reliably use `h2`, which makes every settings block a sibling of the page it
13
+ * sits in and flattens the outline a screen reader announces.
14
+ *
15
+ * As in the page header, the action is a peer of the title rather than of the
16
+ * block, so its position does not move with the length of the description.
17
+ */
18
+ function ConnectSection({ title, description, action, titleId, className, bodyClassName, children, }) {
19
+ return ((0, jsx_runtime_1.jsxs)(react_1.Card, { className: className ?? "p-4 sm:p-5", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex flex-wrap items-start justify-between gap-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "min-w-0 flex-1", children: [(0, jsx_runtime_1.jsx)("h3", { id: titleId, className: "text-base font-semibold", children: title }), description && (0, jsx_runtime_1.jsx)("p", { className: "mt-1 text-sm text-muted", children: description })] }), action && (0, jsx_runtime_1.jsx)("div", { className: "shrink-0", children: action })] }), children && (0, jsx_runtime_1.jsx)("div", { className: bodyClassName ?? "mt-4 space-y-4", children: children })] }));
20
+ }
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConnectSettingRow = ConnectSettingRow;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ /**
6
+ * One labelled setting inside a `ConnectSection`.
7
+ *
8
+ * Stacks on a phone and goes side-by-side from `sm` up, rather than shrinking
9
+ * a two-column row until neither column is readable. The control keeps its
10
+ * intrinsic width so truncation lands on the description.
11
+ */
12
+ function ConnectSettingRow({ title, description, action, children, className, }) {
13
+ return ((0, jsx_runtime_1.jsxs)("div", { className: `flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between ${className ?? ""}`, children: [(0, jsx_runtime_1.jsxs)("div", { className: "min-w-0 flex-1", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-sm font-medium text-foreground", children: title }), description && (0, jsx_runtime_1.jsx)("p", { className: "mt-1 text-sm text-muted", children: description }), children && (0, jsx_runtime_1.jsx)("div", { className: "mt-3", children: children })] }), action && (0, jsx_runtime_1.jsx)("div", { className: "shrink-0 sm:pl-4", children: action })] }));
14
+ }
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConnectStatusChip = ConnectStatusChip;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("@heroui/react");
6
+ /**
7
+ * One status, rendered the way every status should be.
8
+ *
9
+ * Domain knowledge stays in the domain: each caller exports its own
10
+ * `ConnectStatusPresentation` next to the union it describes, and this renders
11
+ * it. A status whose *rendering* carries real logic - a scheduled cancellation
12
+ * outranking a running trial, say - keeps its own component and calls this for
13
+ * the final chip. A map cannot express precedence.
14
+ */
15
+ function ConnectStatusChip({ status, presentation, className, }) {
16
+ return ((0, jsx_runtime_1.jsx)(react_1.Chip, { color: presentation.tone[status], size: "sm", variant: "soft", className: className, children: presentation.label[status] }));
17
+ }
@@ -159,8 +159,8 @@ function checkEnv(root) {
159
159
  status: "fail",
160
160
  title: `${name} set`,
161
161
  detail: declaredEmpty
162
- ? `Declared but empty in .env.local. The value is in your application's Integration tab in Connect.`
163
- : `Not found in .env.local or the environment. Both values are in your application's Integration tab in Connect.`,
162
+ ? `Declared but empty in .env.local. The value is in your application's Connect client section in Connect.`
163
+ : `Not found in .env.local or the environment. Both values are in your application's Connect client section in Connect.`,
164
164
  };
165
165
  });
166
166
  }
@@ -173,7 +173,7 @@ function checkRedirectUri() {
173
173
  return {
174
174
  status: "warn",
175
175
  title: "Redirect URI registered in Connect",
176
- detail: "Not checkable from here. Confirm {your origin}/api/connect/callback is listed as a redirect URI in your application's Integration tab - including the deployed origin, not just http://localhost:3000.",
176
+ detail: "Not checkable from here. Confirm {your origin}/api/connect/callback is listed as a redirect URI in your application's Connect client section - including the deployed origin, not just http://localhost:3000.",
177
177
  };
178
178
  }
179
179
  const MARKER = { pass: " ok ", warn: " note ", fail: " FAIL " };
@@ -39,7 +39,7 @@ const PLANS_PAGE = `"use client";
39
39
  // with the other exports) whenever you want your own copy or layout.
40
40
  export { ConnectPlansPage as default } from "@cosmoops/connect-sdk/client";
41
41
  `;
42
- const ENV_TEMPLATE = `# CosmoOps Connect - values come from your application's Integration tab.
42
+ const ENV_TEMPLATE = `# CosmoOps Connect - values come from your application's Connect client section.
43
43
  CONNECT_API_KEY=
44
44
  CONNECT_CLIENT_ID=
45
45
  `;
@@ -106,7 +106,7 @@ export function runInit(argv = []) {
106
106
  console.log(` ${marker} ${relative(root, target)} - ${scaffold.description}`);
107
107
  }
108
108
  const env = ensureEnv(root);
109
- console.log(` ${env === "skipped" ? "exists " : env === "appended" ? "updated" : "created"} .env.local - fill in the two values from your application's Integration tab`);
109
+ console.log(` ${env === "skipped" ? "exists " : env === "appended" ? "updated" : "created"} .env.local - fill in the two values from your application's Connect client section`);
110
110
  // The two steps a scaffolder must not do for you: editing an existing layout
111
111
  // means parsing and rewriting JSX someone else wrote, and the redirect URI
112
112
  // lives in Connect, not in this repo.
@@ -132,7 +132,7 @@ Three things left, all one-time:
132
132
  Tailwind roots its content scan inside node_modules and skips your app.
133
133
 
134
134
  3. Register {your origin}/api/connect/callback as a redirect URI in your
135
- application's Integration tab in Connect.
135
+ application's Connect client section in Connect.
136
136
 
137
137
  No next.config.ts changes needed - not even transpilePackages: dist/ is
138
138
  already ES2022 with "use client" hoisted, so Turbopack and webpack consume it
@@ -36,12 +36,31 @@ function formatDate(iso) {
36
36
  export function useConnectAccessGate() {
37
37
  const { session, isLoading: sessionLoading } = useConnect();
38
38
  const { data, isLoading: entitlementsLoading } = useConnectEntitlements();
39
- if (sessionLoading || (session?.user && entitlementsLoading)) {
39
+ if (sessionLoading) {
40
40
  return { state: "loading", blocker: null, paymentRetry: null };
41
41
  }
42
42
  if (!session?.user) {
43
43
  return { state: "signed_out", blocker: null, paymentRetry: null };
44
44
  }
45
+ // Connect platform staff pass straight through, and this check has to come
46
+ // *before* the entitlements read below or the gate would hard-block them.
47
+ //
48
+ // Every state this gate can report is a fact about a workspace - suspended,
49
+ // no plan, wrong audience, awaiting approval, payment due. A staff session
50
+ // has no workspace at all (see `SessionAccess.staff`), so it has no subscription
51
+ // either, and `subscription_missing` would send an operator to your plan
52
+ // picker to buy a plan for a workspace that does not exist. "Nothing is
53
+ // blocking this session" is the truthful answer, not a lenient one.
54
+ //
55
+ // The entitlements request is still issued - hooks cannot be called
56
+ // conditionally - and the bridge answers it `403 no_workspace`. That costs
57
+ // one failed request per staff page load, and its result is ignored here.
58
+ if (session.staff) {
59
+ return { state: "clear", blocker: null, paymentRetry: null };
60
+ }
61
+ if (entitlementsLoading) {
62
+ return { state: "loading", blocker: null, paymentRetry: null };
63
+ }
45
64
  if (!data) {
46
65
  // Signed in, not loading, yet nothing came back - a failed read (see
47
66
  // `error` on `useConnectEntitlements`). Fail open rather than hard-block