@agent-native/core 0.43.0 → 0.44.1

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 (124) hide show
  1. package/dist/chat-threads/store.d.ts.map +1 -1
  2. package/dist/chat-threads/store.js +71 -10
  3. package/dist/chat-threads/store.js.map +1 -1
  4. package/dist/cli/pr-visual-recap-workflow.d.ts +1 -1
  5. package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
  6. package/dist/cli/pr-visual-recap-workflow.js +1 -1
  7. package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
  8. package/dist/cli/recap.d.ts +23 -0
  9. package/dist/cli/recap.d.ts.map +1 -1
  10. package/dist/cli/recap.js +177 -13
  11. package/dist/cli/recap.js.map +1 -1
  12. package/dist/cli/skills.d.ts +3 -3
  13. package/dist/cli/skills.d.ts.map +1 -1
  14. package/dist/cli/skills.js +67 -20
  15. package/dist/cli/skills.js.map +1 -1
  16. package/dist/client/AssistantChat.d.ts.map +1 -1
  17. package/dist/client/AssistantChat.js +76 -18
  18. package/dist/client/AssistantChat.js.map +1 -1
  19. package/dist/client/blocks/index.d.ts +0 -2
  20. package/dist/client/blocks/index.d.ts.map +1 -1
  21. package/dist/client/blocks/index.js +0 -2
  22. package/dist/client/blocks/index.js.map +1 -1
  23. package/dist/client/blocks/library/AnnotatedCodeBlock.d.ts.map +1 -1
  24. package/dist/client/blocks/library/AnnotatedCodeBlock.js +22 -9
  25. package/dist/client/blocks/library/AnnotatedCodeBlock.js.map +1 -1
  26. package/dist/client/blocks/library/ApiEndpointBlock.d.ts.map +1 -1
  27. package/dist/client/blocks/library/ApiEndpointBlock.js +113 -13
  28. package/dist/client/blocks/library/ApiEndpointBlock.js.map +1 -1
  29. package/dist/client/blocks/library/DiffBlock.d.ts.map +1 -1
  30. package/dist/client/blocks/library/DiffBlock.js +63 -35
  31. package/dist/client/blocks/library/DiffBlock.js.map +1 -1
  32. package/dist/client/blocks/library/FileTreeBlock.d.ts.map +1 -1
  33. package/dist/client/blocks/library/FileTreeBlock.js +4 -0
  34. package/dist/client/blocks/library/FileTreeBlock.js.map +1 -1
  35. package/dist/client/blocks/library/JsonExplorerBlock.d.ts.map +1 -1
  36. package/dist/client/blocks/library/JsonExplorerBlock.js +1 -1
  37. package/dist/client/blocks/library/JsonExplorerBlock.js.map +1 -1
  38. package/dist/client/blocks/library/MermaidBlock.d.ts.map +1 -1
  39. package/dist/client/blocks/library/MermaidBlock.js +22 -3
  40. package/dist/client/blocks/library/MermaidBlock.js.map +1 -1
  41. package/dist/client/blocks/library/annotation-rail.d.ts +85 -19
  42. package/dist/client/blocks/library/annotation-rail.d.ts.map +1 -1
  43. package/dist/client/blocks/library/annotation-rail.js +149 -27
  44. package/dist/client/blocks/library/annotation-rail.js.map +1 -1
  45. package/dist/client/blocks/library/code-tabs.js +1 -1
  46. package/dist/client/blocks/library/code-tabs.js.map +1 -1
  47. package/dist/client/blocks/library/diagram.d.ts +17 -0
  48. package/dist/client/blocks/library/diagram.d.ts.map +1 -1
  49. package/dist/client/blocks/library/diagram.js +47 -2
  50. package/dist/client/blocks/library/diagram.js.map +1 -1
  51. package/dist/client/blocks/library/server-specs.d.ts.map +1 -1
  52. package/dist/client/blocks/library/server-specs.js +0 -10
  53. package/dist/client/blocks/library/server-specs.js.map +1 -1
  54. package/dist/client/blocks/library/specs.d.ts.map +1 -1
  55. package/dist/client/blocks/library/specs.js +0 -2
  56. package/dist/client/blocks/library/specs.js.map +1 -1
  57. package/dist/client/blocks/library/wireframe.config.d.ts.map +1 -1
  58. package/dist/client/blocks/library/wireframe.config.js +19 -2
  59. package/dist/client/blocks/library/wireframe.config.js.map +1 -1
  60. package/dist/client/blocks/mdx.d.ts.map +1 -1
  61. package/dist/client/blocks/mdx.js +11 -0
  62. package/dist/client/blocks/mdx.js.map +1 -1
  63. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  64. package/dist/client/composer/TiptapComposer.js +13 -8
  65. package/dist/client/composer/TiptapComposer.js.map +1 -1
  66. package/dist/client/composer/pasted-text.d.ts +25 -0
  67. package/dist/client/composer/pasted-text.d.ts.map +1 -1
  68. package/dist/client/composer/pasted-text.js +86 -4
  69. package/dist/client/composer/pasted-text.js.map +1 -1
  70. package/dist/client/rich-markdown-editor/DragHandle.d.ts.map +1 -1
  71. package/dist/client/rich-markdown-editor/DragHandle.js +35 -72
  72. package/dist/client/rich-markdown-editor/DragHandle.js.map +1 -1
  73. package/dist/client/rich-markdown-editor/RegistryBlockNode.d.ts.map +1 -1
  74. package/dist/client/rich-markdown-editor/RegistryBlockNode.js +1 -1
  75. package/dist/client/rich-markdown-editor/RegistryBlockNode.js.map +1 -1
  76. package/dist/client/rich-markdown-editor/SharedRichEditor.d.ts +9 -1
  77. package/dist/client/rich-markdown-editor/SharedRichEditor.d.ts.map +1 -1
  78. package/dist/client/rich-markdown-editor/SharedRichEditor.js +3 -1
  79. package/dist/client/rich-markdown-editor/SharedRichEditor.js.map +1 -1
  80. package/dist/client/rich-markdown-editor/extensions.d.ts +13 -1
  81. package/dist/client/rich-markdown-editor/extensions.d.ts.map +1 -1
  82. package/dist/client/rich-markdown-editor/extensions.js +4 -2
  83. package/dist/client/rich-markdown-editor/extensions.js.map +1 -1
  84. package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts.map +1 -1
  85. package/dist/client/rich-markdown-editor/useCollabReconcile.js +11 -1
  86. package/dist/client/rich-markdown-editor/useCollabReconcile.js.map +1 -1
  87. package/dist/db/migrations.d.ts +10 -0
  88. package/dist/db/migrations.d.ts.map +1 -1
  89. package/dist/db/migrations.js +32 -0
  90. package/dist/db/migrations.js.map +1 -1
  91. package/dist/server/og-fonts-data.d.ts +3 -0
  92. package/dist/server/og-fonts-data.d.ts.map +1 -0
  93. package/dist/server/og-fonts-data.js +9 -0
  94. package/dist/server/og-fonts-data.js.map +1 -0
  95. package/dist/server/og-fonts.d.ts +10 -0
  96. package/dist/server/og-fonts.d.ts.map +1 -0
  97. package/dist/server/og-fonts.js +58 -0
  98. package/dist/server/og-fonts.js.map +1 -0
  99. package/dist/server/poll.d.ts.map +1 -1
  100. package/dist/server/poll.js +30 -14
  101. package/dist/server/poll.js.map +1 -1
  102. package/dist/server/social-og-image.d.ts.map +1 -1
  103. package/dist/server/social-og-image.js +16 -5
  104. package/dist/server/social-og-image.js.map +1 -1
  105. package/dist/styles/blocks.css +121 -2
  106. package/dist/templates/default/.agents/skills/storing-data/SKILL.md +2 -0
  107. package/dist/templates/workspace-core/.agents/skills/performance/SKILL.md +141 -0
  108. package/dist/templates/workspace-core/.agents/skills/storing-data/SKILL.md +2 -0
  109. package/dist/usage/store.d.ts +12 -0
  110. package/dist/usage/store.d.ts.map +1 -1
  111. package/dist/usage/store.js +35 -5
  112. package/dist/usage/store.js.map +1 -1
  113. package/package.json +1 -1
  114. package/src/templates/default/.agents/skills/storing-data/SKILL.md +2 -0
  115. package/src/templates/workspace-core/.agents/skills/performance/SKILL.md +141 -0
  116. package/src/templates/workspace-core/.agents/skills/storing-data/SKILL.md +2 -0
  117. package/dist/client/blocks/library/decision.config.d.ts +0 -37
  118. package/dist/client/blocks/library/decision.config.d.ts.map +0 -1
  119. package/dist/client/blocks/library/decision.config.js +0 -32
  120. package/dist/client/blocks/library/decision.config.js.map +0 -1
  121. package/dist/client/blocks/library/decision.d.ts +0 -19
  122. package/dist/client/blocks/library/decision.d.ts.map +0 -1
  123. package/dist/client/blocks/library/decision.js +0 -119
  124. package/dist/client/blocks/library/decision.js.map +0 -1
