@agent-native/core 0.128.1 → 0.128.3
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/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +12 -0
- package/corpus/core/package.json +2 -1
- package/corpus/core/src/server/email-template.ts +33 -0
- package/corpus/templates/chat/_gitignore +2 -0
- package/corpus/templates/clips/AGENTS.md +6 -1
- package/corpus/templates/clips/actions/complete-transactional-email-summary.ts +56 -0
- package/corpus/templates/clips/actions/import-loom-recording.ts +258 -76
- package/corpus/templates/clips/actions/lib/direct-video.ts +96 -0
- package/corpus/templates/clips/actions/lib/loom-import-job.ts +53 -2
- package/corpus/templates/clips/actions/lib/loom-video.ts +2 -62
- package/corpus/templates/clips/actions/lib/video-download-limits.ts +62 -0
- package/corpus/templates/clips/actions/list-transactional-email-ai-requests.ts +249 -0
- package/corpus/templates/clips/app/components/library/library-grid.tsx +65 -0
- package/corpus/templates/clips/app/components/library/library-layout.tsx +55 -0
- package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +28 -126
- package/corpus/templates/clips/app/global.css +23 -0
- package/corpus/templates/clips/app/hooks/use-transactional-email-bridge.ts +98 -0
- package/corpus/templates/clips/app/i18n/en-US.ts +20 -0
- package/corpus/templates/clips/app/routes/_app.tsx +2 -0
- package/corpus/templates/clips/app/routes/import.tsx +396 -0
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +8 -6
- package/corpus/templates/clips/app/routes/record.tsx +13 -71
- package/corpus/templates/clips/changelog/2026-07-25-two-clip-emails-now-include-a-concise-summary-generated-secu.md +6 -0
- package/corpus/templates/clips/data/clips-transactional-emails/config.json +3 -0
- package/corpus/templates/clips/server/jobs/transactional-emails.ts +1034 -0
- package/corpus/templates/clips/server/lib/transactional-email-store.ts +865 -0
- package/corpus/templates/clips/server/lib/transactional-email-templates.ts +270 -0
- package/corpus/templates/clips/server/plugins/calendar-jobs.ts +2 -0
- package/corpus/templates/clips/server/routes/api/view-event.post.ts +59 -2
- package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +8 -2
- package/corpus/templates/content/AGENTS.md +8 -0
- package/corpus/templates/content/actions/_content-database-personal-view.ts +28 -3
- package/corpus/templates/content/actions/_property-utils.ts +32 -2
- package/corpus/templates/content/actions/configure-document-property.ts +12 -2
- package/corpus/templates/content/actions/delete-document-property.ts +13 -3
- package/corpus/templates/content/actions/duplicate-document-property.ts +13 -3
- package/corpus/templates/content/actions/list-content-spaces.ts +5 -2
- package/corpus/templates/content/actions/list-document-properties.ts +12 -3
- package/corpus/templates/content/actions/move-database-item.ts +74 -55
- package/corpus/templates/content/actions/reorder-document-property.ts +20 -4
- package/corpus/templates/content/actions/set-document-property.ts +10 -1
- package/corpus/templates/content/actions/update-content-database-personal-view.ts +7 -0
- package/corpus/templates/content/app/components/editor/DocumentBlockFields.tsx +31 -21
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +30 -3
- package/corpus/templates/content/app/components/editor/DocumentInfoPanel.tsx +8 -1
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +72 -20
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +79 -10
- package/corpus/templates/content/app/components/editor/database/TimelineView.tsx +8 -1
- package/corpus/templates/content/app/components/editor/database/settings.tsx +6 -0
- package/corpus/templates/content/app/components/editor/database/sidebar.tsx +246 -26
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +491 -197
- package/corpus/templates/content/app/components/sidebar/DocumentTreeItem.tsx +3 -3
- package/corpus/templates/content/app/components/sidebar/sidebar-reorder.tsx +409 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +117 -3
- package/corpus/templates/content/app/hooks/use-content-spaces.ts +1 -0
- package/corpus/templates/content/app/hooks/use-document-properties.ts +62 -18
- package/corpus/templates/content/app/hooks/use-documents.ts +13 -5
- package/corpus/templates/content/app/i18n-data.ts +261 -3
- package/corpus/templates/content/app/routes/_app.page.$id.tsx +9 -2
- package/corpus/templates/content/changelog/2026-07-24-database-page-properties-keep-their-context.md +6 -0
- package/corpus/templates/content/changelog/2026-07-26-sidebar-references-can-now-be-personally-reordered.md +6 -0
- package/corpus/templates/content/docs/solutions/2026-07-24-database-property-context-switch-diagnosis.md +249 -0
- package/corpus/templates/content/docs/solutions/content-sidebar-order-shape.md +389 -0
- package/corpus/templates/content/shared/api.ts +28 -0
- package/corpus/toolkit/CHANGELOG.md +6 -0
- package/corpus/toolkit/package.json +1 -1
- package/corpus/toolkit/src/editor/DragHandle.ts +63 -9
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/server/email-template.d.ts +7 -0
- package/dist/server/email-template.d.ts.map +1 -1
- package/dist/server/email-template.js +24 -0
- package/dist/server/email-template.js.map +1 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/templates/chat/_gitignore +2 -0
- package/package.json +3 -2
- package/src/server/email-template.ts +33 -0
- package/src/templates/chat/_gitignore +2 -0
- package/corpus/core/src/cli/sync-builder-starter-manifest.ts +0 -631
- package/dist/cli/sync-builder-starter-manifest.d.ts +0 -79
- package/dist/cli/sync-builder-starter-manifest.d.ts.map +0 -1
- package/dist/cli/sync-builder-starter-manifest.js +0 -426
- package/dist/cli/sync-builder-starter-manifest.js.map +0 -1
- package/src/cli/sync-builder-starter-manifest.ts +0 -631
|
@@ -199,6 +199,7 @@ export interface DocumentPropertiesResponse {
|
|
|
199
199
|
export interface ConfigureDocumentPropertyRequest {
|
|
200
200
|
id?: string;
|
|
201
201
|
documentId: string;
|
|
202
|
+
databaseId: string;
|
|
202
203
|
name: string;
|
|
203
204
|
type: DocumentPropertyType;
|
|
204
205
|
description?: string;
|
|
@@ -208,22 +209,26 @@ export interface ConfigureDocumentPropertyRequest {
|
|
|
208
209
|
|
|
209
210
|
export interface SetDocumentPropertyRequest {
|
|
210
211
|
documentId: string;
|
|
212
|
+
databaseId: string;
|
|
211
213
|
propertyId: string;
|
|
212
214
|
value: DocumentPropertyValue;
|
|
213
215
|
}
|
|
214
216
|
|
|
215
217
|
export interface DuplicateDocumentPropertyRequest {
|
|
216
218
|
documentId: string;
|
|
219
|
+
databaseId: string;
|
|
217
220
|
propertyId: string;
|
|
218
221
|
}
|
|
219
222
|
|
|
220
223
|
export interface DeleteDocumentPropertyRequest {
|
|
221
224
|
documentId: string;
|
|
225
|
+
databaseId: string;
|
|
222
226
|
propertyId: string;
|
|
223
227
|
}
|
|
224
228
|
|
|
225
229
|
export interface ReorderDocumentPropertyRequest {
|
|
226
230
|
documentId: string;
|
|
231
|
+
databaseId: string;
|
|
227
232
|
propertyId: string;
|
|
228
233
|
targetPropertyId: string;
|
|
229
234
|
position?: "before" | "after";
|
|
@@ -343,6 +348,25 @@ export interface ContentDatabaseViewConfig {
|
|
|
343
348
|
|
|
344
349
|
export const CONTENT_DATABASE_PERSONAL_VIEW_OVERRIDES_VERSION = 2;
|
|
345
350
|
|
|
351
|
+
export type ContentSidebarOrderMode =
|
|
352
|
+
| "custom"
|
|
353
|
+
| "last_edited"
|
|
354
|
+
| "name"
|
|
355
|
+
| "created";
|
|
356
|
+
|
|
357
|
+
export interface OrderedMembershipRef {
|
|
358
|
+
databaseId: string;
|
|
359
|
+
itemId: string;
|
|
360
|
+
documentId: string;
|
|
361
|
+
position: number;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export interface ContentSidebarViewOrder {
|
|
365
|
+
mode: ContentSidebarOrderMode;
|
|
366
|
+
/** Retained while a computed mode is active so Custom can be restored. */
|
|
367
|
+
itemIds: string[];
|
|
368
|
+
}
|
|
369
|
+
|
|
346
370
|
export interface ContentDatabasePersonalViewOverrides {
|
|
347
371
|
version: number;
|
|
348
372
|
activeViewId?: string;
|
|
@@ -351,6 +375,8 @@ export interface ContentDatabasePersonalViewOverrides {
|
|
|
351
375
|
sorts: ContentDatabaseSort[];
|
|
352
376
|
filters: ContentDatabaseFilter[];
|
|
353
377
|
filterMode: ContentDatabaseFilterMode;
|
|
378
|
+
/** Personal-only ordering for this database's Files sidebar. */
|
|
379
|
+
sidebarOrder?: ContentSidebarViewOrder;
|
|
354
380
|
}>;
|
|
355
381
|
}
|
|
356
382
|
|
|
@@ -845,6 +871,8 @@ export interface DatabaseItemsBatchRequest {
|
|
|
845
871
|
}
|
|
846
872
|
|
|
847
873
|
export interface MoveDatabaseItemRequest {
|
|
874
|
+
/** Required with itemId for unambiguous membership moves. */
|
|
875
|
+
databaseId?: string;
|
|
848
876
|
itemId?: string;
|
|
849
877
|
documentId?: string;
|
|
850
878
|
position: number;
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @agent-native/toolkit
|
|
2
2
|
|
|
3
|
+
## 0.10.10
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 16a9d1a: Keep editor block drag previews aligned with the point where the block was grabbed, then clear incidental selection and focus after a successful drop.
|
|
8
|
+
|
|
3
9
|
## 0.10.9
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
|
@@ -148,6 +148,8 @@ type DragSession = {
|
|
|
148
148
|
sourceNodeSize: number;
|
|
149
149
|
startX: number;
|
|
150
150
|
startY: number;
|
|
151
|
+
pointerOffsetX: number;
|
|
152
|
+
pointerOffsetY: number;
|
|
151
153
|
dragging: boolean;
|
|
152
154
|
preview: HTMLElement | null;
|
|
153
155
|
dropLine: HTMLElement | null;
|
|
@@ -194,6 +196,47 @@ let activeHoverRegistration: DragHandleRegistration | null = null;
|
|
|
194
196
|
const clamp = (value: number, min: number, max: number) =>
|
|
195
197
|
Math.min(Math.max(value, min), max);
|
|
196
198
|
|
|
199
|
+
export const dragPreviewTransform = ({
|
|
200
|
+
clientX,
|
|
201
|
+
clientY,
|
|
202
|
+
pointerOffsetX,
|
|
203
|
+
pointerOffsetY,
|
|
204
|
+
}: {
|
|
205
|
+
clientX: number;
|
|
206
|
+
clientY: number;
|
|
207
|
+
pointerOffsetX: number;
|
|
208
|
+
pointerOffsetY: number;
|
|
209
|
+
}) =>
|
|
210
|
+
`translate3d(${clientX - pointerOffsetX}px, ${clientY - pointerOffsetY}px, 0)`;
|
|
211
|
+
|
|
212
|
+
const collapseAndBlurEditorAfterDrop = (
|
|
213
|
+
view: EditorView,
|
|
214
|
+
preferredPos: number,
|
|
215
|
+
) => {
|
|
216
|
+
const pos = clamp(preferredPos, 0, view.state.doc.content.size);
|
|
217
|
+
const selection = TextSelection.near(view.state.doc.resolve(pos), 1);
|
|
218
|
+
if (!view.state.selection.eq(selection)) {
|
|
219
|
+
view.dispatch(view.state.tr.setSelection(selection));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const nativeSelection = window.getSelection();
|
|
223
|
+
const selectionBelongsToEditor = [
|
|
224
|
+
nativeSelection?.anchorNode,
|
|
225
|
+
nativeSelection?.focusNode,
|
|
226
|
+
].some((node) => node && (node === view.dom || view.dom.contains(node)));
|
|
227
|
+
if (selectionBelongsToEditor) {
|
|
228
|
+
nativeSelection?.removeAllRanges();
|
|
229
|
+
}
|
|
230
|
+
const activeElement = document.activeElement;
|
|
231
|
+
if (
|
|
232
|
+
activeElement instanceof HTMLElement &&
|
|
233
|
+
(activeElement === view.dom || view.dom.contains(activeElement))
|
|
234
|
+
) {
|
|
235
|
+
activeElement.blur();
|
|
236
|
+
}
|
|
237
|
+
view.dom.blur();
|
|
238
|
+
};
|
|
239
|
+
|
|
197
240
|
const editorArea = (registration: DragHandleRegistration) => {
|
|
198
241
|
const rect = registration.view.dom.getBoundingClientRect();
|
|
199
242
|
return rect.width * rect.height;
|
|
@@ -977,7 +1020,12 @@ export const DragHandle = Extension.create<DragHandleOptions>({
|
|
|
977
1020
|
) => {
|
|
978
1021
|
if (!session.preview) return;
|
|
979
1022
|
|
|
980
|
-
session.preview.style.transform =
|
|
1023
|
+
session.preview.style.transform = dragPreviewTransform({
|
|
1024
|
+
clientX,
|
|
1025
|
+
clientY,
|
|
1026
|
+
pointerOffsetX: session.pointerOffsetX,
|
|
1027
|
+
pointerOffsetY: session.pointerOffsetY,
|
|
1028
|
+
});
|
|
981
1029
|
};
|
|
982
1030
|
|
|
983
1031
|
const updateDropLine = (
|
|
@@ -1174,6 +1222,7 @@ export const DragHandle = Extension.create<DragHandleOptions>({
|
|
|
1174
1222
|
) {
|
|
1175
1223
|
const sourceNode = session.view.state.doc.nodeAt(sourceStart);
|
|
1176
1224
|
if (sourceNode) {
|
|
1225
|
+
let dropCommitted = false;
|
|
1177
1226
|
const sourceRegistration = registrationForView(session.view);
|
|
1178
1227
|
const transferData = sourceRegistration?.getDragTransferData?.({
|
|
1179
1228
|
view: session.view,
|
|
@@ -1198,7 +1247,7 @@ export const DragHandle = Extension.create<DragHandleOptions>({
|
|
|
1198
1247
|
false);
|
|
1199
1248
|
|
|
1200
1249
|
if (handled) {
|
|
1201
|
-
|
|
1250
|
+
dropCommitted = true;
|
|
1202
1251
|
} else if (target.view === session.view) {
|
|
1203
1252
|
const insertPos =
|
|
1204
1253
|
dropPos > sourceStart ? dropPos - sourceNode.nodeSize : dropPos;
|
|
@@ -1206,10 +1255,8 @@ export const DragHandle = Extension.create<DragHandleOptions>({
|
|
|
1206
1255
|
.delete(sourceStart, sourceEnd)
|
|
1207
1256
|
.insert(insertPos, sourceNode);
|
|
1208
1257
|
|
|
1209
|
-
tr.setSelection(NodeSelection.create(tr.doc, insertPos));
|
|
1210
|
-
|
|
1211
1258
|
session.view.dispatch(tr.scrollIntoView());
|
|
1212
|
-
|
|
1259
|
+
dropCommitted = true;
|
|
1213
1260
|
} else {
|
|
1214
1261
|
try {
|
|
1215
1262
|
const targetNode = target.view.state.schema.nodeFromJSON(
|
|
@@ -1225,9 +1272,6 @@ export const DragHandle = Extension.create<DragHandleOptions>({
|
|
|
1225
1272
|
dropPos,
|
|
1226
1273
|
targetNode,
|
|
1227
1274
|
);
|
|
1228
|
-
insertTr.setSelection(
|
|
1229
|
-
NodeSelection.create(insertTr.doc, dropPos),
|
|
1230
|
-
);
|
|
1231
1275
|
target.view.dispatch(insertTr.scrollIntoView());
|
|
1232
1276
|
|
|
1233
1277
|
const deleteTr = session.view.state.tr.delete(
|
|
@@ -1235,12 +1279,19 @@ export const DragHandle = Extension.create<DragHandleOptions>({
|
|
|
1235
1279
|
sourceEnd,
|
|
1236
1280
|
);
|
|
1237
1281
|
session.view.dispatch(deleteTr);
|
|
1238
|
-
|
|
1282
|
+
dropCommitted = true;
|
|
1239
1283
|
} catch {
|
|
1240
1284
|
// If the target schema cannot accept this node, leave the
|
|
1241
1285
|
// source document untouched.
|
|
1242
1286
|
}
|
|
1243
1287
|
}
|
|
1288
|
+
|
|
1289
|
+
if (dropCommitted) {
|
|
1290
|
+
collapseAndBlurEditorAfterDrop(target.view, dropPos);
|
|
1291
|
+
if (target.view !== session.view) {
|
|
1292
|
+
collapseAndBlurEditorAfterDrop(session.view, sourceStart);
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1244
1295
|
}
|
|
1245
1296
|
}
|
|
1246
1297
|
} else if (commit && !session.dragging && event) {
|
|
@@ -1356,6 +1407,7 @@ export const DragHandle = Extension.create<DragHandleOptions>({
|
|
|
1356
1407
|
if (!sourceNode) return;
|
|
1357
1408
|
|
|
1358
1409
|
e.preventDefault();
|
|
1410
|
+
const sourceRect = currentBlock.getBoundingClientRect();
|
|
1359
1411
|
dragSession = {
|
|
1360
1412
|
view: editorView,
|
|
1361
1413
|
sourceBlock: currentBlock,
|
|
@@ -1363,6 +1415,8 @@ export const DragHandle = Extension.create<DragHandleOptions>({
|
|
|
1363
1415
|
sourceNodeSize: sourceNode.nodeSize,
|
|
1364
1416
|
startX: e.clientX,
|
|
1365
1417
|
startY: e.clientY,
|
|
1418
|
+
pointerOffsetX: e.clientX - sourceRect.left,
|
|
1419
|
+
pointerOffsetY: e.clientY - sourceRect.top,
|
|
1366
1420
|
dragging: false,
|
|
1367
1421
|
preview: null,
|
|
1368
1422
|
dropLine: null,
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* Body: { json: any, fieldName?: string, type?: "map"|"array", requestSource?: string }
|
|
14
14
|
*/
|
|
15
15
|
export declare const postCollabJson: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
16
|
-
error: string;
|
|
17
16
|
ok?: undefined;
|
|
17
|
+
error: string;
|
|
18
18
|
} | {
|
|
19
19
|
error?: undefined;
|
|
20
20
|
ok: boolean;
|
|
@@ -22,6 +22,11 @@ export interface EmailCta {
|
|
|
22
22
|
label: string;
|
|
23
23
|
url: string;
|
|
24
24
|
}
|
|
25
|
+
export interface EmailLinkBlock {
|
|
26
|
+
intro: string;
|
|
27
|
+
url: string;
|
|
28
|
+
placement?: "before-cta" | "after-cta";
|
|
29
|
+
}
|
|
25
30
|
export interface RenderEmailArgs {
|
|
26
31
|
/** Short preview text shown by email clients next to the subject. */
|
|
27
32
|
preheader?: string;
|
|
@@ -31,6 +36,8 @@ export interface RenderEmailArgs {
|
|
|
31
36
|
paragraphs: string[];
|
|
32
37
|
/** Primary call-to-action rendered as a real button. */
|
|
33
38
|
cta?: EmailCta;
|
|
39
|
+
/** A treated, copyable URL shown before or after the CTA. */
|
|
40
|
+
linkBlock?: EmailLinkBlock;
|
|
34
41
|
/**
|
|
35
42
|
* Optional trusted HTML injected above the CTA — e.g. a template-owned video
|
|
36
43
|
* thumbnail with a play badge. Injected verbatim, so only pass markup built
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"email-template.d.ts","sourceRoot":"","sources":["../../src/server/email-template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,eAAO,MAAM,kCAAkC,sBAAsB,CAAC;AAEtE,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,eAAe;IAC9B,qEAAqE;IACrE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,2EAA2E;IAC3E,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,wDAAwD;IACxD,GAAG,CAAC,EAAE,QAAQ,CAAC;IACf;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yDAAyD;IACzD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAsCD,wBAAgB,WAAW,CAAC,IAAI,EAAE,eAAe,GAAG,aAAa,
|
|
1
|
+
{"version":3,"file":"email-template.d.ts","sourceRoot":"","sources":["../../src/server/email-template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,eAAO,MAAM,kCAAkC,sBAAsB,CAAC;AAEtE,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,YAAY,GAAG,WAAW,CAAC;CACxC;AAED,MAAM,WAAW,eAAe;IAC9B,qEAAqE;IACrE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,2EAA2E;IAC3E,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,wDAAwD;IACxD,GAAG,CAAC,EAAE,QAAQ,CAAC;IACf,6DAA6D;IAC7D,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yDAAyD;IACzD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAsCD,wBAAgB,WAAW,CAAC,IAAI,EAAE,eAAe,GAAG,aAAa,CA2IhE;AAeD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAE5D"}
|
|
@@ -73,6 +73,18 @@ export function renderEmail(args) {
|
|
|
73
73
|
const paragraphsHtml = args.paragraphs
|
|
74
74
|
.map((p) => `<p style="margin:0 0 16px 0; font-size:16px; line-height:1.6; color:#d4d4d8;">${p}</p>`)
|
|
75
75
|
.join("");
|
|
76
|
+
const linkBlockHtml = args.linkBlock
|
|
77
|
+
? `
|
|
78
|
+
<p style="margin:24px 0 12px 0; font-size:15px; line-height:1.6; color:#d4d4d8;">${escapeHtml(args.linkBlock.intro)}</p>
|
|
79
|
+
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="margin:0; border:1px solid #3f3f46; border-radius:10px; background:#0a0a0c;">
|
|
80
|
+
<tr>
|
|
81
|
+
<td style="padding:14px 16px;">
|
|
82
|
+
<a href="${escapeAttr(args.linkBlock.url)}" style="display:block; color:#d4d4d8; font-family:'SFMono-Regular', Consolas, 'Liberation Mono', monospace; font-size:13px; line-height:1.5; text-decoration:none; word-break:break-all; overflow-wrap:anywhere;">${escapeHtml(args.linkBlock.url)}</a>
|
|
83
|
+
</td>
|
|
84
|
+
</tr>
|
|
85
|
+
</table>
|
|
86
|
+
`
|
|
87
|
+
: "";
|
|
76
88
|
const ctaHtml = args.cta
|
|
77
89
|
? `
|
|
78
90
|
<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin:24px 0 0 0;">
|
|
@@ -130,7 +142,9 @@ export function renderEmail(args) {
|
|
|
130
142
|
</h1>
|
|
131
143
|
${paragraphsHtml}
|
|
132
144
|
${heroHtml}
|
|
145
|
+
${args.linkBlock?.placement !== "after-cta" ? linkBlockHtml : ""}
|
|
133
146
|
${ctaHtml}
|
|
147
|
+
${args.linkBlock?.placement === "after-cta" ? linkBlockHtml : ""}
|
|
134
148
|
${footerHtml}
|
|
135
149
|
</td>
|
|
136
150
|
</tr>
|
|
@@ -147,10 +161,20 @@ export function renderEmail(args) {
|
|
|
147
161
|
textLines.push(stripTags(p));
|
|
148
162
|
textLines.push("");
|
|
149
163
|
}
|
|
164
|
+
if (args.linkBlock && args.linkBlock.placement !== "after-cta") {
|
|
165
|
+
textLines.push(args.linkBlock.intro);
|
|
166
|
+
textLines.push(args.linkBlock.url);
|
|
167
|
+
textLines.push("");
|
|
168
|
+
}
|
|
150
169
|
if (args.cta) {
|
|
151
170
|
textLines.push(`${args.cta.label}: ${args.cta.url}`);
|
|
152
171
|
textLines.push("");
|
|
153
172
|
}
|
|
173
|
+
if (args.linkBlock?.placement === "after-cta") {
|
|
174
|
+
textLines.push(args.linkBlock.intro);
|
|
175
|
+
textLines.push(args.linkBlock.url);
|
|
176
|
+
textLines.push("");
|
|
177
|
+
}
|
|
154
178
|
if (args.footer) {
|
|
155
179
|
textLines.push(args.footer);
|
|
156
180
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"email-template.js","sourceRoot":"","sources":["../../src/server/email-template.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Reusable dark-themed HTML email template.\n *\n * Email clients have limited CSS support, so everything is inlined and layout\n * uses tables for Outlook compatibility. The design mirrors the app's dark UI:\n * near-black card on neutral background, Inter typography with safe fallbacks.\n *\n * Default is monochrome (white CTA on dark). Pass `brandColor` to tint the\n * CTA button and inline links — Clips, for example, passes its purple.\n *\n * Usage:\n * const { html, text } = renderEmail({\n * preheader: \"…\",\n * heading: \"You're invited to join Acme\",\n * paragraphs: [\"Alice invited you to join…\"],\n * cta: { label: \"Accept invite\", url: \"https://…\" },\n * footer: \"If you weren't expecting this, ignore this email.\",\n * });\n */\n\nimport { getAppName } from \"./app-name.js\";\n\nexport const AGENT_NATIVE_EMAIL_LOGO_CONTENT_ID = \"agent-native-logo\";\n\nexport interface EmailCta {\n label: string;\n url: string;\n}\n\nexport interface RenderEmailArgs {\n /** Short preview text shown by email clients next to the subject. */\n preheader?: string;\n /** Large headline at the top of the card. */\n heading: string;\n /** Body paragraphs rendered after the heading. Plain strings — escaped. */\n paragraphs: string[];\n /** Primary call-to-action rendered as a real button. */\n cta?: EmailCta;\n /**\n * Optional trusted HTML injected above the CTA — e.g. a template-owned video\n * thumbnail with a play badge. Injected verbatim, so only pass markup built\n * by app/template code (never raw user input), and escape any dynamic values.\n */\n heroHtml?: string;\n /** Small muted text under the CTA (e.g. expiry note). */\n footer?: string;\n /** Optional app name shown beside the framework logo. */\n brandName?: string;\n /**\n * Optional absolute `https://` logo URL shown in the brand header. When a\n * valid URL is provided it replaces the default embedded Agent Native logo;\n * anything else (missing, relative, non-https) falls back to that logo.\n */\n brandLogoUrl?: string;\n /**\n * Optional brand hex color for the CTA button and inline links. Defaults to\n * a monochrome near-white button with dark text.\n */\n brandColor?: string;\n}\n\nexport interface RenderedEmail {\n html: string;\n text: string;\n}\n\nfunction escapeHtml(s: string): string {\n return s\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n\nfunction escapeAttr(s: string): string {\n return escapeHtml(s);\n}\n\n/**\n * Only accept a strict `#rrggbb` hex color for `brandColor`. Anything else\n * could inject CSS into the inline `style` attribute (`red; background:url(…)`).\n */\nfunction sanitizeHexColor(input: string | undefined): string | undefined {\n if (!input) return undefined;\n return /^#[0-9a-fA-F]{6}$/.test(input) ? input : undefined;\n}\n\n/**\n * Only accept an absolute `https://` URL for the brand logo. Email clients drop\n * relative and mixed-content images, and an unvalidated string in `src` is an\n * injection surface — so anything else falls back to the embedded logo.\n */\nfunction sanitizeLogoUrl(input: string | undefined): string | undefined {\n if (!input) return undefined;\n try {\n return new URL(input).protocol === \"https:\" ? input : undefined;\n } catch {\n return undefined;\n }\n}\n\nexport function renderEmail(args: RenderEmailArgs): RenderedEmail {\n const preheader = args.preheader || \"\";\n const brand = sanitizeHexColor(args.brandColor);\n const brandName = args.brandName?.trim() || getAppName() || \"Agent Native\";\n const logoSrc =\n sanitizeLogoUrl(args.brandLogoUrl) ??\n `cid:${AGENT_NATIVE_EMAIL_LOGO_CONTENT_ID}`;\n\n // Monochrome default: near-white button with dark text. Brand override:\n // colored button with white text.\n const ctaBg = brand ?? \"#fafafa\";\n const ctaFg = brand ? \"#ffffff\" : \"#0a0a0c\";\n const linkColor = brand ?? \"#a1a1aa\";\n\n // Trusted markup supplied by the caller (template code, not user input),\n // injected as-is so a template can own app-specific previews (e.g. a video\n // thumbnail with a play badge). Callers are responsible for escaping any\n // dynamic values they interpolate.\n const heroHtml = args.heroHtml ?? \"\";\n\n const paragraphsHtml = args.paragraphs\n .map(\n (p) =>\n `<p style=\"margin:0 0 16px 0; font-size:16px; line-height:1.6; color:#d4d4d8;\">${p}</p>`,\n )\n .join(\"\");\n\n const ctaHtml = args.cta\n ? `\n <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"margin:24px 0 0 0;\">\n <tr>\n <td style=\"border-radius:10px; background:${ctaBg};\">\n <a href=\"${escapeAttr(args.cta.url)}\"\n style=\"display:inline-block; padding:14px 26px; font-family:'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size:15px; font-weight:600; color:${ctaFg}; text-decoration:none; border-radius:10px;\">\n ${escapeHtml(args.cta.label)}\n </a>\n </td>\n </tr>\n </table>\n `\n : \"\";\n\n const footerHtml = args.footer\n ? `<p style=\"margin:28px 0 0 0; font-size:13px; line-height:1.5; color:#71717a;\">${escapeHtml(args.footer)}</p>`\n : \"\";\n\n const brandHeaderHtml = `\n <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%\" style=\"margin:0 0 28px 0; padding:0 0 24px 0; border-bottom:1px solid #27272a;\">\n <tr>\n <td align=\"center\">\n <img src=\"${escapeAttr(logoSrc)}\" alt=\"${escapeAttr(brandName)}\" width=\"28\" height=\"28\" style=\"display:inline-block; vertical-align:middle; width:28px; height:28px; margin:0 8px 0 0; border:0;\" />\n <span style=\"font-size:18px; line-height:28px; font-weight:600; color:#fafafa; vertical-align:middle;\">${escapeHtml(brandName)}</span>\n </td>\n </tr>\n </table>`;\n\n const html = `<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <meta name=\"color-scheme\" content=\"dark light\" />\n <meta name=\"supported-color-schemes\" content=\"dark light\" />\n <title>${escapeHtml(args.heading)}</title>\n <style>\n @media (prefers-color-scheme: light) {\n .bg-outer { background-color: #0a0a0c !important; }\n }\n a { color: ${linkColor}; }\n </style>\n </head>\n <body style=\"margin:0; padding:0; background-color:#0a0a0c; font-family:'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; -webkit-font-smoothing:antialiased;\">\n <div style=\"display:none; max-height:0; overflow:hidden; opacity:0; color:transparent;\">\n ${escapeHtml(preheader)}\n </div>\n <table role=\"presentation\" class=\"bg-outer\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%\" style=\"background-color:#0a0a0c; padding:40px 16px;\">\n <tr>\n <td align=\"center\">\n <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%\" style=\"max-width:560px;\">\n <tr>\n <td style=\"background-color:#141417; border:1px solid #27272a; border-radius:16px; padding:36px 36px 32px 36px;\">\n ${brandHeaderHtml}\n <h1 style=\"margin:0 0 20px 0; font-size:24px; line-height:1.3; font-weight:600; color:#fafafa; letter-spacing:-0.02em;\">\n ${escapeHtml(args.heading)}\n </h1>\n ${paragraphsHtml}\n ${heroHtml}\n ${ctaHtml}\n ${footerHtml}\n </td>\n </tr>\n </table>\n </td>\n </tr>\n </table>\n </body>\n</html>`;\n\n const textLines: string[] = [];\n textLines.push(args.heading);\n textLines.push(\"\");\n for (const p of args.paragraphs) {\n textLines.push(stripTags(p));\n textLines.push(\"\");\n }\n if (args.cta) {\n textLines.push(`${args.cta.label}: ${args.cta.url}`);\n textLines.push(\"\");\n }\n if (args.footer) {\n textLines.push(args.footer);\n }\n\n return { html, text: textLines.join(\"\\n\").trim() };\n}\n\nfunction stripTags(s: string): string {\n return s\n .replace(/<br\\s*\\/?>/gi, \"\\n\")\n .replace(/<[^>]+>/g, \"\")\n .replace(/ /g, \" \")\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\")\n .trim();\n}\n\n/**\n * Build an inline `<strong>` tag with consistent styling for use inside\n * paragraph strings passed to `renderEmail`. Escapes the content.\n */\nexport function emailStrong(text: string): string {\n return `<strong style=\"color:#fafafa; font-weight:600;\">${escapeHtml(text)}</strong>`;\n}\n\n/**\n * Build a labelled inline link for paragraph strings passed to `renderEmail`.\n * Use this instead of rendering raw URLs in the visible email body.\n */\nexport function emailLink(label: string, url: string): string {\n return `<a href=\"${escapeAttr(url)}\" style=\"color:#a1a1aa; text-decoration:underline;\">${escapeHtml(label)}</a>`;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"email-template.js","sourceRoot":"","sources":["../../src/server/email-template.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Reusable dark-themed HTML email template.\n *\n * Email clients have limited CSS support, so everything is inlined and layout\n * uses tables for Outlook compatibility. The design mirrors the app's dark UI:\n * near-black card on neutral background, Inter typography with safe fallbacks.\n *\n * Default is monochrome (white CTA on dark). Pass `brandColor` to tint the\n * CTA button and inline links — Clips, for example, passes its purple.\n *\n * Usage:\n * const { html, text } = renderEmail({\n * preheader: \"…\",\n * heading: \"You're invited to join Acme\",\n * paragraphs: [\"Alice invited you to join…\"],\n * cta: { label: \"Accept invite\", url: \"https://…\" },\n * footer: \"If you weren't expecting this, ignore this email.\",\n * });\n */\n\nimport { getAppName } from \"./app-name.js\";\n\nexport const AGENT_NATIVE_EMAIL_LOGO_CONTENT_ID = \"agent-native-logo\";\n\nexport interface EmailCta {\n label: string;\n url: string;\n}\n\nexport interface EmailLinkBlock {\n intro: string;\n url: string;\n placement?: \"before-cta\" | \"after-cta\";\n}\n\nexport interface RenderEmailArgs {\n /** Short preview text shown by email clients next to the subject. */\n preheader?: string;\n /** Large headline at the top of the card. */\n heading: string;\n /** Body paragraphs rendered after the heading. Plain strings — escaped. */\n paragraphs: string[];\n /** Primary call-to-action rendered as a real button. */\n cta?: EmailCta;\n /** A treated, copyable URL shown before or after the CTA. */\n linkBlock?: EmailLinkBlock;\n /**\n * Optional trusted HTML injected above the CTA — e.g. a template-owned video\n * thumbnail with a play badge. Injected verbatim, so only pass markup built\n * by app/template code (never raw user input), and escape any dynamic values.\n */\n heroHtml?: string;\n /** Small muted text under the CTA (e.g. expiry note). */\n footer?: string;\n /** Optional app name shown beside the framework logo. */\n brandName?: string;\n /**\n * Optional absolute `https://` logo URL shown in the brand header. When a\n * valid URL is provided it replaces the default embedded Agent Native logo;\n * anything else (missing, relative, non-https) falls back to that logo.\n */\n brandLogoUrl?: string;\n /**\n * Optional brand hex color for the CTA button and inline links. Defaults to\n * a monochrome near-white button with dark text.\n */\n brandColor?: string;\n}\n\nexport interface RenderedEmail {\n html: string;\n text: string;\n}\n\nfunction escapeHtml(s: string): string {\n return s\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n\nfunction escapeAttr(s: string): string {\n return escapeHtml(s);\n}\n\n/**\n * Only accept a strict `#rrggbb` hex color for `brandColor`. Anything else\n * could inject CSS into the inline `style` attribute (`red; background:url(…)`).\n */\nfunction sanitizeHexColor(input: string | undefined): string | undefined {\n if (!input) return undefined;\n return /^#[0-9a-fA-F]{6}$/.test(input) ? input : undefined;\n}\n\n/**\n * Only accept an absolute `https://` URL for the brand logo. Email clients drop\n * relative and mixed-content images, and an unvalidated string in `src` is an\n * injection surface — so anything else falls back to the embedded logo.\n */\nfunction sanitizeLogoUrl(input: string | undefined): string | undefined {\n if (!input) return undefined;\n try {\n return new URL(input).protocol === \"https:\" ? input : undefined;\n } catch {\n return undefined;\n }\n}\n\nexport function renderEmail(args: RenderEmailArgs): RenderedEmail {\n const preheader = args.preheader || \"\";\n const brand = sanitizeHexColor(args.brandColor);\n const brandName = args.brandName?.trim() || getAppName() || \"Agent Native\";\n const logoSrc =\n sanitizeLogoUrl(args.brandLogoUrl) ??\n `cid:${AGENT_NATIVE_EMAIL_LOGO_CONTENT_ID}`;\n\n // Monochrome default: near-white button with dark text. Brand override:\n // colored button with white text.\n const ctaBg = brand ?? \"#fafafa\";\n const ctaFg = brand ? \"#ffffff\" : \"#0a0a0c\";\n const linkColor = brand ?? \"#a1a1aa\";\n\n // Trusted markup supplied by the caller (template code, not user input),\n // injected as-is so a template can own app-specific previews (e.g. a video\n // thumbnail with a play badge). Callers are responsible for escaping any\n // dynamic values they interpolate.\n const heroHtml = args.heroHtml ?? \"\";\n\n const paragraphsHtml = args.paragraphs\n .map(\n (p) =>\n `<p style=\"margin:0 0 16px 0; font-size:16px; line-height:1.6; color:#d4d4d8;\">${p}</p>`,\n )\n .join(\"\");\n\n const linkBlockHtml = args.linkBlock\n ? `\n <p style=\"margin:24px 0 12px 0; font-size:15px; line-height:1.6; color:#d4d4d8;\">${escapeHtml(args.linkBlock.intro)}</p>\n <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%\" style=\"margin:0; border:1px solid #3f3f46; border-radius:10px; background:#0a0a0c;\">\n <tr>\n <td style=\"padding:14px 16px;\">\n <a href=\"${escapeAttr(args.linkBlock.url)}\" style=\"display:block; color:#d4d4d8; font-family:'SFMono-Regular', Consolas, 'Liberation Mono', monospace; font-size:13px; line-height:1.5; text-decoration:none; word-break:break-all; overflow-wrap:anywhere;\">${escapeHtml(args.linkBlock.url)}</a>\n </td>\n </tr>\n </table>\n `\n : \"\";\n\n const ctaHtml = args.cta\n ? `\n <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"margin:24px 0 0 0;\">\n <tr>\n <td style=\"border-radius:10px; background:${ctaBg};\">\n <a href=\"${escapeAttr(args.cta.url)}\"\n style=\"display:inline-block; padding:14px 26px; font-family:'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size:15px; font-weight:600; color:${ctaFg}; text-decoration:none; border-radius:10px;\">\n ${escapeHtml(args.cta.label)}\n </a>\n </td>\n </tr>\n </table>\n `\n : \"\";\n\n const footerHtml = args.footer\n ? `<p style=\"margin:28px 0 0 0; font-size:13px; line-height:1.5; color:#71717a;\">${escapeHtml(args.footer)}</p>`\n : \"\";\n\n const brandHeaderHtml = `\n <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%\" style=\"margin:0 0 28px 0; padding:0 0 24px 0; border-bottom:1px solid #27272a;\">\n <tr>\n <td align=\"center\">\n <img src=\"${escapeAttr(logoSrc)}\" alt=\"${escapeAttr(brandName)}\" width=\"28\" height=\"28\" style=\"display:inline-block; vertical-align:middle; width:28px; height:28px; margin:0 8px 0 0; border:0;\" />\n <span style=\"font-size:18px; line-height:28px; font-weight:600; color:#fafafa; vertical-align:middle;\">${escapeHtml(brandName)}</span>\n </td>\n </tr>\n </table>`;\n\n const html = `<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <meta name=\"color-scheme\" content=\"dark light\" />\n <meta name=\"supported-color-schemes\" content=\"dark light\" />\n <title>${escapeHtml(args.heading)}</title>\n <style>\n @media (prefers-color-scheme: light) {\n .bg-outer { background-color: #0a0a0c !important; }\n }\n a { color: ${linkColor}; }\n </style>\n </head>\n <body style=\"margin:0; padding:0; background-color:#0a0a0c; font-family:'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; -webkit-font-smoothing:antialiased;\">\n <div style=\"display:none; max-height:0; overflow:hidden; opacity:0; color:transparent;\">\n ${escapeHtml(preheader)}\n </div>\n <table role=\"presentation\" class=\"bg-outer\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%\" style=\"background-color:#0a0a0c; padding:40px 16px;\">\n <tr>\n <td align=\"center\">\n <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%\" style=\"max-width:560px;\">\n <tr>\n <td style=\"background-color:#141417; border:1px solid #27272a; border-radius:16px; padding:36px 36px 32px 36px;\">\n ${brandHeaderHtml}\n <h1 style=\"margin:0 0 20px 0; font-size:24px; line-height:1.3; font-weight:600; color:#fafafa; letter-spacing:-0.02em;\">\n ${escapeHtml(args.heading)}\n </h1>\n ${paragraphsHtml}\n ${heroHtml}\n ${args.linkBlock?.placement !== \"after-cta\" ? linkBlockHtml : \"\"}\n ${ctaHtml}\n ${args.linkBlock?.placement === \"after-cta\" ? linkBlockHtml : \"\"}\n ${footerHtml}\n </td>\n </tr>\n </table>\n </td>\n </tr>\n </table>\n </body>\n</html>`;\n\n const textLines: string[] = [];\n textLines.push(args.heading);\n textLines.push(\"\");\n for (const p of args.paragraphs) {\n textLines.push(stripTags(p));\n textLines.push(\"\");\n }\n if (args.linkBlock && args.linkBlock.placement !== \"after-cta\") {\n textLines.push(args.linkBlock.intro);\n textLines.push(args.linkBlock.url);\n textLines.push(\"\");\n }\n if (args.cta) {\n textLines.push(`${args.cta.label}: ${args.cta.url}`);\n textLines.push(\"\");\n }\n if (args.linkBlock?.placement === \"after-cta\") {\n textLines.push(args.linkBlock.intro);\n textLines.push(args.linkBlock.url);\n textLines.push(\"\");\n }\n if (args.footer) {\n textLines.push(args.footer);\n }\n\n return { html, text: textLines.join(\"\\n\").trim() };\n}\n\nfunction stripTags(s: string): string {\n return s\n .replace(/<br\\s*\\/?>/gi, \"\\n\")\n .replace(/<[^>]+>/g, \"\")\n .replace(/ /g, \" \")\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\")\n .trim();\n}\n\n/**\n * Build an inline `<strong>` tag with consistent styling for use inside\n * paragraph strings passed to `renderEmail`. Escapes the content.\n */\nexport function emailStrong(text: string): string {\n return `<strong style=\"color:#fafafa; font-weight:600;\">${escapeHtml(text)}</strong>`;\n}\n\n/**\n * Build a labelled inline link for paragraph strings passed to `renderEmail`.\n * Use this instead of rendering raw URLs in the visible email body.\n */\nexport function emailLink(label: string, url: string): string {\n return `<a href=\"${escapeAttr(url)}\" style=\"color:#a1a1aa; text-decoration:underline;\">${escapeHtml(label)}</a>`;\n}\n"]}
|
|
@@ -26,9 +26,9 @@ export declare function createRealtimeTokenHandler(): import("h3").EventHandlerW
|
|
|
26
26
|
expiresAt?: undefined;
|
|
27
27
|
ttlSeconds?: undefined;
|
|
28
28
|
} | {
|
|
29
|
+
error?: undefined;
|
|
29
30
|
token: string;
|
|
30
31
|
expiresAt: string;
|
|
31
32
|
ttlSeconds: number;
|
|
32
|
-
error?: undefined;
|
|
33
33
|
}>>;
|
|
34
34
|
//# sourceMappingURL=realtime-token.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.128.
|
|
3
|
+
"version": "0.128.3",
|
|
4
4
|
"description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -164,6 +164,7 @@
|
|
|
164
164
|
"./application-state": "./dist/application-state/index.js",
|
|
165
165
|
"./settings": "./dist/settings/index.js",
|
|
166
166
|
"./user-profile": "./dist/user-profile/index.js",
|
|
167
|
+
"./user-profile/server": "./dist/user-profile/store.js",
|
|
167
168
|
"./feature-flags": "./dist/feature-flags/index.js",
|
|
168
169
|
"./feature-flags/registry": "./dist/feature-flags/registry.js",
|
|
169
170
|
"./feature-flags/actions/get-feature-flags": "./dist/feature-flags/actions/get-feature-flags.js",
|
|
@@ -390,7 +391,7 @@
|
|
|
390
391
|
"yjs": "^13.6.31",
|
|
391
392
|
"zod": "^4.3.6",
|
|
392
393
|
"@agent-native/recap-cli": "0.5.1",
|
|
393
|
-
"@agent-native/toolkit": "^0.10.
|
|
394
|
+
"@agent-native/toolkit": "^0.10.10"
|
|
394
395
|
},
|
|
395
396
|
"devDependencies": {
|
|
396
397
|
"@ai-sdk/anthropic": "^3.0.71",
|
|
@@ -27,6 +27,12 @@ export interface EmailCta {
|
|
|
27
27
|
url: string;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
export interface EmailLinkBlock {
|
|
31
|
+
intro: string;
|
|
32
|
+
url: string;
|
|
33
|
+
placement?: "before-cta" | "after-cta";
|
|
34
|
+
}
|
|
35
|
+
|
|
30
36
|
export interface RenderEmailArgs {
|
|
31
37
|
/** Short preview text shown by email clients next to the subject. */
|
|
32
38
|
preheader?: string;
|
|
@@ -36,6 +42,8 @@ export interface RenderEmailArgs {
|
|
|
36
42
|
paragraphs: string[];
|
|
37
43
|
/** Primary call-to-action rendered as a real button. */
|
|
38
44
|
cta?: EmailCta;
|
|
45
|
+
/** A treated, copyable URL shown before or after the CTA. */
|
|
46
|
+
linkBlock?: EmailLinkBlock;
|
|
39
47
|
/**
|
|
40
48
|
* Optional trusted HTML injected above the CTA — e.g. a template-owned video
|
|
41
49
|
* thumbnail with a play badge. Injected verbatim, so only pass markup built
|
|
@@ -127,6 +135,19 @@ export function renderEmail(args: RenderEmailArgs): RenderedEmail {
|
|
|
127
135
|
)
|
|
128
136
|
.join("");
|
|
129
137
|
|
|
138
|
+
const linkBlockHtml = args.linkBlock
|
|
139
|
+
? `
|
|
140
|
+
<p style="margin:24px 0 12px 0; font-size:15px; line-height:1.6; color:#d4d4d8;">${escapeHtml(args.linkBlock.intro)}</p>
|
|
141
|
+
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="margin:0; border:1px solid #3f3f46; border-radius:10px; background:#0a0a0c;">
|
|
142
|
+
<tr>
|
|
143
|
+
<td style="padding:14px 16px;">
|
|
144
|
+
<a href="${escapeAttr(args.linkBlock.url)}" style="display:block; color:#d4d4d8; font-family:'SFMono-Regular', Consolas, 'Liberation Mono', monospace; font-size:13px; line-height:1.5; text-decoration:none; word-break:break-all; overflow-wrap:anywhere;">${escapeHtml(args.linkBlock.url)}</a>
|
|
145
|
+
</td>
|
|
146
|
+
</tr>
|
|
147
|
+
</table>
|
|
148
|
+
`
|
|
149
|
+
: "";
|
|
150
|
+
|
|
130
151
|
const ctaHtml = args.cta
|
|
131
152
|
? `
|
|
132
153
|
<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin:24px 0 0 0;">
|
|
@@ -187,7 +208,9 @@ export function renderEmail(args: RenderEmailArgs): RenderedEmail {
|
|
|
187
208
|
</h1>
|
|
188
209
|
${paragraphsHtml}
|
|
189
210
|
${heroHtml}
|
|
211
|
+
${args.linkBlock?.placement !== "after-cta" ? linkBlockHtml : ""}
|
|
190
212
|
${ctaHtml}
|
|
213
|
+
${args.linkBlock?.placement === "after-cta" ? linkBlockHtml : ""}
|
|
191
214
|
${footerHtml}
|
|
192
215
|
</td>
|
|
193
216
|
</tr>
|
|
@@ -205,10 +228,20 @@ export function renderEmail(args: RenderEmailArgs): RenderedEmail {
|
|
|
205
228
|
textLines.push(stripTags(p));
|
|
206
229
|
textLines.push("");
|
|
207
230
|
}
|
|
231
|
+
if (args.linkBlock && args.linkBlock.placement !== "after-cta") {
|
|
232
|
+
textLines.push(args.linkBlock.intro);
|
|
233
|
+
textLines.push(args.linkBlock.url);
|
|
234
|
+
textLines.push("");
|
|
235
|
+
}
|
|
208
236
|
if (args.cta) {
|
|
209
237
|
textLines.push(`${args.cta.label}: ${args.cta.url}`);
|
|
210
238
|
textLines.push("");
|
|
211
239
|
}
|
|
240
|
+
if (args.linkBlock?.placement === "after-cta") {
|
|
241
|
+
textLines.push(args.linkBlock.intro);
|
|
242
|
+
textLines.push(args.linkBlock.url);
|
|
243
|
+
textLines.push("");
|
|
244
|
+
}
|
|
212
245
|
if (args.footer) {
|
|
213
246
|
textLines.push(args.footer);
|
|
214
247
|
}
|