@tscircuit/fake-snippets 0.0.101 → 0.0.103

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 +6 -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 +13 -1
  25. package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +17 -0
  26. package/src/components/dialogs/GitHubRepositorySelector.tsx +183 -0
  27. package/src/components/dialogs/create-use-dialog.tsx +8 -2
  28. package/src/components/dialogs/edit-package-details-dialog.tsx +32 -3
  29. package/src/components/dialogs/view-ts-files-dialog.tsx +178 -33
  30. package/src/components/package-port/CodeAndPreview.tsx +4 -1
  31. package/src/components/package-port/CodeEditor.tsx +42 -35
  32. package/src/components/package-port/CodeEditorHeader.tsx +6 -4
  33. package/src/components/package-port/EditorNav.tsx +94 -37
  34. package/src/components/preview/BuildsList.tsx +241 -0
  35. package/src/components/preview/ConnectedPackagesList.tsx +187 -0
  36. package/src/components/preview/ConnectedRepoDashboard.tsx +243 -0
  37. package/src/components/preview/ConnectedRepoOverview.tsx +454 -0
  38. package/src/components/preview/index.tsx +248 -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 +40 -24
  56. package/src/pages/view-connected-repo.tsx +18 -0
@@ -0,0 +1,183 @@
1
+ import { useRef } from "react"
2
+ import {
3
+ Select,
4
+ SelectContent,
5
+ SelectItem,
6
+ SelectTrigger,
7
+ SelectValue,
8
+ } from "@/components/ui/select"
9
+ import { useAxios } from "@/hooks/use-axios"
10
+ import { useApiBaseUrl } from "@/hooks/use-packages-base-api-url"
11
+ import { useQuery } from "react-query"
12
+ import { Button } from "../ui/button"
13
+ import { Label } from "../ui/label"
14
+ import { Minus, Plus } from "lucide-react"
15
+ import { Switch } from "../ui/switch"
16
+
17
+ interface GitHubRepositorySelectorProps {
18
+ selectedRepository?: string
19
+ setSelectedRepository?: (value: string | null) => void
20
+ disabled?: boolean
21
+ open?: boolean
22
+ addFormContent?: (data: {
23
+ enablePrPreview?: boolean
24
+ privateBuild?: boolean
25
+ }) => void
26
+ formData?: any
27
+ }
28
+
29
+ export const GitHubRepositorySelector = ({
30
+ selectedRepository,
31
+ setSelectedRepository,
32
+ disabled = false,
33
+ open = false,
34
+ addFormContent,
35
+ formData,
36
+ }: GitHubRepositorySelectorProps) => {
37
+ const axios = useAxios()
38
+ const apiBaseUrl = useApiBaseUrl()
39
+ const initialValue = useRef(selectedRepository).current
40
+ // Fetch available repositories
41
+ const { data: repositoriesData, error: repositoriesError } = useQuery(
42
+ ["github-repositories"],
43
+ async () => {
44
+ const response = await axios.get("/github/repos/list_available")
45
+ return response.data
46
+ },
47
+ {
48
+ enabled: open, // Only fetch when needed
49
+ retry: false,
50
+ },
51
+ )
52
+
53
+ const handleConnectMoreRepos = async () => {
54
+ window.location.href = `${apiBaseUrl}/github/installations/create_new_installation_redirect?return_to_page=${window.location.pathname}`
55
+ }
56
+
57
+ const handleValueChange = (newValue: string) => {
58
+ if (newValue === "connect-more") {
59
+ handleConnectMoreRepos()
60
+ } else if (newValue === "unlink//repo") {
61
+ setSelectedRepository?.("unlink//repo")
62
+ } else {
63
+ setSelectedRepository?.(newValue)
64
+ }
65
+ }
66
+
67
+ return (
68
+ <>
69
+ <div className="space-y-1 mb-3">
70
+ <Label htmlFor="repository">GitHub Repository</Label>
71
+ {(repositoriesError as any)?.response?.status === 400 &&
72
+ (repositoriesError as any)?.response?.data?.error_code ===
73
+ "github_not_connected" ? (
74
+ <div className="space-y-2">
75
+ <div className="text-sm text-muted-foreground">
76
+ Connect your GitHub account to link this package to a repository.
77
+ </div>
78
+ <Button
79
+ type="button"
80
+ variant="outline"
81
+ onClick={handleConnectMoreRepos}
82
+ className="w-full"
83
+ disabled={disabled}
84
+ >
85
+ <svg
86
+ className="w-4 h-4 mr-2"
87
+ viewBox="0 0 24 24"
88
+ fill="currentColor"
89
+ >
90
+ <path d="M12 0C5.374 0 0 5.373 0 12 0 17.302 3.438 21.8 8.207 23.387c.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.509 11.509 0 0112 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576C20.566 21.797 24 17.3 24 12c0-6.627-5.373-12-12-12z" />
91
+ </svg>
92
+ Connect GitHub Account
93
+ </Button>
94
+ </div>
95
+ ) : (
96
+ <div className="space-y-2">
97
+ <Select
98
+ value={selectedRepository}
99
+ onValueChange={handleValueChange}
100
+ disabled={disabled}
101
+ >
102
+ <SelectTrigger className="w-full">
103
+ <SelectValue placeholder="Select a repository" />
104
+ </SelectTrigger>
105
+ <SelectContent className="!z-[999]">
106
+ {repositoriesData?.repos?.map((repo: any) => (
107
+ <SelectItem key={repo.full_name} value={repo.full_name}>
108
+ <div className="flex items-center space-x-2">
109
+ <span>{repo.unscoped_name}</span>
110
+ {repo.private && (
111
+ <span className="text-xs text-muted-foreground">
112
+ (private)
113
+ </span>
114
+ )}
115
+ </div>
116
+ </SelectItem>
117
+ ))}
118
+ <SelectItem value="connect-more">
119
+ <div className="flex items-center space-x-2 text-blue-600">
120
+ <Plus className="w-3 h-3" />
121
+ <span>Connect More Repos</span>
122
+ </div>
123
+ </SelectItem>
124
+ {Boolean(initialValue) && (
125
+ <SelectItem value="unlink//repo">
126
+ <div className="flex items-center space-x-2 text-red-600">
127
+ <Minus className="w-3 h-3" />
128
+ <span>Unlink Repo</span>
129
+ </div>
130
+ </SelectItem>
131
+ )}
132
+ </SelectContent>
133
+ </Select>
134
+ </div>
135
+ )}
136
+ </div>
137
+
138
+ {initialValue && selectedRepository !== "unlink//repo" && (
139
+ <div className="space-y-4 mt-4 p-4 border rounded-lg bg-gray-50">
140
+ <h4 className="text-sm font-medium text-gray-900">
141
+ Repository Settings
142
+ </h4>
143
+
144
+ <div className="flex items-center justify-between">
145
+ <div className="space-y-0.5">
146
+ <Label className="text-sm font-medium">Private Build</Label>
147
+ <p className="text-xs text-gray-500">
148
+ Keep build previews private
149
+ </p>
150
+ </div>
151
+ <Switch
152
+ checked={formData?.privateBuild}
153
+ onCheckedChange={(checked) =>
154
+ addFormContent?.({
155
+ privateBuild: checked,
156
+ })
157
+ }
158
+ disabled={disabled}
159
+ />
160
+ </div>
161
+
162
+ <div className="flex items-center justify-between">
163
+ <div className="space-y-0.5">
164
+ <Label className="text-sm font-medium">Enable PR Preview</Label>
165
+ <p className="text-xs text-gray-500">
166
+ Generate preview builds for pull requests
167
+ </p>
168
+ </div>
169
+ <Switch
170
+ checked={formData?.enablePrPreview}
171
+ onCheckedChange={(checked) =>
172
+ addFormContent?.({
173
+ enablePrPreview: checked,
174
+ })
175
+ }
176
+ disabled={disabled}
177
+ />
178
+ </div>
179
+ </div>
180
+ )}
181
+ </>
182
+ )
183
+ }
@@ -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,10 @@ 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:
121
+ formData.githubRepoFullName === "unlink//repo"
122
+ ? null
123
+ : formData.githubRepoFullName,
117
124
  ...(formData.unscopedPackageName !== unscopedPackageName && {
118
125
  name: formData.unscopedPackageName.trim(),
119
126
  }),