@@ -1,4 +1,29 @@
1
+ /** The clipboard flavors we care about for a paste. */
2
+ export interface ClipboardPaste {
3
+ /** `text/plain` flavor — used for size heuristics and as the default body. */
4
+ text: string;
5
+ /** `text/html` flavor when the source provided one. */
6
+ html?: string;
7
+ }
8
+ /** Read the relevant clipboard flavors from a paste/drop DataTransfer. */
9
+ export declare function readClipboardPaste(data: {
10
+ getData(type: string): string;
11
+ } | null | undefined): ClipboardPaste;
1
12
  export declare function shouldConvertPasteToAttachment(text: string): boolean;
13
+ /**
14
+ * Whether a clipboard paste is large enough to become a `Pasted text`
15
+ * attachment chip. Mirrors `shouldConvertPasteToAttachment` but evaluates the
16
+ * representation we'd actually store, so an HTML-only paste (empty text/plain)
17
+ * still converts on the strength of its markup.
18
+ */
19
+ export declare function shouldConvertClipboardToAttachment(paste: ClipboardPaste): boolean;
20
+ /**
21
+ * Build the attachment File for a page-sized paste, preserving HTML markup when
22
+ * the pasted content is an HTML document so it travels the same rail as an
23
+ * uploaded .html file.
24
+ */
25
+ export declare function createPastedAttachmentFile(paste: ClipboardPaste): File;
26
+ /** Back-compat helper for callers that only have plain text. */
2
27
  export declare function createPastedTextFile(text: string): File;
