@tscircuit/fake-snippets 0.0.101 → 0.0.102

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 (56) hide show
  1. package/api/generated-index.js +23 -1
  2. package/bun.lock +2 -2
  3. package/dist/bundle.js +530 -367
  4. package/dist/index.d.ts +29 -2
  5. package/dist/index.js +18 -1
  6. package/dist/schema.d.ts +94 -1
  7. package/dist/schema.js +17 -1
  8. package/fake-snippets-api/lib/db/db-client.ts +6 -1
  9. package/fake-snippets-api/lib/db/schema.ts +15 -0
  10. package/fake-snippets-api/lib/public-mapping/public-map-package.ts +2 -0
  11. package/fake-snippets-api/routes/api/github/installations/create_new_installation_redirect.ts +75 -0
  12. package/fake-snippets-api/routes/api/github/repos/list_available.ts +91 -0
  13. package/fake-snippets-api/routes/api/packages/update.ts +4 -0
  14. package/package.json +2 -2
  15. package/src/App.tsx +10 -1
  16. package/src/components/CreateReleaseDialog.tsx +124 -0
  17. package/src/components/FileSidebar.tsx +128 -23
  18. package/src/components/PackageBuildsPage/package-build-header.tsx +9 -1
  19. package/src/components/PageSearchComponent.tsx +2 -2
  20. package/src/components/SearchComponent.tsx +2 -2
  21. package/src/components/SuspenseRunFrame.tsx +2 -2
  22. package/src/components/TrendingPackagesCarousel.tsx +2 -2
  23. package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +1 -0
  24. package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +1 -0
  25. package/src/components/dialogs/GitHubRepositorySelector.tsx +123 -0
  26. package/src/components/dialogs/create-use-dialog.tsx +8 -2
  27. package/src/components/dialogs/edit-package-details-dialog.tsx +22 -3
  28. package/src/components/dialogs/view-ts-files-dialog.tsx +178 -33
  29. package/src/components/package-port/CodeAndPreview.tsx +4 -1
  30. package/src/components/package-port/CodeEditor.tsx +42 -35
  31. package/src/components/package-port/CodeEditorHeader.tsx +6 -4
  32. package/src/components/package-port/EditorNav.tsx +94 -37
  33. package/src/components/preview/BuildsList.tsx +238 -0
  34. package/src/components/preview/ConnectedRepoDashboard.tsx +258 -0
  35. package/src/components/preview/ConnectedRepoOverview.tsx +454 -0
  36. package/src/components/preview/ConnectedRepoSettings.tsx +343 -0
  37. package/src/components/preview/ConnectedReposCards.tsx +191 -0
  38. package/src/components/preview/index.tsx +207 -0
  39. package/src/components/ui/tree-view.tsx +23 -6
  40. package/src/hooks/use-axios.ts +2 -2
  41. package/src/hooks/use-create-release-dialog.ts +160 -0
  42. package/src/hooks/use-package-details-form.ts +7 -0
  43. package/src/hooks/use-packages-base-api-url.ts +1 -1
  44. package/src/hooks/use-sign-in.ts +2 -2
  45. package/src/hooks/useFileManagement.ts +22 -2
  46. package/src/index.css +4 -0
  47. package/src/lib/utils/formatTimeAgo.ts +10 -0
  48. package/src/lib/utils/isValidFileName.ts +15 -3
  49. package/src/pages/dashboard.tsx +2 -2
  50. package/src/pages/dev-login.tsx +2 -2
  51. package/src/pages/latest.tsx +2 -2
  52. package/src/pages/preview-build.tsx +380 -0
  53. package/src/pages/search.tsx +2 -2
  54. package/src/pages/trending.tsx +2 -2
  55. package/src/pages/user-profile.tsx +32 -24
  56. package/src/pages/view-connected-repo.tsx +24 -0
