@cosmoops/connect-sdk 0.9.0 → 0.16.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.
- package/CHANGELOG.md +566 -0
- package/README.md +113 -42
- package/dist/cjs/cli/doctor.js +3 -3
- package/dist/cjs/cli/init.js +3 -3
- package/dist/cjs/client/components/access-gate.js +20 -1
- package/dist/cjs/client/components/ai-screens.js +73 -0
- package/dist/cjs/client/components/ai-widgets.js +37 -0
- package/dist/cjs/client/components/app-shell.js +75 -41
- package/dist/cjs/client/components/apps-grid.js +3 -2
- package/dist/cjs/client/components/ask-ai.js +50 -5
- package/dist/cjs/client/components/confirm-dialog.js +40 -0
- package/dist/cjs/client/components/cursor-pagination.js +22 -0
- package/dist/cjs/client/components/filter-tabs.js +24 -0
- package/dist/cjs/client/components/grievance-notice.js +30 -0
- package/dist/cjs/client/components/link-button.js +17 -10
- package/dist/cjs/client/components/logo.js +4 -3
- package/dist/cjs/client/components/nav.js +41 -0
- package/dist/cjs/client/components/plan-pricing.js +27 -0
- package/dist/cjs/client/components/plans.js +1 -1
- package/dist/cjs/client/components/platform-gate.js +48 -0
- package/dist/cjs/client/components/shell-context.js +149 -0
- package/dist/cjs/client/components/side-nav.js +49 -23
- package/dist/cjs/client/components/sidebar.js +127 -0
- package/dist/cjs/client/components/subscribe-dialog.js +2 -2
- package/dist/cjs/client/components/subscription.js +1 -1
- package/dist/cjs/client/components/user-menu.js +96 -37
- package/dist/cjs/client/components/workspace-switcher.js +100 -0
- package/dist/cjs/client/index.js +72 -2
- package/dist/cjs/client/provider.js +57 -2
- package/dist/cjs/client/use-checkout.js +9 -5
- package/dist/cjs/errors.js +1 -1
- package/dist/cjs/index.js +16 -1
- package/dist/cjs/mcp/index.js +90 -0
- package/dist/cjs/mcp/tools.js +369 -0
- package/dist/cjs/next/index.js +2 -2
- package/dist/cjs/server/client.js +25 -2
- package/dist/cjs/server/handler.js +203 -18
- package/dist/cjs/server/http.js +6 -2
- package/dist/cjs/server/resources/acting-as.js +16 -0
- package/dist/cjs/server/resources/ai.js +22 -2
- package/dist/cjs/server/resources/data.js +374 -0
- package/dist/cjs/server/resources/identity.js +7 -3
- package/dist/cjs/server/resources/kv.js +84 -0
- package/dist/cjs/server/resources/platform.js +43 -0
- package/dist/cjs/server/resources/sessions.js +1 -1
- package/dist/cjs/server/resources/workspaces.js +74 -0
- package/dist/cjs/store-schema.js +66 -0
- package/dist/cjs/store.js +152 -0
- package/dist/cjs/ui/empty-state.js +28 -0
- package/dist/cjs/ui/index.js +44 -0
- package/dist/cjs/ui/page-header.js +27 -0
- package/dist/cjs/ui/page-shell.js +44 -0
- package/dist/cjs/ui/section.js +20 -0
- package/dist/cjs/ui/setting-row.js +14 -0
- package/dist/cjs/ui/status-chip.js +17 -0
- package/dist/esm/cli/doctor.js +3 -3
- package/dist/esm/cli/init.js +3 -3
- package/dist/esm/client/components/access-gate.js +20 -1
- package/dist/esm/client/components/ai-screens.js +69 -0
- package/dist/esm/client/components/ai-widgets.js +37 -1
- package/dist/esm/client/components/app-shell.js +81 -47
- package/dist/esm/client/components/apps-grid.js +3 -2
- package/dist/esm/client/components/ask-ai.js +51 -6
- package/dist/esm/client/components/confirm-dialog.js +37 -0
- package/dist/esm/client/components/cursor-pagination.js +19 -0
- package/dist/esm/client/components/filter-tabs.js +21 -0
- package/dist/esm/client/components/grievance-notice.js +27 -0
- package/dist/esm/client/components/link-button.js +17 -10
- package/dist/esm/client/components/logo.js +4 -3
- package/dist/esm/client/components/nav.js +40 -0
- package/dist/esm/client/components/plan-pricing.js +25 -0
- package/dist/esm/client/components/plans.js +2 -2
- package/dist/esm/client/components/platform-gate.js +45 -0
- package/dist/esm/client/components/shell-context.js +141 -0
- package/dist/esm/client/components/side-nav.js +48 -21
- package/dist/esm/client/components/sidebar.js +120 -0
- package/dist/esm/client/components/subscribe-dialog.js +3 -3
- package/dist/esm/client/components/subscription.js +2 -2
- package/dist/esm/client/components/user-menu.js +95 -38
- package/dist/esm/client/components/workspace-switcher.js +97 -0
- package/dist/esm/client/index.js +47 -4
- package/dist/esm/client/provider.js +53 -2
- package/dist/esm/client/use-checkout.js +9 -5
- package/dist/esm/errors.js +1 -1
- package/dist/esm/index.js +4 -0
- package/dist/esm/mcp/index.js +85 -0
- package/dist/esm/mcp/tools.js +366 -0
- package/dist/esm/next/index.js +2 -2
- package/dist/esm/server/client.js +25 -2
- package/dist/esm/server/handler.js +203 -18
- package/dist/esm/server/http.js +6 -2
- package/dist/esm/server/resources/acting-as.js +13 -0
- package/dist/esm/server/resources/ai.js +22 -2
- package/dist/esm/server/resources/data.js +369 -0
- package/dist/esm/server/resources/identity.js +7 -3
- package/dist/esm/server/resources/kv.js +80 -0
- package/dist/esm/server/resources/platform.js +39 -0
- package/dist/esm/server/resources/sessions.js +1 -1
- package/dist/esm/server/resources/workspaces.js +70 -0
- package/dist/esm/store-schema.js +62 -0
- package/dist/esm/store.js +141 -0
- package/dist/esm/ui/empty-state.js +25 -0
- package/dist/esm/ui/index.js +34 -0
- package/dist/esm/ui/page-header.js +24 -0
- package/dist/esm/ui/page-shell.js +41 -0
- package/dist/esm/ui/section.js +17 -0
- package/dist/esm/ui/setting-row.js +11 -0
- package/dist/esm/ui/status-chip.js +14 -0
- package/dist/types/client/components/ai-screens.d.ts +25 -0
- package/dist/types/client/components/ai-widgets.d.ts +34 -1
- package/dist/types/client/components/app-shell.d.ts +65 -27
- package/dist/types/client/components/apps-grid.d.ts +11 -2
- package/dist/types/client/components/ask-ai.d.ts +47 -1
- package/dist/types/client/components/confirm-dialog.d.ts +34 -0
- package/dist/types/client/components/cursor-pagination.d.ts +27 -0
- package/dist/types/client/components/filter-tabs.d.ts +26 -0
- package/dist/types/client/components/grievance-notice.d.ts +31 -0
- package/dist/types/client/components/link-button.d.ts +32 -9
- package/dist/types/client/components/logo.d.ts +18 -2
- package/dist/types/client/components/nav.d.ts +24 -0
- package/dist/types/client/components/plan-pricing.d.ts +10 -0
- package/dist/types/client/components/platform-gate.d.ts +53 -0
- package/dist/types/client/components/shell-context.d.ts +116 -0
- package/dist/types/client/components/side-nav.d.ts +27 -13
- package/dist/types/client/components/sidebar.d.ts +95 -0
- package/dist/types/client/components/user-menu.d.ts +68 -22
- package/dist/types/client/components/workspace-switcher.d.ts +49 -0
- package/dist/types/client/index.d.ts +29 -5
- package/dist/types/client/provider.d.ts +109 -6
- package/dist/types/client/use-checkout.d.ts +8 -4
- package/dist/types/errors.d.ts +1 -1
- package/dist/types/index.d.ts +6 -0
- package/dist/types/mcp/index.d.ts +74 -0
- package/dist/types/mcp/tools.d.ts +107 -0
- package/dist/types/next/index.d.ts +1 -1
- package/dist/types/server/client.d.ts +25 -2
- package/dist/types/server/http.d.ts +8 -2
- package/dist/types/server/index.d.ts +4 -0
- package/dist/types/server/resources/acting-as.d.ts +11 -0
- package/dist/types/server/resources/ai.d.ts +20 -2
- package/dist/types/server/resources/data.d.ts +258 -0
- package/dist/types/server/resources/kv.d.ts +63 -0
- package/dist/types/server/resources/platform.d.ts +32 -0
- package/dist/types/server/resources/workspaces.d.ts +55 -0
- package/dist/types/store-schema.d.ts +52 -0
- package/dist/types/store.d.ts +96 -0
- package/dist/types/types.d.ts +369 -16
- package/dist/types/ui/empty-state.d.ts +34 -0
- package/dist/types/ui/index.d.ts +40 -0
- package/dist/types/ui/page-header.d.ts +34 -0
- package/dist/types/ui/page-shell.d.ts +39 -0
- package/dist/types/ui/section.d.ts +26 -0
- package/dist/types/ui/setting-row.d.ts +18 -0
- package/dist/types/ui/status-chip.d.ts +36 -0
- package/package.json +15 -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
|
+
}
|
package/dist/esm/cli/doctor.js
CHANGED
|
@@ -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
|
|
163
|
-
: `Not found in .env.local or the environment. Both values are in your application's
|
|
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
|
|
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 " };
|
package/dist/esm/cli/init.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Matching a live path against the screens your application declared.
|
|
3
|
+
*
|
|
4
|
+
* The same rule Connect uses for its own routes, and it has to be here too
|
|
5
|
+
* because only this side knows what your app's routes are: a dynamic segment
|
|
6
|
+
* matches exactly one path segment, and where several patterns fit, the most
|
|
7
|
+
* *literal* wins - so `/deals/new` resolves to itself rather than to
|
|
8
|
+
* `/deals/[dealId]` merely because that also fits.
|
|
9
|
+
*
|
|
10
|
+
* Plain functions with no React and no imports beyond the type, so the panel,
|
|
11
|
+
* the action links and any host that wants to reuse the matching can share one
|
|
12
|
+
* implementation.
|
|
13
|
+
*/
|
|
14
|
+
/** Trims the query string, the hash and any trailing slash. */
|
|
15
|
+
function normalise(path) {
|
|
16
|
+
const withoutQuery = path.split(/[?#]/)[0] ?? "";
|
|
17
|
+
return withoutQuery.length > 1 ? withoutQuery.replace(/\/+$/, "") : withoutQuery;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The declared screen a live path is on, or `null` if none matches.
|
|
21
|
+
*
|
|
22
|
+
* `null` is a normal outcome, not an error: an application need only describe
|
|
23
|
+
* the screens it wants the assistant to know about, and an undescribed one
|
|
24
|
+
* simply costs the assistant its orientation rather than costing the user an
|
|
25
|
+
* answer.
|
|
26
|
+
*/
|
|
27
|
+
export function matchConnectScreen(screens, path) {
|
|
28
|
+
const segments = normalise(path).split("/").filter(Boolean);
|
|
29
|
+
let best = null;
|
|
30
|
+
for (const screen of screens) {
|
|
31
|
+
const pattern = normalise(screen.path).split("/").filter(Boolean);
|
|
32
|
+
if (pattern.length !== segments.length)
|
|
33
|
+
continue;
|
|
34
|
+
let literals = 0;
|
|
35
|
+
let matches = true;
|
|
36
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
37
|
+
const part = pattern[i];
|
|
38
|
+
if (part.startsWith("[") && part.endsWith("]"))
|
|
39
|
+
continue;
|
|
40
|
+
if (part !== segments[i]) {
|
|
41
|
+
matches = false;
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
literals++;
|
|
45
|
+
}
|
|
46
|
+
if (matches && (!best || literals > best.literals))
|
|
47
|
+
best = { screen, literals };
|
|
48
|
+
}
|
|
49
|
+
return best?.screen ?? null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Whether an href the model produced may be rendered as a link.
|
|
53
|
+
*
|
|
54
|
+
* Same reasoning as Connect's own `showActions` check, and the same conclusion:
|
|
55
|
+
* an assistant that can emit an arbitrary URL is a redirect the model can be
|
|
56
|
+
* talked into, and retrieved content shares a context window with its
|
|
57
|
+
* tool-calling authority. So a link is drawn only when it resolves to a screen
|
|
58
|
+
* *your application declared* - which makes an external destination structurally
|
|
59
|
+
* impossible, since no route pattern has a scheme or a host.
|
|
60
|
+
*
|
|
61
|
+
* The protocol-relative form is refused separately: `//evil.example.com` has no
|
|
62
|
+
* scheme for a path matcher to trip over, and would otherwise reach an anchor
|
|
63
|
+
* as an absolute destination.
|
|
64
|
+
*/
|
|
65
|
+
export function isLinkableConnectScreen(screens, href) {
|
|
66
|
+
if (!href.startsWith("/") || href.startsWith("//"))
|
|
67
|
+
return false;
|
|
68
|
+
return matchConnectScreen(screens, href) !== null;
|
|
69
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { useMemo, useState } from "react";
|
|
3
|
+
import { Fragment, useMemo, useState } from "react";
|
|
4
4
|
import { Button } from "@heroui/react";
|
|
5
5
|
import { ArrowDown, ArrowUp, ChevronsUpDown, Download, ExternalLink, FileText, Minus, TrendingDown, TrendingUp } from "lucide-react";
|
|
6
|
+
import { isLinkableConnectScreen } from "./ai-screens.js";
|
|
6
7
|
import { AI_EMPTY_VALUE, AI_TONE_CHIP, AI_TONE_DOT, aiStatusTone, formatAiValue, isNumericAiFormat, } from "./ai-format.js";
|
|
7
8
|
/**
|
|
8
9
|
* What Ask AI draws.
|
|
@@ -221,3 +222,38 @@ export function AiSources({ sources }) {
|
|
|
221
222
|
return (_jsx("li", { children: source.uri ? (_jsxs("a", { href: source.uri, target: "_blank", rel: "noopener noreferrer", className: "flex gap-1.5 rounded-sm hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus", children: [label, _jsx(ExternalLink, { className: "mt-0.5 size-3 shrink-0 text-muted", "aria-hidden": true })] })) : (_jsx("span", { className: "flex gap-1.5", children: label })) }, `${source.uri ?? source.title}-${index}`));
|
|
222
223
|
}) })] }));
|
|
223
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* Where to go next, as links into *your* application.
|
|
227
|
+
*
|
|
228
|
+
* ## Why links and not navigation
|
|
229
|
+
*
|
|
230
|
+
* The obvious version of "the assistant can act" moves the user. It is also the
|
|
231
|
+
* version that breaks the property the whole design rests on: nothing happens
|
|
232
|
+
* without a click. Retrieved content shares a context window with the model's
|
|
233
|
+
* tool-calling authority, so an auto-navigate is a redirect primitive that
|
|
234
|
+
* content can reach. A link cannot be, and loses nothing - the user was going
|
|
235
|
+
* to click something anyway.
|
|
236
|
+
*
|
|
237
|
+
* ## Why every href is checked against your declared screens
|
|
238
|
+
*
|
|
239
|
+
* The model can emit any string. Only your application knows which strings are
|
|
240
|
+
* real routes, which is why `screens` is a prop rather than something Connect
|
|
241
|
+
* could supply. A link that does not resolve is *dropped* rather than rendered
|
|
242
|
+
* broken - the prose around it still stands, so a missing link reads as terse
|
|
243
|
+
* rather than wrong.
|
|
244
|
+
*
|
|
245
|
+
* `renderLink` is how this stays router-agnostic. Given one, a link is your
|
|
246
|
+
* framework's - a Next `<Link>`, a TanStack `<Link>` - and navigation is a
|
|
247
|
+
* client transition. Without one it falls back to an anchor, which works
|
|
248
|
+
* everywhere and costs a document load.
|
|
249
|
+
*/
|
|
250
|
+
export function AiActions({ title, actions, screens, renderLink, }) {
|
|
251
|
+
const resolved = actions.filter((action) => isLinkableConnectScreen(screens, action.href));
|
|
252
|
+
if (resolved.length === 0)
|
|
253
|
+
return null;
|
|
254
|
+
const className = "group flex items-center gap-2 rounded-lg border border-separator bg-surface px-3 py-2 no-underline transition-colors hover:border-accent";
|
|
255
|
+
return (_jsxs("div", { className: "my-3", children: [title ? _jsx("p", { className: "mb-1.5 text-xs font-medium text-muted", children: title }) : null, _jsx("div", { className: "flex flex-col gap-1.5", children: resolved.map((action) => {
|
|
256
|
+
const body = (_jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "block truncate text-xs font-medium text-foreground", children: action.label }), action.detail ? (_jsx("span", { className: "block truncate text-[11px] text-muted", children: action.detail })) : null] }));
|
|
257
|
+
return (_jsx(Fragment, { children: renderLink ? (renderLink({ href: action.href, className, children: body })) : (_jsx("a", { href: action.href, className: className, children: body })) }, action.href));
|
|
258
|
+
}) })] }));
|
|
259
|
+
}
|