vibeman 0.0.1 → 0.0.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.
Files changed (102) hide show
  1. package/dist/index.js +5 -7
  2. package/dist/runtime/api/.tsbuildinfo +1 -1
  3. package/dist/runtime/api/agent/agent-service.d.ts +18 -19
  4. package/dist/runtime/api/agent/agent-service.js +61 -58
  5. package/dist/runtime/api/agent/ai-providers/claude-code-adapter.d.ts +2 -2
  6. package/dist/runtime/api/agent/ai-providers/claude-code-adapter.js +25 -36
  7. package/dist/runtime/api/agent/ai-providers/codex-cli-provider.d.ts +2 -0
  8. package/dist/runtime/api/agent/ai-providers/codex-cli-provider.js +109 -43
  9. package/dist/runtime/api/agent/ai-providers/types.d.ts +2 -0
  10. package/dist/runtime/api/agent/codex-cli-provider.test.js +83 -1
  11. package/dist/runtime/api/agent/parsers.d.ts +1 -0
  12. package/dist/runtime/api/agent/parsers.js +75 -8
  13. package/dist/runtime/api/agent/prompt-service.d.ts +14 -1
  14. package/dist/runtime/api/agent/prompt-service.js +123 -14
  15. package/dist/runtime/api/agent/prompt-service.test.js +230 -0
  16. package/dist/runtime/api/agent/routing-policy.d.ts +25 -42
  17. package/dist/runtime/api/agent/routing-policy.js +82 -132
  18. package/dist/runtime/api/agent/routing-policy.test.js +63 -0
  19. package/dist/runtime/api/api/routers/ai.d.ts +19 -7
  20. package/dist/runtime/api/api/routers/ai.js +9 -23
  21. package/dist/runtime/api/api/routers/executions.d.ts +4 -4
  22. package/dist/runtime/api/api/routers/executions.js +12 -21
  23. package/dist/runtime/api/api/routers/provider-config.d.ts +165 -0
  24. package/dist/runtime/api/api/routers/provider-config.js +252 -0
  25. package/dist/runtime/api/api/routers/tasks.d.ts +9 -9
  26. package/dist/runtime/api/api/routers/workflows.d.ts +23 -16
  27. package/dist/runtime/api/api/routers/workflows.js +30 -27
  28. package/dist/runtime/api/api/routers/worktrees.d.ts +4 -5
  29. package/dist/runtime/api/api/routers/worktrees.js +11 -11
  30. package/dist/runtime/api/api/trpc.d.ts +16 -16
  31. package/dist/runtime/api/index.js +2 -10
  32. package/dist/runtime/api/lib/local-config.d.ts +245 -0
  33. package/dist/runtime/api/lib/local-config.js +288 -0
  34. package/dist/runtime/api/lib/provider-detection.d.ts +59 -0
  35. package/dist/runtime/api/lib/provider-detection.js +244 -0
  36. package/dist/runtime/api/lib/server/bootstrap.d.ts +38 -0
  37. package/dist/runtime/api/lib/server/bootstrap.js +197 -0
  38. package/dist/runtime/api/lib/server/project-root.js +24 -1
  39. package/dist/runtime/api/lib/trpc/server.d.ts +143 -30
  40. package/dist/runtime/api/lib/trpc/server.js +8 -8
  41. package/dist/runtime/api/lib/trpc/ws-server.js +2 -2
  42. package/dist/runtime/api/router.d.ts +144 -31
  43. package/dist/runtime/api/router.js +9 -31
  44. package/dist/runtime/api/settings-service.js +51 -1
  45. package/dist/runtime/api/types/index.d.ts +8 -1
  46. package/dist/runtime/api/types/settings.d.ts +15 -2
  47. package/dist/runtime/api/workflows/vibing-orchestrator.d.ts +8 -3
  48. package/dist/runtime/api/workflows/vibing-orchestrator.js +214 -184
  49. package/dist/runtime/web/.next/BUILD_ID +1 -1
  50. package/dist/runtime/web/.next/app-build-manifest.json +19 -12
  51. package/dist/runtime/web/.next/app-path-routes-manifest.json +2 -1
  52. package/dist/runtime/web/.next/build-manifest.json +2 -2
  53. package/dist/runtime/web/.next/prerender-manifest.json +10 -10
  54. package/dist/runtime/web/.next/routes-manifest.json +8 -0
  55. package/dist/runtime/web/.next/server/app/.vibeman/assets/images/[...path]/route.js +1 -0
  56. package/dist/runtime/web/.next/server/app/.vibeman/assets/images/[...path]/route.js.nft.json +1 -0
  57. package/dist/runtime/web/.next/server/app/.vibeman/assets/images/[...path]/route_client-reference-manifest.js +1 -0
  58. package/dist/runtime/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  59. package/dist/runtime/web/.next/server/app/_not-found.html +2 -2
  60. package/dist/runtime/web/.next/server/app/_not-found.rsc +5 -5
  61. package/dist/runtime/web/.next/server/app/api/health/route.js +1 -1
  62. package/dist/runtime/web/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
  63. package/dist/runtime/web/.next/server/app/api/images/[...path]/route.js +1 -1
  64. package/dist/runtime/web/.next/server/app/api/images/[...path]/route_client-reference-manifest.js +1 -1
  65. package/dist/runtime/web/.next/server/app/api/upload/route.js +1 -1
  66. package/dist/runtime/web/.next/server/app/api/upload/route_client-reference-manifest.js +1 -1
  67. package/dist/runtime/web/.next/server/app/index.html +2 -2
  68. package/dist/runtime/web/.next/server/app/index.rsc +6 -6
  69. package/dist/runtime/web/.next/server/app/page.js +21 -21
  70. package/dist/runtime/web/.next/server/app/page_client-reference-manifest.js +1 -1
  71. package/dist/runtime/web/.next/server/app-paths-manifest.json +2 -1
  72. package/dist/runtime/web/.next/server/chunks/458.js +1 -1
  73. package/dist/runtime/web/.next/server/pages/404.html +2 -2
  74. package/dist/runtime/web/.next/server/pages/500.html +1 -1
  75. package/dist/runtime/web/.next/server/pages-manifest.json +1 -1
  76. package/dist/runtime/web/.next/server/server-reference-manifest.json +1 -1
  77. package/dist/runtime/web/.next/static/5_15u1WQCxN1_eHZpldCv/_buildManifest.js +1 -0
  78. package/dist/runtime/web/.next/static/chunks/{277-0142a939f08738c3.js → 823-6f371a6e829adbba.js} +1 -1
  79. package/dist/runtime/web/.next/static/chunks/app/.vibeman/assets/images/[...path]/route-751c9265a65409e5.js +1 -0
  80. package/dist/runtime/web/.next/static/chunks/app/api/health/route-751c9265a65409e5.js +1 -0
  81. package/dist/runtime/web/.next/static/chunks/app/api/images/[...path]/route-751c9265a65409e5.js +1 -0
  82. package/dist/runtime/web/.next/static/chunks/app/api/upload/route-751c9265a65409e5.js +1 -0
  83. package/dist/runtime/web/.next/static/chunks/app/{layout-dc0cfd29075b2160.js → layout-8435322f09fd0975.js} +1 -1
  84. package/dist/runtime/web/.next/static/chunks/app/page-9fe7d75095b4ccec.js +1 -0
  85. package/dist/tsconfig.tsbuildinfo +1 -1
  86. package/package.json +5 -1
  87. package/dist/runtime/api/lib/image-paste-drop-extension.d.ts +0 -26
  88. package/dist/runtime/api/lib/image-paste-drop-extension.js +0 -125
  89. package/dist/runtime/api/lib/markdown-utils.d.ts +0 -8
  90. package/dist/runtime/api/lib/markdown-utils.js +0 -282
  91. package/dist/runtime/api/lib/markdown-utils.test.js +0 -348
  92. package/dist/runtime/api/lib/tiptap-utils.clamp-selection.test.js +0 -27
  93. package/dist/runtime/api/lib/tiptap-utils.d.ts +0 -130
  94. package/dist/runtime/api/lib/tiptap-utils.js +0 -327
  95. package/dist/runtime/web/.next/static/1HR8N0rJkCvFRtbTPJMyH/_buildManifest.js +0 -1
  96. package/dist/runtime/web/.next/static/chunks/app/api/health/route-105a61ae865ba536.js +0 -1
  97. package/dist/runtime/web/.next/static/chunks/app/api/images/[...path]/route-105a61ae865ba536.js +0 -1
  98. package/dist/runtime/web/.next/static/chunks/app/api/upload/route-105a61ae865ba536.js +0 -1
  99. package/dist/runtime/web/.next/static/chunks/app/page-f34a8b196b18850b.js +0 -1
  100. /package/dist/runtime/api/{lib/markdown-utils.test.d.ts → agent/prompt-service.test.d.ts} +0 -0
  101. /package/dist/runtime/api/{lib/tiptap-utils.clamp-selection.test.d.ts → agent/routing-policy.test.d.ts} +0 -0
  102. /package/dist/runtime/web/.next/static/{1HR8N0rJkCvFRtbTPJMyH → 5_15u1WQCxN1_eHZpldCv}/_ssgManifest.js +0 -0