@@ -5,14 +5,19 @@ export const createUseDialog = <DialogType extends React.ComponentType<any>>(
5
5
  ) => {
6
6
  return () => {
7
7
  const [open, setOpen] = useState(false)
8
+ const [dialogProps, setDialogProps] = useState<any>({})
8
9
 
9
10
  return useMemo(
10
11
  () => ({
11
- openDialog: () => {
12
+ openDialog: (props?: any) => {
13
+ if (props) {
14
+ setDialogProps(props)
15
+ }
12
16
  setOpen(true)
13
17
  },
14
18
  closeDialog: () => {
15
19
  setOpen(false)
20
+ setDialogProps({})
16
21
  },
17
22
  Dialog: (
18
23
  props: Omit<
@@ -22,13 +27,14 @@ export const createUseDialog = <DialogType extends React.ComponentType<any>>(
22
27
  ) => (
23
28
  <DialogComponent
24
29
  {...(props as any)}
30
+ {...dialogProps}
25
31
  open={open}
26
32
  onOpenChange={setOpen}
27
33
  />
28
34
  ),
29
35
  open,
30
36
  }),
31
- [open],
37
+ [open, dialogProps],
32
38
  )
33
39
  }
34
40
  }
@@ -27,6 +27,7 @@ import { createUseDialog } from "./create-use-dialog"
27
27
  import { ChevronDown } from "lucide-react"
28
28
  import { useLocation } from "wouter"
29
29
  import { useDeletePackage } from "@/hooks/use-delete-package"
30
+ import { GitHubRepositorySelector } from "./GitHubRepositorySelector"
30
31
 
31
32
  interface EditPackageDetailsDialogProps {
32
33
  open: boolean
@@ -36,6 +37,7 @@ interface EditPackageDetailsDialogProps {
36
37
  currentWebsite: string
37
38
  currentLicense?: string | null
38
39
  currentDefaultView?: string
40
+ currentGithubRepoFullName?: string | null
39
41
  isPrivate?: boolean
40
42
  packageName: string
41
43
  unscopedPackageName: string
@@ -57,6 +59,7 @@ export const EditPackageDetailsDialog = ({
57
59
  currentWebsite,
58
60
  currentLicense,
59
61
  currentDefaultView = "files",
62
+ currentGithubRepoFullName,
60
63
  isPrivate = false,
61
64
  unscopedPackageName,
62
65
  packageReleaseId,
@@ -75,6 +78,7 @@ export const EditPackageDetailsDialog = ({
75
78
  isFormValid,
76
79
  } = usePackageDetailsForm({
77
80
  initialDescription: currentDescription,
81
+ initialGithubRepoFullName: currentGithubRepoFullName ?? null,
78
82
  initialWebsite: currentWebsite,
79
83
  initialLicense: currentLicense || null,
80
84
  initialDefaultView: currentDefaultView,
@@ -83,7 +87,6 @@ export const EditPackageDetailsDialog = ({
83
87
  initialVisibility: isPrivate ? "private" : "public",
84
88
  })
85
89
 
86
- const [deleting, setDeleting] = useState(false)
87
90
  const [showConfirmDelete, setShowConfirmDelete] = useState(false)
88
91
  const [dangerOpen, setDangerOpen] = useState(false)
89
92
  const [, setLocation] = useLocation()
@@ -114,6 +117,7 @@ export const EditPackageDetailsDialog = ({
114
117
  website: formData.website.trim(),
115
118
  is_private: formData.visibility == "private",
116
119
  default_view: formData.defaultView,
120
+ github_repo_full_name: formData.githubRepoFullName,
117
121
  ...(formData.unscopedPackageName !== unscopedPackageName && {
118
122
  name: formData.unscopedPackageName.trim(),
119
123
  }),
@@ -371,6 +375,19 @@ export const EditPackageDetailsDialog = ({
371
375
  </SelectContent>
372
376
  </Select>
373
377
  </div>
378
+ <div className="space-y-1">
379
+ <GitHubRepositorySelector
380
+ value={formData.githubRepoFullName || ""}
381
+ onValueChange={(value) =>
382
+ setFormData((prev) => ({
383
+ ...prev,
384
+ githubRepoFullName: value,
385
+ }))
386
+ }
387
+ disabled={updatePackageDetailsMutation.isLoading}
388
+ open={open}
389
+ />
390
+ </div>
374
391
  </div>
375
392
 
376
393
  <details
@@ -394,10 +411,12 @@ export const EditPackageDetailsDialog = ({
394
411
  variant="destructive"
395
412
  size="default"
396
413
  onClick={() => setShowConfirmDelete(true)}
397
- disabled={deleting}
414
+ disabled={deletePackageMutation.isLoading}
398
415
  className="shrink-0 lg:w-[115px] w-[70px]"
399
416
  >
400
- {deleting ? "Deleting..." : "Delete"}
417
+ {deletePackageMutation.isLoading
418
+ ? "Deleting..."
419
+ : "Delete"}
401
420
  </Button>
402
421
  </div>
403
422
  </div>
@@ -21,13 +21,23 @@ import JSZip from "jszip"
21
21
  import { saveAs } from "file-saver"
22
22
  import { EditorView } from "codemirror"
23
23
  import { EditorState } from "@codemirror/state"
24
+ import { autocompletion } from "@codemirror/autocomplete"
24
25
  import { basicSetup } from "@/lib/codemirror/basic-setup"
25
26
  import { javascript } from "@codemirror/lang-javascript"
26
27
  import { json } from "@codemirror/lang-json"
28
+ import { tsAutocomplete, tsFacet, tsSync } from "@valtown/codemirror-ts"
29
+ import {
30
+ createSystem,
31
+ createVirtualTypeScriptEnvironment,
32
+ } from "@typescript/vfs"
33
+ import { loadDefaultLibMap } from "@/lib/ts-lib-cache"
34
+ import tsModule from "typescript"
27
35
 
28
36
  interface ViewTsFilesDialogProps {
29
37
  open: boolean
30
38
  onOpenChange: (open: boolean) => void
39
+ initialFile?: string
40
+ initialLine?: number
31
41
  }
32
42
 
33
43
  interface FileNode {
@@ -41,6 +51,8 @@ interface FileNode {
41
51
  export const ViewTsFilesDialog: React.FC<ViewTsFilesDialogProps> = ({
42
52
  open,
43
53
  onOpenChange,
54
+ initialFile,
55
+ initialLine,
44
56
  }) => {
45
57
  const [files, setFiles] = useState<Map<string, string>>(new Map())
46
58
  const [selectedFile, setSelectedFile] = useState<string | null>(null)
@@ -48,8 +60,12 @@ export const ViewTsFilesDialog: React.FC<ViewTsFilesDialogProps> = ({
48
60
  const [copiedFile, setCopiedFile] = useState<string | null>(null)
49
61
  const [expandedFolders, setExpandedFolders] = useState<Set<string>>(new Set())
50
62
  const [sidebarOpen, setSidebarOpen] = useState(true)
63
+ const [targetLine, setTargetLine] = useState<number | null>(null)
51
64
  const editorRef = useRef<HTMLDivElement>(null)
52
65
  const viewRef = useRef<EditorView | null>(null)
66
+ const [tsEnv, setTsEnv] = useState<ReturnType<
67
+ typeof createVirtualTypeScriptEnvironment
68
+ > | null>(null)
53
69
 
54
70
  const fileTree = useMemo(() => {
55
71
  const tree: FileNode[] = []
@@ -159,33 +175,86 @@ export const ViewTsFilesDialog: React.FC<ViewTsFilesDialogProps> = ({
159
175
  viewRef.current.destroy()
160
176
  }
161
177
 
162
- const state = EditorState.create({
163
- doc: content,
164
- extensions: [
165
- basicSetup,
166
- isJson ? json() : javascript({ typescript: true, jsx: true }),
167
- EditorState.readOnly.of(true),
168
- EditorView.theme({
169
- "&": {
170
- height: "100%",
171
- fontSize: "14px",
172
- },
173
- ".cm-content": {
174
- padding: "16px",
175
- minHeight: "100%",
176
- },
177
- ".cm-focused": {
178
- outline: "none",
179
- },
180
- ".cm-editor": {
181
- height: "100%",
182
- },
183
- ".cm-scroller": {
184
- fontFamily:
185
- "ui-monospace, SFMono-Regular, 'SF Mono', Consolas, 'Liberation Mono', Menlo, monospace",
178
+ const extensions = [
179
+ basicSetup,
180
+ isJson ? json() : javascript({ typescript: true, jsx: true }),
181
+ EditorState.readOnly.of(true),
182
+ EditorView.theme({
183
+ "&": {
184
+ height: "100%",
185
+ fontSize: "14px",
186
+ },
187
+ ".cm-content": {
188
+ padding: "16px",
189
+ minHeight: "100%",
190
+ },
191
+ ".cm-focused": {
192
+ outline: "none",
193
+ },
194
+ ".cm-editor": {
195
+ height: "100%",
196
+ },
197
+ ".cm-scroller": {
198
+ fontFamily:
199
+ "ui-monospace, SFMono-Regular, 'SF Mono', Consolas, 'Liberation Mono', Menlo, monospace",
200
+ },
201
+ }),
202
+ ]
203
+
204
+ if (
205
+ tsEnv &&
206
+ !isJson &&
207
+ (selectedFile.endsWith(".ts") || selectedFile.endsWith(".tsx"))
208
+ ) {
209
+ extensions.push(
210
+ tsFacet.of({
211
+ env: tsEnv,
212
+ path: selectedFile.startsWith("/")
213
+ ? selectedFile.slice(1)
214
+ : selectedFile,
215
+ }),
216
+ tsSync(),
217
+ autocompletion({ override: [tsAutocomplete()] }),
218
+ EditorView.domEventHandlers({
219
+ click: (event, view) => {
220
+ if (event.ctrlKey || event.metaKey) {
221
+ const pos = view.posAtCoords({
222
+ x: event.clientX,
223
+ y: event.clientY,
224
+ })
225
+ if (pos !== null) {
226
+ const path = selectedFile.startsWith("/")
227
+ ? selectedFile.slice(1)
228
+ : selectedFile
229
+ const definitions =
230
+ tsEnv.languageService.getDefinitionAtPosition(path, pos)
231
+ if (definitions && definitions.length > 0) {
232
+ const definition = definitions[0]
233
+ const definitionFileName = definition.fileName
234
+ if (definitionFileName && files.has(definitionFileName)) {
235
+ const definitionContent =
236
+ files.get(definitionFileName) || ""
237
+ const lines = definitionContent
238
+ .substring(0, definition.textSpan.start)
239
+ .split("\n")
240
+ const lineNumber = lines.length
241
+
242
+ setSelectedFile(definitionFileName)
243
+ setTargetLine(lineNumber)
244
+ return true
245
+ }
246
+ }
247
+ }
248
+ }
249
+ return false
186
250
  },
187
251
  }),
188
- ],
252
+ )
253
+ }
254
+
255
+ const state = EditorState.create({
256
+ doc: content,
257
+ extensions,
189
258
  })
190
259
 
191
260
  viewRef.current = new EditorView({
@@ -193,25 +262,85 @@ export const ViewTsFilesDialog: React.FC<ViewTsFilesDialogProps> = ({
193
262
  parent: editorRef.current,
194
263
  })
195
264
 
265
+ if (targetLine && targetLine > 0) {
266
+ const scrollToLine = () => {
267
+ if (viewRef.current) {
268
+ const doc = viewRef.current.state.doc
269
+ if (targetLine <= doc.lines) {
270
+ const line = doc.line(targetLine)
271
+
272
+ const performScroll = () => {
273
+ if (viewRef.current) {
274
+ viewRef.current.dispatch({
275
+ selection: { anchor: line.from, head: line.to },
276
+ effects: EditorView.scrollIntoView(line.from, {
277
+ y: "center",
278
+ }),
279
+ })
280
+
281
+ setTimeout(() => {
282
+ if (viewRef.current) {
283
+ viewRef.current.dispatch({
284
+ effects: EditorView.scrollIntoView(line.from, {
285
+ y: "center",
286
+ }),
287
+ })
288
+
289
+ setTimeout(() => {
290
+ if (viewRef.current) {
291
+ viewRef.current.dispatch({
292
+ effects: EditorView.scrollIntoView(line.from, {
293
+ y: "center",
294
+ }),
295
+ })
296
+ }
297
+ }, 200)
298
+ }
299
+ }, 150)
300
+ }
301
+ }
302
+
303
+ requestAnimationFrame(performScroll)
304
+ }
305
+ }
306
+ }
307
+
308
+ // Extra delay when TypeScript environment is not ready or for large line numbers
309
+ // This handles cases when triggered from CodeEditor.tsx with TypeScript definitions
310
+ const isLargeLine = targetLine > 100
311
+ const needsExtraDelay = !tsEnv || isLargeLine
312
+ const initialDelay = needsExtraDelay ? 500 : 200
313
+
314
+ setTimeout(scrollToLine, initialDelay)
315
+ setTargetLine(null)
316
+ }
317
+
196
318
  return () => {
197
319
  if (viewRef.current) {
198
320
  viewRef.current.destroy()
199
321
  viewRef.current = null
200
322
  }
201
323
  }
202
- }, [selectedFile, files])
324
+ }, [selectedFile, files, targetLine, tsEnv])
203
325
 
204
326
  useEffect(() => {
205
327
  if (open && window.__DEBUG_CODE_EDITOR_FS_MAP) {
206
328
  setFiles(window.__DEBUG_CODE_EDITOR_FS_MAP)
207
329
 
208
330
  if (window.__DEBUG_CODE_EDITOR_FS_MAP.size > 0) {
209
- const firstFile = Array.from(
210
- window.__DEBUG_CODE_EDITOR_FS_MAP.keys(),
211
- )[0]
212
- setSelectedFile(firstFile)
331
+ let fileToSelect: string
332
+ if (initialFile && window.__DEBUG_CODE_EDITOR_FS_MAP.has(initialFile)) {
333
+ fileToSelect = initialFile
334
+ if (initialLine) {
335
+ setTargetLine(initialLine)
336
+ }
337
+ } else {
338
+ fileToSelect = Array.from(window.__DEBUG_CODE_EDITOR_FS_MAP.keys())[0]
339
+ }
213
340
 
214
- let normalizedPath = firstFile
341
+ setSelectedFile(fileToSelect)
342
+
343
+ let normalizedPath = fileToSelect
215
344
  if (normalizedPath.startsWith("/")) {
216
345
  normalizedPath = normalizedPath.slice(1)
217
346
  }
@@ -225,7 +354,23 @@ export const ViewTsFilesDialog: React.FC<ViewTsFilesDialogProps> = ({
225
354
  setExpandedFolders(foldersToExpand)
226
355
  }
227
356
  }
228
- }, [open])
357
+ }, [open, initialFile, initialLine])
358
+
359
+ useEffect(() => {
360
+ if (files.size > 0) {
361
+ const setupTsEnv = async () => {
362
+ try {
363
+ const libMap = await loadDefaultLibMap()
364
+ const system = createSystem(new Map([...libMap, ...files]))
365
+ const env = createVirtualTypeScriptEnvironment(system, [], tsModule)
366
+ setTsEnv(env)
367
+ } catch (error) {
368
+ console.error("Failed to setup TypeScript environment:", error)
369
+ }
370
+ }
371
+ setupTsEnv()
372
+ }
373
+ }, [files])
229
374
 
230
375
  useEffect(() => {
231
376
  const handleResize = () => {
@@ -367,7 +512,7 @@ export const ViewTsFilesDialog: React.FC<ViewTsFilesDialogProps> = ({
367
512
  </Badge>
368
513
  </div>
369
514
  </div>
370
- <div className="flex items-center gap-2">
515
+ <div className="flex items-center gap-2 mr-4">
371
516
  <Button
372
517
  variant="outline"
373
518
  size="sm"
@@ -78,6 +78,7 @@ export function CodeAndPreview({ pkg, projectUrl }: Props) {
78
78
  localFiles,
79
79
  initialFiles,
80
80
  renameFile,
81
+ packageFilesMeta,
81
82
  } = useFileManagement({
82
83
  templateCode: templateFromUrl?.code,
83
84
  currentPackage: pkg,
@@ -197,6 +198,8 @@ export function CodeAndPreview({ pkg, projectUrl }: Props) {
197
198
  setState((prev) => ({ ...prev, showPreview: !prev.showPreview }))
198
199
  }
199
200
  previewOpen={state.showPreview}
201
+ files={localFiles}
202
+ packageFilesMeta={packageFilesMeta}
200
203
  />
201
204
  <div
202
205
  className={`flex ${state.showPreview ? "flex-col md:flex-row" : ""}`}
@@ -231,7 +234,7 @@ export function CodeAndPreview({ pkg, projectUrl }: Props) {
231
234
  </div>
232
235
  <div
233
236
  className={cn(
234
- "flex p-0 flex-col min-h-[640px]",
237
+ "flex p-0 flex-col min-h-[640px] overflow-y-hidden",
235
238
  state.fullScreen
236
239
  ? "fixed inset-0 z-50 bg-white p-4 overflow-hidden"
237
240
  : "w-full md:w-1/2",
@@ -1,4 +1,4 @@
1
- import { usePackagesBaseApiUrl } from "@/hooks/use-packages-base-api-url"
1
+ import { useApiBaseUrl } from "@/hooks/use-packages-base-api-url"
2
2
  import { useHotkeyCombo } from "@/hooks/use-hotkey"
3
3
  import { basicSetup } from "@/lib/codemirror/basic-setup"
4
4
  import {
@@ -49,7 +49,7 @@ import {
49
49
  } from "@/hooks/useFileManagement"
50
50
  import { isHiddenFile } from "../ViewPackagePage/utils/is-hidden-file"
51
51
  import { inlineCopilot } from "codemirror-copilot"
52
- import { resolveRelativePath } from "@/lib/utils/resolveRelativePath"
52
+ import { useViewTsFilesDialog } from "@/components/dialogs/view-ts-files-dialog"
53
53
 
54
54
  const defaultImports = `
55
55
  import React from "@types/react/jsx-runtime"
@@ -92,7 +92,7 @@ export const CodeEditor = ({
92
92
  const viewRef = useRef<EditorView | null>(null)
93
93
  const ataRef = useRef<ReturnType<typeof setupTypeAcquisition> | null>(null)
94
94
  const lastReceivedTsFileTimeRef = useRef<number>(0)
95
- const apiUrl = usePackagesBaseApiUrl()
95
+ const apiUrl = useApiBaseUrl()
96
96
  const [cursorPosition, setCursorPosition] = useState<number | null>(null)
97
97
  const [code, setCode] = useState(files[0]?.content || "")
98
98
  const [fontSize, setFontSize] = useState(14)
@@ -108,6 +108,8 @@ export const CodeEditor = ({
108
108
  const filePathFromUrl = urlParams.get("file_path")
109
109
  const lineNumberFromUrl = urlParams.get("line")
110
110
  const [aiAutocompleteEnabled, setAiAutocompleteEnabled] = useState(false)
111
+ const { Dialog: ViewTsFilesDialog, openDialog: openViewTsFilesDialog } =
112
+ useViewTsFilesDialog()
111
113
 
112
114
  const entryPointFileName = useMemo(() => {
113
115
  const entryPointFile = findTargetFile(files, null)
@@ -519,42 +521,47 @@ export const CodeEditor = ({
519
521
  }
520
522
  }
521
523
  }
522
-
523
- // Check for local file imports
524
- const localFileMatches = Array.from(
525
- lineText.matchAll(LOCAL_FILE_IMPORT_PATTERN),
526
- )
527
- for (const match of localFileMatches) {
528
- if (match.index !== undefined) {
529
- const start = lineStart + match.index
530
- const end = start + match[0].length
531
- if (pos >= start && pos <= end) {
532
- const relativePath = match[0]
533
- const resolvedPath = resolveRelativePath(
534
- relativePath,
535
- currentFile || "",
536
- )
537
-
538
- // Add common extensions if not present
539
- let targetPath = resolvedPath
540
- if (!targetPath.includes(".")) {
541
- const extensions = [".tsx", ".ts", ".js", ".jsx"]
542
- for (const ext of extensions) {
543
- if (fileMap[`${targetPath}${ext}`]) {
544
- targetPath = `${targetPath}${ext}`
545
- break
546
- }
547
- }
548
- }
549
-
550
- if (fileMap[targetPath]) {
551
- onFileSelect(targetPath)
524
+ // TypeScript "Go to Definition" functionality
525
+ const facet = view.state.facet(tsFacet)
526
+ if (facet) {
527
+ const { env, path } = facet
528
+ const definitions =
529
+ env.languageService.getDefinitionAtPosition(path, pos)
530
+ if (definitions && definitions.length > 0) {
531
+ const definition = definitions[0]
532
+ const definitionFileName = definition.fileName
533
+ if (definitionFileName) {
534
+ const localFilePath = definitionFileName.startsWith("/")
535
+ ? definitionFileName.replace("/", "")
536
+ : definitionFileName
537
+ if (fileMap[localFilePath]) {
538
+ const definitionContent = fileMap[localFilePath]
539
+ const lines = definitionContent
540
+ ?.substring(0, definition.textSpan.start)
541
+ .split("\n")
542
+ const lineNumber = lines?.length
543
+
544
+ onFileSelect(localFilePath, lineNumber)
545
+ return true
546
+ } else {
547
+ const definitionContent =
548
+ env
549
+ .getSourceFile(definitionFileName)
550
+ ?.getFullText() || ""
551
+ const lines = definitionContent
552
+ .substring(0, definition.textSpan.start)
553
+ .split("\n")
554
+ const lineNumber = lines.length
555
+ openViewTsFilesDialog({
556
+ initialFile: definitionFileName,
557
+ initialLine: lineNumber,
558
+ })
552
559
  return true
553
560
  }
554
- return !!fileMap[targetPath]
555
561
  }
556
562
  }
557
563
  }
564
+
558
565
  return false
559
566
  },
560
567
  keydown: (event) => {
@@ -660,7 +667,6 @@ export const CodeEditor = ({
660
667
  }, [
661
668
  !isStreaming,
662
669
  currentFile,
663
- code !== "",
664
670
  Boolean(highlighter),
665
671
  isSaving,
666
672
  fontSize,
@@ -867,6 +873,7 @@ export const CodeEditor = ({
867
873
  onClose={() => setShowGlobalFindReplace(false)}
868
874
  />
869
875
  )}
876
+ <ViewTsFilesDialog />
870
877
  </div>
871
878
  )
872
879
  }
@@ -27,7 +27,7 @@ import {
27
27
  } from "@/components/ui/tooltip"
28
28
  import { convertRawEasyToTsx, fetchEasyEDAComponent } from "easyeda/browser"
29
29
  import { ComponentSearchResult } from "@tscircuit/runframe/runner"
30
- import { usePackagesBaseApiUrl } from "@/hooks/use-packages-base-api-url"
30
+ import { useApiBaseUrl } from "@/hooks/use-packages-base-api-url"
31
31
  import { ICreateFileProps, ICreateFileResult } from "@/hooks/useFileManagement"
32
32
  import { useGlobalStore } from "@/hooks/use-global-store"
33
33
 
@@ -60,7 +60,7 @@ export const CodeEditorHeader: React.FC<CodeEditorHeaderProps> = ({
60
60
  useImportComponentDialog()
61
61
  const { toast, toastLibrary } = useToast()
62
62
  const [sidebarOpen, setSidebarOpen] = fileSidebarState
63
- const API_BASE = usePackagesBaseApiUrl()
63
+ const API_BASE = useApiBaseUrl()
64
64
  const [aiAutocompleteEnabled, setAiAutocompleteEnabled] = aiAutocompleteState
65
65
  const session = useGlobalStore((s) => s.session)
66
66
 
@@ -184,11 +184,13 @@ export const CodeEditorHeader: React.FC<CodeEditorHeaderProps> = ({
184
184
  )
185
185
  const tsxComponent = await convertRawEasyToTsx(jlcpcbComponent)
186
186
  let componentName = component.name.replace(/ /g, "-")
187
- if (files[`${componentName}.tsx`] || files[`./${componentName}.tsx`]) {
187
+ let componentPath = `imports/${componentName}.tsx`
188
+ if (files[componentPath] || files[`./${componentPath}`]) {
188
189
  componentName = `${componentName}-1`
190
+ componentPath = `imports/${componentName}.tsx`
189
191
  }
190
192
  const createFileResult = createFile({
191
- newFileName: `${componentName}.tsx`,
193
+ newFileName: componentPath,
192
194
  content: tsxComponent,
193
195
  onError: (error) => {
194
196
  throw error