@@ -371,6 +378,26 @@ export const EditPackageDetailsDialog = ({
371
378
  </SelectContent>
372
379
  </Select>
373
380
  </div>
381
+ <div className="space-y-1">
382
+ <GitHubRepositorySelector
383
+ selectedRepository={formData.githubRepoFullName || ""}
384
+ setSelectedRepository={(value) =>
385
+ setFormData((prev) => ({
386
+ ...prev,
387
+ githubRepoFullName: value,
388
+ }))
389
+ }
390
+ disabled={updatePackageDetailsMutation.isLoading}
391
+ open={open}
392
+ formData={formData}
393
+ addFormContent={(content) => {
394
+ setFormData((prev) => ({
395
+ ...prev,
396
+ ...content,
397
+ }))
398
+ }}
399
+ />
400
+ </div>
374
401
  </div>
375
402
 
376
403
  <details
@@ -394,10 +421,12 @@ export const EditPackageDetailsDialog = ({
394
421
  variant="destructive"
395
422
  size="default"
396
423
  onClick={() => setShowConfirmDelete(true)}
397
- disabled={deleting}
424
+ disabled={deletePackageMutation.isLoading}
398
425
  className="shrink-0 lg:w-[115px] w-[70px]"
399
426
  >
400
- {deleting ? "Deleting..." : "Delete"}
427
+ {deletePackageMutation.isLoading
428
+ ? "Deleting..."
429
+ : "Delete"}
401
430
  </Button>
402
431
  </div>
403
432
  </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",