@aprovan/patchwork-editor 0.1.2-dev.03aaf5b → 0.1.2-dev.f456953

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.
@@ -1,5 +1,5 @@
1
- import { applyDiffs, hasDiffBlocks, parseEditResponse } from '../../lib/diff';
2
- import type { EditRequest, EditResponse } from './types';
1
+ import { applyDiffs, hasDiffBlocks, parseEditResponse } from "../../lib/diff";
2
+ import type { EditRequest, EditResponse } from "./types";
3
3
 
4
4
  export interface EditApiOptions {
5
5
  endpoint?: string;
@@ -12,22 +12,27 @@ export async function sendEditRequest(
12
12
  request: EditRequest,
13
13
  options: EditApiOptions = {},
14
14
  ): Promise<EditResponse> {
15
- const { endpoint = '/api/edit', onProgress, sanitize = true } = options;
15
+ const { endpoint = "/api/edit", onProgress, sanitize = true } = options;
16
16
 
17
17
  const response = await fetch(endpoint, {
18
- method: 'POST',
19
- headers: { 'Content-Type': 'application/json' },
18
+ method: "POST",
19
+ headers: { "Content-Type": "application/json" },
20
20
  body: JSON.stringify(request),
21
21
  });
22
22
 
23
23
  if (!response.ok) {
24
- throw new Error('Edit request failed');
24
+ throw new Error("Edit request failed");
25
25
  }
26
26
 
27
27
  const text = await streamResponse(response, onProgress);
28
28
 
29
29
  if (!hasDiffBlocks(text)) {
30
- throw new Error('No valid diffs in response');
30
+ // No diffs return the original code unchanged with the response as summary
31
+ return {
32
+ newCode: request.code,
33
+ summary: text.trim(),
34
+ progressNotes: [],
35
+ };
31
36
  }
32
37
 
33
38
  const parsed = parseEditResponse(text);
@@ -37,7 +42,7 @@ export async function sendEditRequest(
37
42
  // Provide detailed context about failed diffs for better error feedback
38
43
  const failedDetails = result.failed
39
44
  .map((f, i) => `[${i + 1}] "${f}"`)
40
- .join('\n');
45
+ .join("\n");
41
46
  throw new Error(
42
47
  `Failed to apply ${parsed.diffs.length} diff(s). None of the SEARCH blocks matched the code.\n\nFailed searches:\n${failedDetails}\n\nThis usually means the code has changed or the SEARCH text doesn't match exactly.`,
43
48
  );
@@ -66,7 +71,7 @@ async function streamResponse(
66
71
  }
67
72
 
68
73
  const decoder = new TextDecoder();
69
- let fullText = '';
74
+ let fullText = "";
70
75
  const emittedNotes = new Set<string>();
71
76
 
72
77
  let done = false;
@@ -1,4 +1,4 @@
1
- export type FileCategory = 'compilable' | 'text' | 'media' | 'binary';
1
+ export type FileCategory = "compilable" | "text" | "media" | "binary";
2
2
 
3
3
  export interface FileTypeInfo {
4
4
  category: FileCategory;
@@ -6,55 +6,75 @@ export interface FileTypeInfo {
6
6
  mimeType: string;
7
7
  }
8
8
 
9
- const COMPILABLE_EXTENSIONS = ['.tsx', '.jsx', '.ts', '.js'];
10
- const MEDIA_EXTENSIONS = ['.svg', '.png', '.jpg', '.jpeg', '.gif', '.webp', '.mp4', '.mov', '.webm'];
11
- const TEXT_EXTENSIONS = ['.json', '.yaml', '.yml', '.md', '.txt', '.css', '.html', '.xml', '.toml'];
9
+ const COMPILABLE_EXTENSIONS = [".tsx", ".jsx", ".ts", ".js"];
10
+ const MEDIA_EXTENSIONS = [
11
+ ".svg",
12
+ ".png",
13
+ ".jpg",
14
+ ".jpeg",
15
+ ".gif",
16
+ ".webp",
17
+ ".mp4",
18
+ ".mov",
19
+ ".webm",
20
+ ];
21
+ const TEXT_EXTENSIONS = [
22
+ ".json",
23
+ ".yaml",
24
+ ".yml",
25
+ ".md",
26
+ ".txt",
27
+ ".css",
28
+ ".html",
29
+ ".xml",
30
+ ".toml",
31
+ ];
12
32
 
13
33
  const EXTENSION_TO_LANGUAGE: Record<string, string> = {
14
- '.tsx': 'tsx',
15
- '.jsx': 'jsx',
16
- '.ts': 'typescript',
17
- '.js': 'javascript',
18
- '.json': 'json',
19
- '.yaml': 'yaml',
20
- '.yml': 'yaml',
21
- '.md': 'markdown',
22
- '.txt': 'text',
23
- '.css': 'css',
24
- '.html': 'html',
25
- '.xml': 'xml',
26
- '.toml': 'toml',
27
- '.svg': 'xml',
34
+ ".tsx": "tsx",
35
+ ".jsx": "jsx",
36
+ ".ts": "typescript",
37
+ ".js": "javascript",
38
+ ".json": "json",
39
+ ".yaml": "yaml",
40
+ ".yml": "yaml",
41
+ ".md": "markdown",
42
+ ".txt": "text",
43
+ ".css": "css",
44
+ ".html": "html",
45
+ ".xml": "xml",
46
+ ".toml": "toml",
47
+ ".svg": "xml",
28
48
  };
29
49
 
30
50
  const EXTENSION_TO_MIME: Record<string, string> = {
31
- '.tsx': 'text/typescript-jsx',
32
- '.jsx': 'text/javascript-jsx',
33
- '.ts': 'text/typescript',
34
- '.js': 'text/javascript',
35
- '.json': 'application/json',
36
- '.yaml': 'text/yaml',
37
- '.yml': 'text/yaml',
38
- '.md': 'text/markdown',
39
- '.txt': 'text/plain',
40
- '.css': 'text/css',
41
- '.html': 'text/html',
42
- '.xml': 'application/xml',
43
- '.toml': 'text/toml',
44
- '.svg': 'image/svg+xml',
45
- '.png': 'image/png',
46
- '.jpg': 'image/jpeg',
47
- '.jpeg': 'image/jpeg',
48
- '.gif': 'image/gif',
49
- '.webp': 'image/webp',
50
- '.mp4': 'video/mp4',
51
- '.mov': 'video/quicktime',
52
- '.webm': 'video/webm',
51
+ ".tsx": "text/typescript-jsx",
52
+ ".jsx": "text/javascript-jsx",
53
+ ".ts": "text/typescript",
54
+ ".js": "text/javascript",
55
+ ".json": "application/json",
56
+ ".yaml": "text/yaml",
57
+ ".yml": "text/yaml",
58
+ ".md": "text/markdown",
59
+ ".txt": "text/plain",
60
+ ".css": "text/css",
61
+ ".html": "text/html",
62
+ ".xml": "application/xml",
63
+ ".toml": "text/toml",
64
+ ".svg": "image/svg+xml",
65
+ ".png": "image/png",
66
+ ".jpg": "image/jpeg",
67
+ ".jpeg": "image/jpeg",
68
+ ".gif": "image/gif",
69
+ ".webp": "image/webp",
70
+ ".mp4": "video/mp4",
71
+ ".mov": "video/quicktime",
72
+ ".webm": "video/webm",
53
73
  };
54
74
 
55
75
  function getExtension(path: string): string {
56
- const lastDot = path.lastIndexOf('.');
57
- if (lastDot === -1) return '';
76
+ const lastDot = path.lastIndexOf(".");
77
+ if (lastDot === -1) return "";
58
78
  return path.slice(lastDot).toLowerCase();
59
79
  }
60
80
 
@@ -63,32 +83,32 @@ export function getFileType(path: string): FileTypeInfo {
63
83
 
64
84
  if (COMPILABLE_EXTENSIONS.includes(ext)) {
65
85
  return {
66
- category: 'compilable',
86
+ category: "compilable",
67
87
  language: EXTENSION_TO_LANGUAGE[ext] ?? null,
68
- mimeType: EXTENSION_TO_MIME[ext] ?? 'text/plain',
88
+ mimeType: EXTENSION_TO_MIME[ext] ?? "text/plain",
69
89
  };
70
90
  }
71
91
 
72
92
  if (TEXT_EXTENSIONS.includes(ext)) {
73
93
  return {
74
- category: 'text',
94
+ category: "text",
75
95
  language: EXTENSION_TO_LANGUAGE[ext] ?? null,
76
- mimeType: EXTENSION_TO_MIME[ext] ?? 'text/plain',
96
+ mimeType: EXTENSION_TO_MIME[ext] ?? "text/plain",
77
97
  };
78
98
  }
79
99
 
80
100
  if (MEDIA_EXTENSIONS.includes(ext)) {
81
101
  return {
82
- category: 'media',
83
- language: ext === '.svg' ? 'xml' : null,
84
- mimeType: EXTENSION_TO_MIME[ext] ?? 'application/octet-stream',
102
+ category: "media",
103
+ language: ext === ".svg" ? "xml" : null,
104
+ mimeType: EXTENSION_TO_MIME[ext] ?? "application/octet-stream",
85
105
  };
86
106
  }
87
107
 
88
108
  return {
89
- category: 'binary',
109
+ category: "binary",
90
110
  language: null,
91
- mimeType: 'application/octet-stream',
111
+ mimeType: "application/octet-stream",
92
112
  };
93
113
  }
94
114
 
@@ -104,6 +124,14 @@ export function isTextFile(path: string): boolean {
104
124
  return TEXT_EXTENSIONS.includes(getExtension(path));
105
125
  }
106
126
 
127
+ export function isMarkdownFile(path: string): boolean {
128
+ return getExtension(path) === ".md";
129
+ }
130
+
131
+ export function isPreviewable(path: string): boolean {
132
+ return isCompilable(path) || isMarkdownFile(path);
133
+ }
134
+
107
135
  export function getLanguageFromExt(path: string): string | null {
108
136
  const ext = getExtension(path);
109
137
  return EXTENSION_TO_LANGUAGE[ext] ?? null;
@@ -111,15 +139,15 @@ export function getLanguageFromExt(path: string): string | null {
111
139
 
112
140
  export function getMimeType(path: string): string {
113
141
  const ext = getExtension(path);
114
- return EXTENSION_TO_MIME[ext] ?? 'application/octet-stream';
142
+ return EXTENSION_TO_MIME[ext] ?? "application/octet-stream";
115
143
  }
116
144
 
117
145
  export function isImageFile(path: string): boolean {
118
146
  const ext = getExtension(path);
119
- return ['.svg', '.png', '.jpg', '.jpeg', '.gif', '.webp'].includes(ext);
147
+ return [".svg", ".png", ".jpg", ".jpeg", ".gif", ".webp"].includes(ext);
120
148
  }
121
149
 
122
150
  export function isVideoFile(path: string): boolean {
123
151
  const ext = getExtension(path);
124
- return ['.mp4', '.mov', '.webm'].includes(ext);
152
+ return [".mp4", ".mov", ".webm"].includes(ext);
125
153
  }
@@ -12,6 +12,7 @@ import type {
12
12
  export interface UseEditSessionOptions {
13
13
  originalCode?: string;
14
14
  originalProject?: VirtualProject;
15
+ initialActiveFile?: string;
15
16
  compile?: CompileFn;
16
17
  apiEndpoint?: string;
17
18
  }
@@ -29,6 +30,7 @@ export function useEditSession(
29
30
  const {
30
31
  originalCode,
31
32
  originalProject: providedProject,
33
+ initialActiveFile,
32
34
  compile,
33
35
  apiEndpoint,
34
36
  } = options;
@@ -49,7 +51,11 @@ export function useEditSession(
49
51
  const lastSyncedProjectRef = useRef<VirtualProject>(originalProject);
50
52
 
51
53
  const [project, setProject] = useState<VirtualProject>(originalProject);
52
- const [activeFile, setActiveFile] = useState(originalProject.entry);
54
+ const [activeFile, setActiveFile] = useState(
55
+ initialActiveFile && originalProject.files.has(initialActiveFile)
56
+ ? initialActiveFile
57
+ : originalProject.entry,
58
+ );
53
59
  const [history, setHistory] = useState<EditHistoryEntry[]>([]);
54
60
  const [isApplying, setIsApplying] = useState(false);
55
61
  const [error, setError] = useState<string | null>(null);
@@ -61,12 +67,16 @@ export function useEditSession(
61
67
  if (originalProject !== lastSyncedProjectRef.current) {
62
68
  lastSyncedProjectRef.current = originalProject;
63
69
  setProject(originalProject);
64
- setActiveFile(originalProject.entry);
70
+ setActiveFile(
71
+ initialActiveFile && originalProject.files.has(initialActiveFile)
72
+ ? initialActiveFile
73
+ : originalProject.entry,
74
+ );
65
75
  setHistory([]);
66
76
  setError(null);
67
77
  setStreamingNotes([]);
68
78
  }
69
- }, [originalProject]);
79
+ }, [originalProject, initialActiveFile]);
70
80
 
71
81
  const performEdit = useCallback(
72
82
  async (
@@ -1,5 +1,7 @@
1
- export { CodeBlockExtension } from './CodeBlockExtension';
2
- export { CodePreview } from './CodePreview';
3
- export { MarkdownEditor } from './MarkdownEditor';
4
- export { ServicesInspector, type ServiceInfo } from './ServicesInspector';
5
- export * from './edit';
1
+ export { CodeBlockExtension } from "./CodeBlockExtension";
2
+ export { CodePreview } from "./CodePreview";
3
+ export { WidgetPreview } from "./WidgetPreview";
4
+ export { MarkdownEditor } from "./MarkdownEditor";
5
+ export { MarkdownPreview } from "./MarkdownPreview";
6
+ export { ServicesInspector, type ServiceInfo } from "./ServicesInspector";
7
+ export * from "./edit";
package/src/index.ts CHANGED
@@ -2,10 +2,12 @@
2
2
  export {
3
3
  CodeBlockExtension,
4
4
  CodePreview,
5
+ WidgetPreview,
5
6
  MarkdownEditor,
7
+ MarkdownPreview,
6
8
  ServicesInspector,
7
9
  type ServiceInfo,
8
- } from './components';
10
+ } from "./components";
9
11
 
10
12
  // Edit components
11
13
  export {
@@ -40,11 +42,13 @@ export {
40
42
  isCompilable,
41
43
  isMediaFile,
42
44
  isTextFile,
45
+ isMarkdownFile,
46
+ isPreviewable,
43
47
  isImageFile,
44
48
  isVideoFile,
45
49
  getLanguageFromExt,
46
50
  getMimeType,
47
- } from './components/edit';
51
+ } from "./components/edit";
48
52
 
49
53
  // Lib utilities
50
54
  export {
@@ -58,7 +62,7 @@ export {
58
62
  type CodePart,
59
63
  type ParsedPart,
60
64
  type ExtractOptions,
61
-
65
+
62
66
  // Diff utilities
63
67
  parseCodeBlockAttributes,
64
68
  parseCodeBlocks,
@@ -74,7 +78,7 @@ export {
74
78
  type CodeBlock,
75
79
  type DiffBlock,
76
80
  type ParsedEditResponse,
77
-
81
+
78
82
  // VFS utilities
79
83
  getVFSConfig,
80
84
  getVFSStore,
@@ -83,7 +87,7 @@ export {
83
87
  listProjects,
84
88
  saveFile,
85
89
  isVFSAvailable,
86
-
90
+
87
91
  // General utilities
88
92
  cn,
89
- } from './lib';
93
+ } from "./lib";