3
28
  export declare function isPastedTextAttachmentName(name: string | undefined): boolean;
4
29
  export declare function unwrapAttachmentEnvelope(text: string): string;
@@ -1 +1 @@
1
- {"version":3,"file":"pasted-text.d.ts","sourceRoot":"","sources":["../../../src/client/composer/pasted-text.ts"],"names":[],"mappings":"AAQA,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAWpE;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAKvD;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAE5E;AAKD,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG7D;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAO/C"}
1
+ {"version":3,"file":"pasted-text.d.ts","sourceRoot":"","sources":["../../../src/client/composer/pasted-text.ts"],"names":[],"mappings":"AA4CA,uDAAuD;AACvD,MAAM,WAAW,cAAc;IAC7B,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,0EAA0E;AAC1E,wBAAgB,kBAAkB,CAChC,IAAI,EAAE;IAAE,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,GACzD,cAAc,CAIhB;AAmCD,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAWpE;AAED;;;;;GAKG;AACH,wBAAgB,kCAAkC,CAChD,KAAK,EAAE,cAAc,GACpB,OAAO,CAET;AAQD;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI,CAGtE;AAED,gEAAgE;AAChE,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAEvD;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAE5E;AAKD,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG7D;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAO/C"}
@@ -4,6 +4,67 @@
4
4
  const PASTED_TEXT_MIN_CHARS = 3200;
5
5
  const PASTED_TEXT_MIN_LINES = 24;
6
6
  const PASTED_TEXT_FILENAME_PREFIX = "pasted-text-";