@@ -1,282 +0,0 @@
1
- import { marked } from 'marked';
2
- import TurndownService from 'turndown';
3
- import { gfm } from 'turndown-plugin-gfm';
4
- marked.setOptions({
5
- gfm: true,
6
- breaks: false,
7
- });
8
- const turndownService = new TurndownService({
9
- headingStyle: 'atx',
10
- hr: '---',
11
- bulletListMarker: '-',
12
- codeBlockStyle: 'fenced',
13
- fence: '```',
14
- });
15
- // Use GitHub Flavored Markdown plugin
16
- turndownService.use(gfm);
17
- // Custom rule for regular list items to use single space after dash
18
- turndownService.addRule('listItem', {
19
- filter: (node) => {
20
- return (node.nodeName === 'LI' && !node.getAttribute('data-type') // Not a task item
21
- );
22
- },
23
- replacement: (content, node, _options) => {
24
- content = content
25
- .replace(/^\n+/, '') // Remove leading newlines
26
- .replace(/\n+$/, '\n') // Replace trailing newlines with just one
27
- .replace(/\n/gm, '\n '); // Indent subsequent lines
28
- let prefix = '- '; // Use single space after dash
29
- const parent = node.parentNode;
30
- if (parent && parent.nodeName === 'OL') {
31
- const index = Array.prototype.indexOf.call(parent.children, node);
32
- const start = parent.getAttribute('start');
33
- const startIndex = start ? parseInt(start, 10) - 1 : 0;
34
- prefix = startIndex + index + 1 + '. ';
35
- }
36
- return prefix + content + (node.nextSibling && !/\n$/.test(content) ? '\n' : '');
37
- },
38
- });
39
- // Custom rule for TipTap TaskItems
40
- turndownService.addRule('tiptapTaskItem', {
41
- filter: (node, _options) => {
42
- return (node.nodeName === 'LI' &&
43
- node.getAttribute('data-type') === 'taskItem' &&
44
- node.getAttribute('data-checked') !== null);
45
- },
46
- replacement: (content, node) => {
47
- const element = node;
48
- const isChecked = element.getAttribute('data-checked') === 'true';
49
- const checkbox = isChecked ? '[x]' : '[ ]';
50
- // Extract text content from the div > p structure
51
- const contentDiv = element.querySelector('div');
52
- const textContent = contentDiv ? contentDiv.textContent?.trim() || '' : content.trim();
53
- return `- ${checkbox} ${textContent}`;
54
- },
55
- });
56
- // Custom rule for TipTap TaskLists - handles nested structure properly
57
- turndownService.addRule('tiptapTaskList', {
58
- filter: (node) => {
59
- return node.nodeName === 'UL' && node.getAttribute('data-type') === 'taskList';
60
- },
61
- replacement: (content, node) => {
62
- // Process only direct child li elements to preserve nesting
63
- const directItems = Array.from(node.children).filter((child) => child.nodeName === 'LI' && child.getAttribute('data-type') === 'taskItem');
64
- if (directItems.length === 0)
65
- return content;
66
- const processTaskItem = (li, indent = '') => {
67
- const isChecked = li.getAttribute('data-checked') === 'true';
68
- const checkbox = isChecked ? '[x]' : '[ ]';
69
- // Get text content from the div, excluding nested lists
70
- const contentDiv = li.querySelector('div');
71
- let textContent = '';
72
- if (contentDiv) {
73
- // Clone the div to avoid modifying the original
74
- const tempDiv = contentDiv.cloneNode(true);
75
- // Remove any nested ul elements from the clone to get just the text
76
- const nestedLists = Array.from(tempDiv.querySelectorAll('ul'));
77
- nestedLists.forEach((list) => {
78
- if (list instanceof Element) {
79
- list.remove();
80
- }
81
- });
82
- textContent = tempDiv.textContent?.trim() || '';
83
- }
84
- let result = `${indent}- ${checkbox} ${textContent}`;
85
- // Process nested task lists
86
- const nestedList = li.querySelector('ul[data-type="taskList"]');
87
- if (nestedList) {
88
- const nestedItems = Array.from(nestedList.children).filter((child) => child.nodeName === 'LI' && child.getAttribute('data-type') === 'taskItem');
89
- nestedItems.forEach((nestedLi) => {
90
- if (nestedLi instanceof Element) {
91
- result += '\n' + processTaskItem(nestedLi, indent + ' ');
92
- }
93
- });
94
- }
95
- return result;
96
- };
97
- return '\n' + directItems.map((li) => processTaskItem(li)).join('\n') + '\n';
98
- },
99
- });
100
- // Override paragraph handling to preserve paragraph breaks
101
- // Ensure there is a blank line between paragraphs in markdown
102
- turndownService.addRule('paragraph', {
103
- filter: 'p',
104
- replacement: (content) => {
105
- return '\n\n' + content.trim() + '\n\n';
106
- },
107
- });
108
- /**
109
- * Clean up HTML by removing <p> tags within <li> elements under <ul> or <ol> lists
110
- */
111
- function cleanupListParagraphs(html) {
112
- if (!html?.trim())
113
- return html;
114
- // Use DOM parsing to handle nested structures properly
115
- if (typeof window !== 'undefined') {
116
- const parser = new DOMParser();
117
- const doc = parser.parseFromString(html, 'text/html');
118
- // Find all <li> elements within <ul> or <ol>
119
- const listItems = doc.querySelectorAll('ul > li, ol > li');
120
- listItems.forEach((li) => {
121
- // Find all <p> tags directly within this <li>
122
- const paragraphs = Array.from(li.querySelectorAll('p'));
123
- paragraphs.forEach((p) => {
124
- // Only remove <p> if it's a direct child or within the list item structure
125
- if (p.parentElement === li ||
126
- (p.parentElement && ['UL', 'OL', 'LI'].includes(p.parentElement.nodeName))) {
127
- // Replace <p> with its content
128
- const content = p.innerHTML;
129
- const textNode = doc.createDocumentFragment();
130
- textNode.appendChild(doc.createTextNode(content));
131
- // For better HTML structure, we'll replace with the content directly
132
- p.outerHTML = content;
133
- }
134
- });
135
- });
136
- return doc.body.innerHTML;
137
- }
138
- // Fallback for server-side: use regex approach (less robust but works)
139
- // Remove <p> and </p> tags within <li> elements
140
- return html
141
- .replace(/<li([^>]*)><p>/g, '<li$1>')
142
- .replace(/<\/p><\/li>/g, '</li>')
143
- .replace(/<\/p><ul>/g, '<ul>')
144
- .replace(/<\/p><ol>/g, '<ol>')
145
- .replace(/<\/ul><p>/g, '</ul>')
146
- .replace(/<\/ol><p>/g, '</ol>');
147
- }
148
- /**
149
- * Convert GFM checkbox HTML to Tiptap TaskItem format
150
- */
151
- function convertToTiptapTaskItems(html) {
152
- if (!html?.trim())
153
- return html;
154
- // Use DOM parsing for better accuracy
155
- if (typeof window !== 'undefined') {
156
- const parser = new DOMParser();
157
- const doc = parser.parseFromString(html, 'text/html');
158
- // Process all ul elements, starting from the deepest nested ones
159
- const lists = Array.from(doc.querySelectorAll('ul')).reverse();
160
- lists.forEach((ul) => {
161
- let hasTaskItems = false;
162
- // Only process direct children li elements to preserve nesting
163
- const directListItems = Array.from(ul.children).filter((child) => child.nodeName === 'LI');
164
- directListItems.forEach((li) => {
165
- const checkbox = li.querySelector('input[type="checkbox"]');
166
- if (checkbox) {
167
- hasTaskItems = true;
168
- // Get the checkbox state
169
- const isChecked = checkbox.hasAttribute('checked');
170
- // Set TaskItem attributes
171
- li.setAttribute('data-type', 'taskItem');
172
- li.setAttribute('data-checked', isChecked ? 'true' : 'false');
173
- // Get the text content, preserving nested structure
174
- const clonedLi = li.cloneNode(true);
175
- // Remove the checkbox from the clone
176
- const clonedCheckbox = clonedLi.querySelector('input[type="checkbox"]');
177
- if (clonedCheckbox) {
178
- clonedCheckbox.remove();
179
- }
180
- // Extract nested lists to preserve them
181
- const nestedLists = Array.from(clonedLi.querySelectorAll('ul'));
182
- const nestedListsHTML = nestedLists.map((list) => list.outerHTML);
183
- // Remove nested lists temporarily to get just the text content
184
- nestedLists.forEach((list) => {
185
- if (list instanceof Element) {
186
- list.remove();
187
- }
188
- });
189
- // Get the text content
190
- const textContent = clonedLi.textContent?.trim() || '';
191
- // Create the proper TipTap structure
192
- let innerHTML = `
193
- <label contenteditable="false">
194
- <input type="checkbox" ${isChecked ? 'checked' : ''}>
195
- <span></span>
196
- </label>
197
- <div>
198
- <p>${textContent}</p>`;
199
- // Add back any nested lists, ensuring they have the correct data-type
200
- if (nestedListsHTML.length > 0) {
201
- nestedListsHTML.forEach((nestedHTML) => {
202
- // Make sure nested task lists have the correct data-type attribute
203
- let processedHTML = nestedHTML;
204
- if (nestedHTML.includes('data-type="taskItem"') &&
205
- !nestedHTML.includes('data-type="taskList"')) {
206
- processedHTML = nestedHTML.replace('<ul>', '<ul data-type="taskList">');
207
- }
208
- innerHTML += processedHTML;
209
- });
210
- }
211
- innerHTML += '</div>';
212
- li.innerHTML = innerHTML.trim();
213
- }
214
- });
215
- // If this ul contains task items, mark it as a taskList
216
- if (hasTaskItems) {
217
- ul.setAttribute('data-type', 'taskList');
218
- }
219
- });
220
- return doc.body.innerHTML;
221
- }
222
- // Fallback for server-side: use regex approach (less robust)
223
- return (html
224
- // First pass: Convert list items with checkboxes
225
- .replace(/<li>(\s*)<input\s+([^>]*?)type="checkbox"([^>]*?)>([^<]*)/gi, (match, leadingSpace, beforeType, afterType, textContent) => {
226
- const isChecked = beforeType.includes('checked') || afterType.includes('checked');
227
- const cleanText = textContent.trim();
228
- return `<li data-type="taskItem" data-checked="${isChecked ? 'true' : 'false'}">
229
- <label contenteditable="false">
230
- <input type="checkbox" ${isChecked ? 'checked' : ''}>
231
- <span></span>
232
- </label>
233
- <div>
234
- <p>${cleanText}</p>
235
- </div>`;
236
- })
237
- // Second pass: Mark parent ul as taskList if it contains taskItems
238
- .replace(/<ul>(\s*(?:<li[^>]*data-type="taskItem"[^>]*>[\s\S]*?<\/li>\s*)+)<\/ul>/gi, '<ul data-type="taskList">$1</ul>'));
239
- }
240
- /**
241
- * Pre-process markdown to ensure proper task list item boundaries
242
- */
243
- function preprocessMarkdownTaskItems(markdown) {
244
- if (!markdown?.trim())
245
- return markdown;
246
- // Split markdown into lines
247
- const lines = markdown.split('\n');
248
- const processedLines = [];
249
- for (let i = 0; i < lines.length; i++) {
250
- const line = lines[i];
251
- const nextLine = i + 1 < lines.length ? lines[i + 1] : '';
252
- processedLines.push(line);
253
- // If current line is a task item and next line is not empty and not indented
254
- // and not another list item, add a blank line to separate them
255
- if (line.trim().match(/^-\s+\[[ x]\]/) &&
256
- nextLine.trim() &&
257
- !nextLine.match(/^-\s+/) &&
258
- !nextLine.match(/^\s{2,}/)) {
259
- processedLines.push(''); // Add blank line
260
- }
261
- }
262
- return processedLines.join('\n');
263
- }
264
- /**
265
- * Convert markdown to HTML for Tiptap editor
266
- */
267
- export function markdownToHtml(markdown) {
268
- if (!markdown?.trim())
269
- return '';
270
- const preprocessedMarkdown = preprocessMarkdownTaskItems(markdown);
271
- const html = marked(preprocessedMarkdown);
272
- return convertToTiptapTaskItems(html);
273
- }
274
- /**
275
- * Convert HTML to markdown for storage
276
- */
277
- export function htmlToMarkdown(html) {
278
- if (!html?.trim())
279
- return '';
280
- const cleanedHtml = cleanupListParagraphs(html);
281
- return turndownService.turndown(cleanedHtml);
282
- }
@@ -1,348 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { markdownToHtml, htmlToMarkdown } from './markdown-utils.js';
3
- describe('markdown-utils', () => {
4
- describe('markdownToHtml', () => {
5
- it('should return empty string for empty input', () => {
6
- expect(markdownToHtml('')).toBe('');
7
- expect(markdownToHtml(' ')).toBe('');
8
- expect(markdownToHtml(null)).toBe('');
9
- expect(markdownToHtml(undefined)).toBe('');
10
- });
11
- it('should convert task item to HTML', () => {
12
- const markdown = `- [ ] Task item
13
- - [x] Checked task`;
14
- const html = markdownToHtml(markdown);
15
- // Check for proper TipTap TaskList structure
16
- expect(html).toContain('data-type="taskList"');
17
- expect(html).toContain('data-type="taskItem"');
18
- expect(html).toContain('data-checked="false"');
19
- expect(html).toContain('data-checked="true"');
20
- expect(html).toContain('<label contenteditable="false">');
21
- expect(html).toContain('<input type="checkbox" >');
22
- expect(html).toContain('<input type="checkbox" checked>');
23
- expect(html).toContain('<p>Task item</p>');
24
- expect(html).toContain('<p>Checked task</p>');
25
- });
26
- it('should convert basic markdown to HTML', () => {
27
- const markdown = '# Heading 1\n\nThis is a paragraph.\n\n**Bold text** and *italic text*.';
28
- const html = markdownToHtml(markdown);
29
- expect(html).toContain('<h1>Heading 1</h1>');
30
- expect(html).toContain('<p>This is a paragraph.</p>');
31
- expect(html).toContain('<strong>Bold text</strong>');
32
- expect(html).toContain('<em>italic text</em>');
33
- });
34
- // it('should convert simple task lists to HTML with data-type attribute', () => {
35
- // const markdown = '- [ ] Unchecked task\n- [x] Checked task';
36
- // const html = markdownToHtml(markdown);
37
- // expect(html).toContain('data-type="taskItem"');
38
- // expect(html).toContain('<input type="checkbox" disabled>');
39
- // expect(html).toContain('<input type="checkbox" checked disabled>');
40
- // expect(html).toContain('Unchecked task');
41
- // expect(html).toContain('Checked task');
42
- // });
43
- // it('should handle two-level nested task lists', () => {
44
- // const markdown = `- [ ] Parent task
45
- // - [ ] Child task 1
46
- // - [x] Child task 2`;
47
- // const html = markdownToHtml(markdown);
48
- // expect(html).toContain('Parent task');
49
- // expect(html).toContain('Child task 1');
50
- // expect(html).toContain('Child task 2');
51
- // expect(html).toMatch(/<ul>[\s\S]*<ul>/); // Nested ul elements
52
- // });
53
- // it('should handle three-level nested task lists', () => {
54
- // const markdown = `- [ ] Level 1
55
- // - [ ] Level 2
56
- // - [x] Level 3`;
57
- // const html = markdownToHtml(markdown);
58
- // expect(html).toContain('Level 1');
59
- // expect(html).toContain('Level 2');
60
- // expect(html).toContain('Level 3');
61
- // // Should have at least 3 ul elements for 3 levels
62
- // const ulCount = (html.match(/<ul>/g) || []).length;
63
- // expect(ulCount).toBeGreaterThanOrEqual(3);
64
- // });
65
- // it('should handle deeply nested task lists (5 levels)', () => {
66
- // const markdown = `- [ ] Level 1
67
- // - [ ] Level 2
68
- // - [ ] Level 3
69
- // - [ ] Level 4
70
- // - [x] Level 5`;
71
- // const html = markdownToHtml(markdown);
72
- // expect(html).toContain('Level 1');
73
- // expect(html).toContain('Level 5');
74
- // const ulCount = (html.match(/<ul>/g) || []).length;
75
- // expect(ulCount).toBeGreaterThanOrEqual(5);
76
- // });
77
- // it('should handle mixed task lists and regular lists', () => {
78
- // const markdown = `- [ ] Task item
79
- // - Regular nested item
80
- // - [ ] Nested task
81
- // - Another regular item`;
82
- // const html = markdownToHtml(markdown);
83
- // expect(html).toContain('Task item');
84
- // expect(html).toContain('Regular nested item');
85
- // expect(html).toContain('Nested task');
86
- // expect(html).toContain('Another regular item');
87
- // });
88
- // it('should handle task lists with complex content', () => {
89
- // const markdown = `- [ ] Task with **bold** and *italic*
90
- // - [ ] Task with \`code\`
91
- // - [ ] Task with [link](https://example.com)`;
92
- // const html = markdownToHtml(markdown);
93
- // // Complex markdown inside task items may not be parsed yet
94
- // // This is a known limitation - task item text is not re-parsed for markdown
95
- // expect(html).toContain('Task with **bold** and *italic*');
96
- // expect(html).toContain('Task with `code`');
97
- // expect(html).toContain('Task with [link](https://example.com)');
98
- // });
99
- it('should handle numbered lists', () => {
100
- const markdown = '1. First item\n2. Second item\n3. Third item';
101
- const html = markdownToHtml(markdown);
102
- expect(html).toContain('<ol>');
103
- expect(html).toContain('First item');
104
- expect(html).toContain('Second item');
105
- });
106
- it('should handle code blocks', () => {
107
- const markdown = '```javascript\nconst hello = "world";\n```';
108
- const html = markdownToHtml(markdown);
109
- expect(html).toContain('<pre>');
110
- expect(html).toContain('<code');
111
- expect(html).toContain('hello');
112
- });
113
- it('should handle blockquotes', () => {
114
- const markdown = '> This is a quote';
115
- const html = markdownToHtml(markdown);
116
- expect(html).toContain('<blockquote>');
117
- expect(html).toContain('This is a quote');
118
- });
119
- });
120
- describe('htmlToMarkdown', () => {
121
- it('should return empty string for empty input', () => {
122
- expect(htmlToMarkdown('')).toBe('');
123
- expect(htmlToMarkdown(' ')).toBe('');
124
- expect(htmlToMarkdown(null)).toBe('');
125
- expect(htmlToMarkdown(undefined)).toBe('');
126
- });
127
- it('should convert basic HTML to markdown', () => {
128
- const html = '<h1>Heading 1</h1><p>This is a paragraph.</p><p><strong>Bold text</strong> and <em>italic text</em>.</p>';
129
- const markdown = htmlToMarkdown(html);
130
- expect(markdown).toContain('# Heading 1');
131
- expect(markdown).toContain('This is a paragraph.');
132
- expect(markdown).toContain('**Bold text**');
133
- // Turndown uses underscore for italic by default
134
- expect(markdown).toContain('_italic text_');
135
- });
136
- it('should convert task list HTML back to markdown', () => {
137
- const html = `<ul>
138
- <li data-type="taskItem"><input type="checkbox" disabled> Unchecked task</li>
139
- <li data-type="taskItem"><input type="checkbox" checked disabled> Checked task</li>
140
- </ul>`;
141
- const markdown = htmlToMarkdown(html);
142
- expect(markdown).toContain('- [ ] Unchecked task');
143
- expect(markdown).toContain('- [x] Checked task');
144
- });
145
- it('should preserve two-level nested task list indentation', () => {
146
- const html = `<ul>
147
- <li data-type="taskItem"><input type="checkbox" disabled> Parent task
148
- <ul>
149
- <li data-type="taskItem"><input type="checkbox" disabled> Child task 1</li>
150
- <li data-type="taskItem"><input type="checkbox" checked disabled> Child task 2</li>
151
- </ul>
152
- </li>
153
- </ul>`;
154
- const markdown = htmlToMarkdown(html);
155
- expect(markdown).toContain('- [ ] Parent task');
156
- expect(markdown).toContain(' - [ ] Child task 1');
157
- expect(markdown).toContain(' - [x] Child task 2');
158
- });
159
- it('should preserve three-level nested task list indentation', () => {
160
- const html = `<ul>
161
- <li data-type="taskItem"><input type="checkbox" disabled> Level 1
162
- <ul>
163
- <li data-type="taskItem"><input type="checkbox" disabled> Level 2
164
- <ul>
165
- <li data-type="taskItem"><input type="checkbox" checked disabled> Level 3</li>
166
- </ul>
167
- </li>
168
- </ul>
169
- </li>
170
- </ul>`;
171
- const markdown = htmlToMarkdown(html);
172
- expect(markdown).toContain('- [ ] Level 1');
173
- expect(markdown).toContain(' - [ ] Level 2');
174
- expect(markdown).toContain(' - [x] Level 3');
175
- });
176
- it('should handle regular nested lists', () => {
177
- const html = `<ul>
178
- <li>Parent item
179
- <ul>
180
- <li>Child item 1</li>
181
- <li>Child item 2</li>
182
- </ul>
183
- </li>
184
- </ul>`;
185
- const markdown = htmlToMarkdown(html);
186
- // Turndown may add extra spacing in lists
187
- expect(markdown).toContain('Parent item');
188
- expect(markdown).toContain('Child item 1');
189
- expect(markdown).toContain('Child item 2');
190
- // Check for proper nesting with indentation
191
- expect(markdown).toMatch(/.*Child item 1/);
192
- });
193
- it('should handle numbered lists', () => {
194
- const html = '<ol><li>First</li><li>Second</li><li>Third</li></ol>';
195
- const markdown = htmlToMarkdown(html);
196
- // Turndown may add extra spacing after the period
197
- expect(markdown).toMatch(/1\.\s+First/);
198
- expect(markdown).toMatch(/2\.\s+Second/);
199
- expect(markdown).toMatch(/3\.\s+Third/);
200
- });
201
- it('should handle code blocks', () => {
202
- const html = '<pre><code class="language-javascript">const hello = "world";</code></pre>';
203
- const markdown = htmlToMarkdown(html);
204
- expect(markdown).toContain('```');
205
- expect(markdown).toContain('const hello = "world";');
206
- });
207
- it('should handle inline code', () => {
208
- const html = '<p>Use <code>npm install</code> to install.</p>';
209
- const markdown = htmlToMarkdown(html);
210
- expect(markdown).toContain('`npm install`');
211
- });
212
- it('should handle links', () => {
213
- const html = '<p>Visit <a href="https://example.com">example</a></p>';
214
- const markdown = htmlToMarkdown(html);
215
- expect(markdown).toContain('[example](https://example.com)');
216
- });
217
- it('should handle blockquotes', () => {
218
- const html = '<blockquote><p>This is a quote</p></blockquote>';
219
- const markdown = htmlToMarkdown(html);
220
- expect(markdown).toContain('> This is a quote');
221
- });
222
- it('should handle horizontal rules', () => {
223
- const html = '<hr>';
224
- const markdown = htmlToMarkdown(html);
225
- expect(markdown).toContain('---');
226
- });
227
- it('should render list item', () => {
228
- const html = '<li>Test</li>';
229
- const markdown = htmlToMarkdown(html);
230
- expect(markdown).toContain('- Test');
231
- });
232
- });
233
- // describe('Round-trip conversion', () => {
234
- // it('should preserve simple task lists through round-trip conversion', () => {
235
- // const original = '- [ ] Task 1\n- [x] Task 2';
236
- // const html = markdownToHtml(original);
237
- // const result = htmlToMarkdown(html);
238
- // expect(result).toContain('- [ ] Task 1');
239
- // expect(result).toContain('- [x] Task 2');
240
- // });
241
- // it('should preserve two-level nested task lists through round-trip conversion', () => {
242
- // const original = `- [ ] Parent task
243
- // - [ ] Child task 1
244
- // - [x] Child task 2`;
245
- // const html = markdownToHtml(original);
246
- // const result = htmlToMarkdown(html);
247
- // expect(result).toContain('- [ ] Parent task');
248
- // expect(result).toContain(' - [ ] Child task 1');
249
- // expect(result).toContain(' - [x] Child task 2');
250
- // });
251
- // it('should preserve three-level nested task lists through round-trip conversion', () => {
252
- // const original = `- [ ] Level 1
253
- // - [ ] Level 2
254
- // - [x] Level 3`;
255
- // const html = markdownToHtml(original);
256
- // const result = htmlToMarkdown(html);
257
- // expect(result).toContain('- [ ] Level 1');
258
- // expect(result).toContain(' - [ ] Level 2');
259
- // expect(result).toContain(' - [x] Level 3');
260
- // });
261
- // it('should preserve complex nested task list structure', () => {
262
- // const original = `- [ ] Task 1
263
- // - [ ] Task 1.1
264
- // - [x] Task 1.2
265
- // - [ ] Task 1.2.1
266
- // - [ ] Task 1.2.2
267
- // - [ ] Task 2
268
- // - [ ] Task 2.1`;
269
- // const html = markdownToHtml(original);
270
- // const result = htmlToMarkdown(html);
271
- // // Check all items are present
272
- // expect(result).toContain('- [ ] Task 1');
273
- // expect(result).toContain(' - [ ] Task 1.1');
274
- // expect(result).toContain(' - [x] Task 1.2');
275
- // expect(result).toContain(' - [ ] Task 1.2.1');
276
- // expect(result).toContain(' - [ ] Task 1.2.2');
277
- // expect(result).toContain('- [ ] Task 2');
278
- // expect(result).toContain(' - [ ] Task 2.1');
279
- // });
280
- // it('should handle task list with description formatting', () => {
281
- // const original = `- [ ] Add button to trigger task refinement
282
- // - [ ] **Description**: Clear explanation
283
- // - [ ] *Acceptance Criteria*: Tests pass
284
- // - [ ] \`Technical Details\`: Use React`;
285
- // const html = markdownToHtml(original);
286
- // const result = htmlToMarkdown(html);
287
- // expect(result).toContain('- [ ] Add button to trigger task refinement');
288
- // // Markdown inside task items may not be preserved through round-trip
289
- // // This is a known limitation of the current implementation
290
- // expect(result).toContain('Description');
291
- // expect(result).toContain('Acceptance Criteria');
292
- // expect(result).toContain('Technical Details');
293
- // });
294
- // });
295
- describe('Edge cases', () => {
296
- it('should handle malformed task list syntax gracefully', () => {
297
- const markdown = '- [] Invalid task\n-[x] Another invalid\n- [ ] Valid task';
298
- const html = markdownToHtml(markdown);
299
- // Should still parse what it can
300
- expect(html).toContain('Valid task');
301
- });
302
- // it('should handle empty task descriptions', () => {
303
- // const markdown = '- [ ] \n- [x] ';
304
- // const html = markdownToHtml(markdown);
305
- // expect(html).toContain('data-type="taskItem"');
306
- // });
307
- // it('should handle very deeply nested lists (10 levels)', () => {
308
- // let markdown = '- [ ] L1';
309
- // for (let i = 2; i <= 10; i++) {
310
- // markdown += '\n' + ' '.repeat(i - 1) + `- [ ] L${i}`;
311
- // }
312
- // const html = markdownToHtml(markdown);
313
- // const result = htmlToMarkdown(html);
314
- // expect(result).toContain('- [ ] L1');
315
- // expect(result).toContain(' '.repeat(9) + '- [ ] L10');
316
- // });
317
- it('should handle mixed content in list items', () => {
318
- const html = `<ul>
319
- <li data-type="taskItem"><input type="checkbox" disabled> Task with <strong>bold</strong> and <a href="#">link</a></li>
320
- </ul>`;
321
- const markdown = htmlToMarkdown(html);
322
- expect(markdown).toContain('- [ ] Task with **bold** and [link](#)');
323
- });
324
- it('should strip extra whitespace properly', () => {
325
- const html = `<ul>
326
- <li data-type="taskItem"> <input type="checkbox" disabled> Task with spaces </li>
327
- </ul>`;
328
- const markdown = htmlToMarkdown(html);
329
- expect(markdown.trim()).toBe('- [ ] Task with spaces');
330
- });
331
- it('should handle nested task lists properly', () => {
332
- const nestedMarkdown = `- [ ] Parent task with nested items:
333
- - [ ] Child task 1
334
- - [x] Child task 2`;
335
- const html = markdownToHtml(nestedMarkdown);
336
- // Check that HTML contains proper nesting structure
337
- expect(html).toContain('data-type="taskList"');
338
- expect(html).toContain('data-type="taskItem"');
339
- expect(html).toContain('data-checked="false"');
340
- expect(html).toContain('data-checked="true"');
341
- expect(html).toContain('Parent task with nested items:');
342
- expect(html).toContain('Child task 1');
343
- expect(html).toContain('Child task 2');
344
- // Verify the nested structure is present in HTML
345
- expect(html).toMatch(/<li[^>]*data-type="taskItem"[^>]*>.*<ul>.*<li[^>]*data-type="taskItem"/s);
346
- });
347
- });
348
- });
@@ -1,27 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { clampSelectionPos } from './tiptap-utils.js';
3
- describe('clampSelectionPos', () => {
4
- it('keeps positions within valid range', () => {
5
- const res = clampSelectionPos(5, 8, 20);
6
- expect(res).toEqual({ from: 5, to: 8 });
7
- });
8
- it('clamps underflow to 1', () => {
9
- const res = clampSelectionPos(-10, 0, 20);
10
- expect(res).toEqual({ from: 1, to: 1 });
11
- });
12
- it('clamps overflow to doc.nodeSize - 2', () => {
13
- const res = clampSelectionPos(100, 200, 20);
14
- // maxPos = 18 for nodeSize 20
15
- expect(res).toEqual({ from: 18, to: 18 });
16
- });
17
- it('orders from/to when anchor > head', () => {
18
- const res = clampSelectionPos(10, 5, 20);
19
- expect(res).toEqual({ from: 5, to: 10 });
20
- });
21
- it('handles tiny documents safely', () => {
22
- const res0 = clampSelectionPos(5, 6, 0);
23
- expect(res0).toEqual({ from: 1, to: 1 });
24
- const res1 = clampSelectionPos(2, 3, 1);
25
- expect(res1).toEqual({ from: 1, to: 1 });
26
- });
27
- });