7
+ // A copied HTML document/source is recognizable from its markup: a closing tag
8
+ // (`</div>`), a doctype, or a common structural/element tag. We key off the
9
+ // *content* the user actually pasted, not the clipboard's `text/html` flavor —
10
+ // editors (VS Code) and rich-text apps (Google Docs) populate `text/html` with
11
+ // syntax-highlight spans or formatting wrappers even when the real content is
12
+ // plain code/prose, so trusting `text/html` blindly would mangle those pastes.
13
+ const HTML_SOURCE_SIGNAL = /<!doctype\s+html|<html[\s>]|<\/[a-z][a-z0-9-]*\s*>|<(?:body|head|div|span|section|main|header|footer|nav|article|aside|ul|ol|li|table|thead|tbody|tr|td|th|h[1-6]|p|a|img|button|input|textarea|select|form|label|script|style|link|meta|svg|canvas|template)\b/i;
14
+ // A real HTML *document* announces itself with a doctype / html / head / body.
15
+ // When one of these is present we keep the HTML classification even if an inline
16
+ // <script> contains JS keywords — it's a genuine page.
17
+ const HTML_DOCUMENT_SIGNAL = /<!doctype\s+html|<html[\s>]|<head[\s>]|<body[\s>]/i;
18
+ // JSX/TSX source contains the same `</div>`/`<span>` tags as an HTML document,
19
+ // so the HTML tag signal alone misfiles a pasted React/TS component (even a bare
20
+ // function component) as an `.html` artifact — the agent then mishandles it as a
21
+ // hostable document instead of source. These markers appear in JS/TS/JSX source
22
+ // but not in a plain HTML *fragment*: `className=` (JSX uses it; HTML uses
23
+ // `class=`), ES module import/export, arrow functions, TS type/React annotations,
24
+ // React hooks, and the basic JS declaration/return keywords that make up a
25
+ // component body.
26
+ const CODE_SOURCE_SIGNAL = /\bclassName=|\bimport\b|\bexport\b|=>|:\s*React\.|\buse[A-Z]\w*\(|\b(?:function|const|let|var|return|class|interface|type|enum)\b/;
27
+ function looksLikeHtml(value) {
28
+ if (!value || !HTML_SOURCE_SIGNAL.test(value))
29
+ return false;
30
+ // A self-announcing HTML document stays HTML even if it embeds a <script>.
31
+ if (HTML_DOCUMENT_SIGNAL.test(value))
32
+ return true;
33
+ // Otherwise it's a bare fragment — if it carries JS/TS/JSX code signals,
34
+ // treat it as source code, not an HTML attachment.
35
+ if (CODE_SOURCE_SIGNAL.test(value))
36
+ return false;
37
+ return true;
38
+ }
39
+ /** Read the relevant clipboard flavors from a paste/drop DataTransfer. */
40
+ export function readClipboardPaste(data) {
41
+ const text = data?.getData("text/plain") ?? "";
42
+ const html = data?.getData("text/html") ?? "";
43
+ return { text, html: html.trim() ? html : undefined };
44
+ }
45
+ // Decide what to actually store for a paste. Preserving HTML markup means a
46
+ // pasted HTML document behaves exactly like uploading that .html file: the agent
47
+ // recognizes it as a hostable artifact and reads it verbatim via
48
+ // `contentFromAttachment` instead of retyping it inline (which cuts off
49
+ // mid-stream on large files and triggers a continuation loop / "spin").
50
+ function selectPasteBody(paste) {
51
+ const plain = paste.text ?? "";
52
+ const html = paste.html ?? "";
53
+ // 1) The pasted text is itself HTML source (copied from an editor, a file, or
54
+ // view-source). The plain text *is* the markup, so keep it as .html.
55
+ if (plain.trim() && looksLikeHtml(plain)) {
56
+ return { body: plain, ext: "html", type: "text/html" };
57
+ }
58
+ // 2) No usable plain text, but a real HTML flavor exists (some apps only
59
+ // expose text/html). Fall back to the markup so nothing is dropped.
60
+ if (!plain.trim() && looksLikeHtml(html)) {
61
+ return { body: html, ext: "html", type: "text/html" };
62
+ }
63
+ // 3) Default: keep the clean plain text. Avoids the syntax-highlight /
64
+ // rich-text noise that lives in text/html when the plain text is already
65
+ // the real content (code from an editor, prose from a doc).
66
+ return { body: plain, ext: "txt", type: "text/plain" };
67
+ }
7
68
  export function shouldConvertPasteToAttachment(text) {
8
69
  if (!text)
9
70
  return false;
@@ -19,11 +80,32 @@ export function shouldConvertPasteToAttachment(text) {
19
80
  }
20
81
  return false;
21
82
  }
22
- export function createPastedTextFile(text) {
23
- const name = `${PASTED_TEXT_FILENAME_PREFIX}${Date.now()}-${Math.random()
83
+ /**
84
+ * Whether a clipboard paste is large enough to become a `Pasted text`
85
+ * attachment chip. Mirrors `shouldConvertPasteToAttachment` but evaluates the
86
+ * representation we'd actually store, so an HTML-only paste (empty text/plain)
87
+ * still converts on the strength of its markup.
88
+ */
89
+ export function shouldConvertClipboardToAttachment(paste) {
90
+ return shouldConvertPasteToAttachment(selectPasteBody(paste).body);
91
+ }
92
+ function pastedAttachmentName(ext) {
93
+ return `${PASTED_TEXT_FILENAME_PREFIX}${Date.now()}-${Math.random()
24
94
  .toString(36)
25
- .slice(2, 8)}.txt`;
26
- return new File([text], name, { type: "text/plain" });
95
+ .slice(2, 8)}.${ext}`;
96
+ }
97
+ /**
98
+ * Build the attachment File for a page-sized paste, preserving HTML markup when
99
+ * the pasted content is an HTML document so it travels the same rail as an
100
+ * uploaded .html file.
101
+ */
102
+ export function createPastedAttachmentFile(paste) {
103
+ const { body, ext, type } = selectPasteBody(paste);
104
+ return new File([body], pastedAttachmentName(ext), { type });
105
+ }
106
+ /** Back-compat helper for callers that only have plain text. */
107
+ export function createPastedTextFile(text) {
108
+ return createPastedAttachmentFile({ text });
27
109
  }
28
110
  export function isPastedTextAttachmentName(name) {
29
111
  return !!name && name.startsWith(PASTED_TEXT_FILENAME_PREFIX);
@@ -1 +1 @@
1
- {"version":3,"file":"pasted-text.js","sourceRoot":"","sources":["../../../src/client/composer/pasted-text.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,0EAA0E;AAC1E,+DAA+D;AAC/D,MAAM,qBAAqB,GAAG,IAAI,CAAC;AACnC,MAAM,qBAAqB,GAAG,EAAE,CAAC;AAEjC,MAAM,2BAA2B,GAAG,cAAc,CAAC;AAEnD,MAAM,UAAU,8BAA8B,CAAC,IAAY;IACzD,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,IAAI,IAAI,CAAC,MAAM,IAAI,qBAAqB;QAAE,OAAO,IAAI,CAAC;IACtD,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC;YAC9B,KAAK,EAAE,CAAC;YACR,IAAI,KAAK,IAAI,qBAAqB;gBAAE,OAAO,IAAI,CAAC;QAClD,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAC/C,MAAM,IAAI,GAAG,GAAG,2BAA2B,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE;SACtE,QAAQ,CAAC,EAAE,CAAC;SACZ,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC;IACrB,OAAO,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC;AACxD,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,IAAwB;IACjE,OAAO,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC,UAAU,CAAC,2BAA2B,CAAC,CAAC;AAChE,CAAC;AAED,yEAAyE;AACzE,+EAA+E;AAC/E,2BAA2B;AAC3B,MAAM,UAAU,wBAAwB,CAAC,IAAY;IACnD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,kDAAkD,CAAC,CAAC;IAC7E,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,IAAI,CAAC,IAAI;QAAE,OAAO,CAAC,CAAC;IACpB,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,EAAE;YAAE,KAAK,EAAE,CAAC;IACzC,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC","sourcesContent":["// Page-sized pastes turn into a `Pasted text` attachment chip instead of being\n// dumped into the editor. Short paragraphs and everyday lists should stay\n// inline so the composer still feels like a normal text field.\nconst PASTED_TEXT_MIN_CHARS = 3200;\nconst PASTED_TEXT_MIN_LINES = 24;\n\nconst PASTED_TEXT_FILENAME_PREFIX = \"pasted-text-\";\n\nexport function shouldConvertPasteToAttachment(text: string): boolean {\n if (!text) return false;\n if (text.length >= PASTED_TEXT_MIN_CHARS) return true;\n let lines = 1;\n for (let i = 0; i < text.length; i++) {\n if (text.charCodeAt(i) === 10) {\n lines++;\n if (lines >= PASTED_TEXT_MIN_LINES) return true;\n }\n }\n return false;\n}\n\nexport function createPastedTextFile(text: string): File {\n const name = `${PASTED_TEXT_FILENAME_PREFIX}${Date.now()}-${Math.random()\n .toString(36)\n .slice(2, 8)}.txt`;\n return new File([text], name, { type: \"text/plain\" });\n}\n\nexport function isPastedTextAttachmentName(name: string | undefined): boolean {\n return !!name && name.startsWith(PASTED_TEXT_FILENAME_PREFIX);\n}\n\n// Strips the `<attachment name=...>\\n` / `\\n</attachment>` envelope that\n// SimpleTextAttachmentAdapter wraps the file body in when sending. Returns the\n// raw body for previewing.\nexport function unwrapAttachmentEnvelope(text: string): string {\n const match = text.match(/^<attachment\\b[^>]*>\\n([\\s\\S]*)\\n<\\/attachment>$/);\n return match ? match[1] : text;\n}\n\nexport function countLines(text: string): number {\n if (!text) return 0;\n let lines = 1;\n for (let i = 0; i < text.length; i++) {\n if (text.charCodeAt(i) === 10) lines++;\n }\n return lines;\n}\n"]}
1
+ {"version":3,"file":"pasted-text.js","sourceRoot":"","sources":["../../../src/client/composer/pasted-text.ts"],"names":[],"mappings":"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","sourcesContent":["// Page-sized pastes turn into a `Pasted text` attachment chip instead of being\n// dumped into the editor. Short paragraphs and everyday lists should stay\n// inline so the composer still feels like a normal text field.\nconst PASTED_TEXT_MIN_CHARS = 3200;\nconst PASTED_TEXT_MIN_LINES = 24;\n\nconst PASTED_TEXT_FILENAME_PREFIX = \"pasted-text-\";\n\n// A copied HTML document/source is recognizable from its markup: a closing tag\n// (`</div>`), a doctype, or a common structural/element tag. We key off the\n// *content* the user actually pasted, not the clipboard's `text/html` flavor —\n// editors (VS Code) and rich-text apps (Google Docs) populate `text/html` with\n// syntax-highlight spans or formatting wrappers even when the real content is\n// plain code/prose, so trusting `text/html` blindly would mangle those pastes.\nconst HTML_SOURCE_SIGNAL =\n /<!doctype\\s+html|<html[\\s>]|<\\/[a-z][a-z0-9-]*\\s*>|<(?:body|head|div|span|section|main|header|footer|nav|article|aside|ul|ol|li|table|thead|tbody|tr|td|th|h[1-6]|p|a|img|button|input|textarea|select|form|label|script|style|link|meta|svg|canvas|template)\\b/i;\n\n// A real HTML *document* announces itself with a doctype / html / head / body.\n// When one of these is present we keep the HTML classification even if an inline\n// <script> contains JS keywords — it's a genuine page.\nconst HTML_DOCUMENT_SIGNAL =\n /<!doctype\\s+html|<html[\\s>]|<head[\\s>]|<body[\\s>]/i;\n\n// JSX/TSX source contains the same `</div>`/`<span>` tags as an HTML document,\n// so the HTML tag signal alone misfiles a pasted React/TS component (even a bare\n// function component) as an `.html` artifact — the agent then mishandles it as a\n// hostable document instead of source. These markers appear in JS/TS/JSX source\n// but not in a plain HTML *fragment*: `className=` (JSX uses it; HTML uses\n// `class=`), ES module import/export, arrow functions, TS type/React annotations,\n// React hooks, and the basic JS declaration/return keywords that make up a\n// component body.\nconst CODE_SOURCE_SIGNAL =\n /\\bclassName=|\\bimport\\b|\\bexport\\b|=>|:\\s*React\\.|\\buse[A-Z]\\w*\\(|\\b(?:function|const|let|var|return|class|interface|type|enum)\\b/;\n\nfunction looksLikeHtml(value: string): boolean {\n if (!value || !HTML_SOURCE_SIGNAL.test(value)) return false;\n // A self-announcing HTML document stays HTML even if it embeds a <script>.\n if (HTML_DOCUMENT_SIGNAL.test(value)) return true;\n // Otherwise it's a bare fragment — if it carries JS/TS/JSX code signals,\n // treat it as source code, not an HTML attachment.\n if (CODE_SOURCE_SIGNAL.test(value)) return false;\n return true;\n}\n\n/** The clipboard flavors we care about for a paste. */\nexport interface ClipboardPaste {\n /** `text/plain` flavor — used for size heuristics and as the default body. */\n text: string;\n /** `text/html` flavor when the source provided one. */\n html?: string;\n}\n\n/** Read the relevant clipboard flavors from a paste/drop DataTransfer. */\nexport function readClipboardPaste(\n data: { getData(type: string): string } | null | undefined,\n): ClipboardPaste {\n const text = data?.getData(\"text/plain\") ?? \"\";\n const html = data?.getData(\"text/html\") ?? \"\";\n return { text, html: html.trim() ? html : undefined };\n}\n\ninterface SelectedPasteBody {\n body: string;\n ext: \"html\" | \"txt\";\n type: \"text/html\" | \"text/plain\";\n}\n\n// Decide what to actually store for a paste. Preserving HTML markup means a\n// pasted HTML document behaves exactly like uploading that .html file: the agent\n// recognizes it as a hostable artifact and reads it verbatim via\n// `contentFromAttachment` instead of retyping it inline (which cuts off\n// mid-stream on large files and triggers a continuation loop / \"spin\").\nfunction selectPasteBody(paste: ClipboardPaste): SelectedPasteBody {\n const plain = paste.text ?? \"\";\n const html = paste.html ?? \"\";\n\n // 1) The pasted text is itself HTML source (copied from an editor, a file, or\n // view-source). The plain text *is* the markup, so keep it as .html.\n if (plain.trim() && looksLikeHtml(plain)) {\n return { body: plain, ext: \"html\", type: \"text/html\" };\n }\n\n // 2) No usable plain text, but a real HTML flavor exists (some apps only\n // expose text/html). Fall back to the markup so nothing is dropped.\n if (!plain.trim() && looksLikeHtml(html)) {\n return { body: html, ext: \"html\", type: \"text/html\" };\n }\n\n // 3) Default: keep the clean plain text. Avoids the syntax-highlight /\n // rich-text noise that lives in text/html when the plain text is already\n // the real content (code from an editor, prose from a doc).\n return { body: plain, ext: \"txt\", type: \"text/plain\" };\n}\n\nexport function shouldConvertPasteToAttachment(text: string): boolean {\n if (!text) return false;\n if (text.length >= PASTED_TEXT_MIN_CHARS) return true;\n let lines = 1;\n for (let i = 0; i < text.length; i++) {\n if (text.charCodeAt(i) === 10) {\n lines++;\n if (lines >= PASTED_TEXT_MIN_LINES) return true;\n }\n }\n return false;\n}\n\n/**\n * Whether a clipboard paste is large enough to become a `Pasted text`\n * attachment chip. Mirrors `shouldConvertPasteToAttachment` but evaluates the\n * representation we'd actually store, so an HTML-only paste (empty text/plain)\n * still converts on the strength of its markup.\n */\nexport function shouldConvertClipboardToAttachment(\n paste: ClipboardPaste,\n): boolean {\n return shouldConvertPasteToAttachment(selectPasteBody(paste).body);\n}\n\nfunction pastedAttachmentName(ext: \"html\" | \"txt\"): string {\n return `${PASTED_TEXT_FILENAME_PREFIX}${Date.now()}-${Math.random()\n .toString(36)\n .slice(2, 8)}.${ext}`;\n}\n\n/**\n * Build the attachment File for a page-sized paste, preserving HTML markup when\n * the pasted content is an HTML document so it travels the same rail as an\n * uploaded .html file.\n */\nexport function createPastedAttachmentFile(paste: ClipboardPaste): File {\n const { body, ext, type } = selectPasteBody(paste);\n return new File([body], pastedAttachmentName(ext), { type });\n}\n\n/** Back-compat helper for callers that only have plain text. */\nexport function createPastedTextFile(text: string): File {\n return createPastedAttachmentFile({ text });\n}\n\nexport function isPastedTextAttachmentName(name: string | undefined): boolean {\n return !!name && name.startsWith(PASTED_TEXT_FILENAME_PREFIX);\n}\n\n// Strips the `<attachment name=...>\\n` / `\\n</attachment>` envelope that\n// SimpleTextAttachmentAdapter wraps the file body in when sending. Returns the\n// raw body for previewing.\nexport function unwrapAttachmentEnvelope(text: string): string {\n const match = text.match(/^<attachment\\b[^>]*>\\n([\\s\\S]*)\\n<\\/attachment>$/);\n return match ? match[1] : text;\n}\n\nexport function countLines(text: string): number {\n if (!text) return 0;\n let lines = 1;\n for (let i = 0; i < text.length; i++) {\n if (text.charCodeAt(i) === 10) lines++;\n }\n return lines;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"DragHandle.d.ts","sourceRoot":"","sources":["../../../src/client/rich-markdown-editor/DragHandle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAQzC,OAAO,KAAK,EAAE,IAAI,IAAI,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD;;;;;;;;GAQG;AACH,eAAO,MAAM,oCAAoC,2BAA2B,CAAC;AAE7E,MAAM,WAAW,iBAAiB;IAChC;;;;;;;;OAQG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,CAAC,OAAO,EAAE;QAC9B,IAAI,EAAE,UAAU,CAAC;QACjB,IAAI,EAAE,eAAe,CAAC;QACtB,GAAG,EAAE,MAAM,CAAC;KACb,KAAK,OAAO,CAAC;IACd;;;;OAIG;IACH,uBAAuB,CAAC,EAAE,CACxB,IAAI,EAAE,OAAO,EACb,OAAO,EAAE;QACP,IAAI,EAAE,UAAU,CAAC;QACjB,IAAI,EAAE,eAAe,CAAC;QACtB,GAAG,EAAE,MAAM,CAAC;QACZ,UAAU,EAAE,UAAU,CAAC;KACxB,KACE,IAAI,CAAC;IACV;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,qBAAqB,KAAK,OAAO,CAAC;CACzE;AAqCD,MAAM,MAAM,uBAAuB,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;AAE5E,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE,UAAU,CAAC;IACjB,UAAU,EAAE,UAAU,CAAC;IACvB,UAAU,EAAE,eAAe,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,eAAe,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,uBAAuB,CAAC;CACpC,CAAC;AAiWF;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,UAAU,mCA67BrB,CAAC"}
1
+ {"version":3,"file":"DragHandle.d.ts","sourceRoot":"","sources":["../../../src/client/rich-markdown-editor/DragHandle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAQzC,OAAO,KAAK,EAAE,IAAI,IAAI,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD;;;;;;;;GAQG;AACH,eAAO,MAAM,oCAAoC,2BAA2B,CAAC;AAE7E,MAAM,WAAW,iBAAiB;IAChC;;;;;;;;OAQG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,CAAC,OAAO,EAAE;QAC9B,IAAI,EAAE,UAAU,CAAC;QACjB,IAAI,EAAE,eAAe,CAAC;QACtB,GAAG,EAAE,MAAM,CAAC;KACb,KAAK,OAAO,CAAC;IACd;;;;OAIG;IACH,uBAAuB,CAAC,EAAE,CACxB,IAAI,EAAE,OAAO,EACb,OAAO,EAAE;QACP,IAAI,EAAE,UAAU,CAAC;QACjB,IAAI,EAAE,eAAe,CAAC;QACtB,GAAG,EAAE,MAAM,CAAC;QACZ,UAAU,EAAE,UAAU,CAAC;KACxB,KACE,IAAI,CAAC;IACV;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,qBAAqB,KAAK,OAAO,CAAC;CACzE;AA8DD,MAAM,MAAM,uBAAuB,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;AAE5E,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE,UAAU,CAAC;IACjB,UAAU,EAAE,UAAU,CAAC;IACvB,UAAU,EAAE,eAAe,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,eAAe,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,uBAAuB,CAAC;CACpC,CAAC;AAuSF;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,UAAU,mCA48BrB,CAAC"}
@@ -33,6 +33,20 @@ const DRAG_HANDLE_MENU_STYLE_ID = "an-rich-md-drag-menu-styles";
33
33
  const DRAG_HANDLE_MENU_WIDTH = 220;
34
34
  const DRAG_HANDLE_MENU_GAP = 6;
35
35
  const DRAG_HANDLE_MENU_VIEWPORT_PADDING = 8;
36
+ /**
37
+ * Wraps Tabler outline icon path data in the standard 24×24 stroke SVG so the
38
+ * DOM-based block menu renders the same icons the React UI uses (Tabler is the
39
+ * framework-wide icon set). The editor is plain DOM, not React, so we inline the
40
+ * markup instead of importing `@tabler/icons-react` components.
41
+ */
42
+ const tablerIconSvg = (paths) => `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">${paths}</svg>`;
43
+ // Tabler `copy`, `trash`, and `plus` (outline). Path data copied verbatim from
44
+ // @tabler/icons so the glyphs stay pixel-identical to the React icon set.
45
+ const DRAG_HANDLE_MENU_ICON_DUPLICATE = tablerIconSvg('<path d="M7 9.667a2.667 2.667 0 0 1 2.667 -2.667h8.666a2.667 2.667 0 0 1 2.667 2.667v8.666a2.667 2.667 0 0 1 -2.667 2.667h-8.666a2.667 2.667 0 0 1 -2.667 -2.667l0 -8.666" /><path d="M4.012 16.737a2.005 2.005 0 0 1 -1.012 -1.737v-10c0 -1.1 .9 -2 2 -2h10c.75 0 1.158 .385 1.5 1" />');
46
+ const DRAG_HANDLE_MENU_ICON_DELETE = tablerIconSvg('<path d="M4 7l16 0" /><path d="M10 11l0 6" /><path d="M14 11l0 6" /><path d="M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2 -2l1 -12" /><path d="M9 7v-3a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v3" />');
47
+ const DRAG_HANDLE_MENU_ICON_INSERT = tablerIconSvg('<path d="M12 5l0 14" /><path d="M5 12l14 0" />');
48
+ // Tabler `grip-vertical` (outline) for the left-margin drag grip.
49
+ const DRAG_HANDLE_GRIP_ICON = tablerIconSvg('<path d="M8 5a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /><path d="M8 12a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /><path d="M8 19a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /><path d="M14 5a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /><path d="M14 12a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /><path d="M14 19a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" />');
36
50
  const dragHandleRegistrations = new Set();
37
51
  let dragHandleGlobalHoverListeners = 0;
38
52
  let activeDragRegistration = null;
@@ -223,8 +237,10 @@ const ensureDragHandleMenuStyles = () => {
223
237
  }
224
238
 
225
239
  .an-rich-md-drag-menu__icon {
226
- position: relative;
240
+ display: flex;
227
241
  flex: 0 0 auto;
242
+ align-items: center;
243
+ justify-content: center;
228
244
  width: 18px;
229
245
  height: 18px;
230
246
  color: hsl(var(--muted-foreground, 215.4 16.3% 46.9%));
@@ -234,69 +250,9 @@ const ensureDragHandleMenuStyles = () => {
234
250
  color: currentColor;
235
251
  }
236
252
 
237
- .an-rich-md-drag-menu__icon::before,
238
- .an-rich-md-drag-menu__icon::after {
239
- content: "";
240
- position: absolute;
241
- box-sizing: border-box;
242
- }
243
-
244
- .an-rich-md-drag-menu__icon--duplicate::before,
245
- .an-rich-md-drag-menu__icon--duplicate::after {
246
- width: 11px;
247
- height: 11px;
248
- border: 1.5px solid currentColor;
249
- border-radius: 2px;
250
- }
251
-
252
- .an-rich-md-drag-menu__icon--duplicate::before {
253
- left: 6px;
254
- top: 2px;
255
- opacity: 0.55;
256
- }
257
-
258
- .an-rich-md-drag-menu__icon--duplicate::after {
259
- left: 2px;
260
- top: 6px;
261
- background: hsl(var(--popover, 0 0% 100%));
262
- }
263
-
264
- .an-rich-md-drag-menu__icon--insert::before {
265
- left: 3px;
266
- top: 8px;
267
- width: 12px;
268
- height: 1.5px;
269
- border-radius: 999px;
270
- background: currentColor;
271
- }
272
-
273
- .an-rich-md-drag-menu__icon--insert::after {
274
- left: 8px;
275
- top: 3px;
276
- width: 1.5px;
277
- height: 12px;
278
- border-radius: 999px;
279
- background: currentColor;
280
- }
281
-
282
- .an-rich-md-drag-menu__icon--delete::before {
283
- left: 4px;
284
- top: 7px;
285
- width: 10px;
286
- height: 11px;
287
- border: 1.5px solid currentColor;
288
- border-top: 0;
289
- border-radius: 0 0 2px 2px;
290
- }
291
-
292
- .an-rich-md-drag-menu__icon--delete::after {
293
- left: 3px;
294
- top: 4px;
295
- width: 12px;
296
- height: 1.5px;
297
- border-radius: 999px;
298
- background: currentColor;
299
- box-shadow: 3px -2.5px 0 -0.4px currentColor;
253
+ .an-rich-md-drag-menu__icon svg {
254
+ width: 17px;
255
+ height: 17px;
300
256
  }
301
257
 
302
258
  .an-rich-md-drag-menu__label {
@@ -587,7 +543,7 @@ export const DragHandle = Extension.create({
587
543
  DRAG_HANDLE_MENU_VIEWPORT_PADDING)}px`;
588
544
  menu.style.top = `${clamp(anchorRect.top - 4, DRAG_HANDLE_MENU_VIEWPORT_PADDING, viewportHeight - menuHeight - DRAG_HANDLE_MENU_VIEWPORT_PADDING)}px`;
589
545
  };
590
- const createMenuItem = (label, iconModifier, action, options = {}) => {
546
+ const createMenuItem = (label, iconSvg, action, options = {}) => {
591
547
  const button = document.createElement("button");
592
548
  button.type = "button";
593
549
  button.className = "an-rich-md-drag-menu__item";
@@ -596,8 +552,9 @@ export const DragHandle = Extension.create({
596
552
  if (options.danger)
597
553
  button.setAttribute("data-danger", "true");
598
554
  const icon = document.createElement("span");
599
- icon.className = `an-rich-md-drag-menu__icon an-rich-md-drag-menu__icon--${iconModifier}`;
555
+ icon.className = "an-rich-md-drag-menu__icon";
600
556
  icon.setAttribute("aria-hidden", "true");
557
+ icon.innerHTML = iconSvg;
601
558
  const labelElement = document.createElement("span");
602
559
  labelElement.className = "an-rich-md-drag-menu__label";
603
560
  labelElement.textContent = label;
@@ -628,7 +585,9 @@ export const DragHandle = Extension.create({
628
585
  el.setAttribute("role", "menu");
629
586
  el.setAttribute("aria-label", "Block actions");
630
587
  el.setAttribute("data-plan-interactive", "true");
631
- el.append(createMenuItem("Duplicate", "duplicate", duplicateBlock), createMenuItem("Delete", "delete", deleteBlock, { danger: true }), createMenuItem("Insert block below", "insert", insertParagraphBelow));
588
+ el.append(createMenuItem("Duplicate", DRAG_HANDLE_MENU_ICON_DUPLICATE, duplicateBlock), createMenuItem("Delete", DRAG_HANDLE_MENU_ICON_DELETE, deleteBlock, {
589
+ danger: true,
590
+ }), createMenuItem("Insert block below", DRAG_HANDLE_MENU_ICON_INSERT, insertParagraphBelow));
632
591
  menu = el;
633
592
  menuContext = {
634
593
  view: resolved.view,
@@ -796,11 +755,15 @@ export const DragHandle = Extension.create({
796
755
  // grip DIV — so a press on the icon gets swallowed and the block can't be
797
756
  // dragged out of / between columns. `pointer-events:none` makes every
798
757
  // press in the grip area resolve to the DIV instead.
799
- el.innerHTML = `<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor" style="pointer-events:none">
800
- <circle cx="5.5" cy="3" r="1.5"/><circle cx="10.5" cy="3" r="1.5"/>
801
- <circle cx="5.5" cy="8" r="1.5"/><circle cx="10.5" cy="8" r="1.5"/>
802
- <circle cx="5.5" cy="13" r="1.5"/><circle cx="10.5" cy="13" r="1.5"/>
803
- </svg>`;
758
+ // Tabler `grip-vertical` (the framework-wide icon set). `pointer-events:none`
759
+ // keeps every press in the grip area resolving to the DIV, not the SVG.
760
+ el.innerHTML = DRAG_HANDLE_GRIP_ICON;
761
+ const gripSvg = el.querySelector("svg");
762
+ if (gripSvg) {
763
+ gripSvg.setAttribute("width", "16");
764
+ gripSvg.setAttribute("height", "16");
765
+ gripSvg.style.pointerEvents = "none";
766
+ }
804
767
  return el;
805
768
  };
806
769
  const hideHandle = () => {