@tscircuit/fake-snippets 0.0.107 → 0.0.109

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 (80) hide show
  1. package/api/generated-index.js +82 -22
  2. package/biome.json +7 -1
  3. package/bun-tests/fake-snippets-api/routes/package_builds/get.test.ts +0 -15
  4. package/bun-tests/fake-snippets-api/routes/package_builds/list.test.ts +0 -12
  5. package/bun.lock +62 -19
  6. package/dist/bundle.js +25 -24
  7. package/dist/index.d.ts +26 -15
  8. package/dist/index.js +19 -18
  9. package/dist/schema.d.ts +32 -24
  10. package/dist/schema.js +7 -6
  11. package/fake-snippets-api/lib/db/db-client.ts +10 -1
  12. package/fake-snippets-api/lib/db/schema.ts +4 -3
  13. package/fake-snippets-api/lib/db/seed.ts +6 -9
  14. package/fake-snippets-api/lib/public-mapping/public-map-package-build.ts +0 -3
  15. package/fake-snippets-api/lib/public-mapping/public-map-package-release.ts +3 -0
  16. package/package.json +7 -8
  17. package/src/App.tsx +12 -11
  18. package/src/components/DownloadButtonAndMenu.tsx +133 -35
  19. package/src/components/FileSidebar.tsx +45 -193
  20. package/src/components/Footer.tsx +0 -1
  21. package/src/components/HeaderLogin.tsx +1 -1
  22. package/src/components/HiddenFilesDropdown.tsx +0 -2
  23. package/src/components/PackageBreadcrumb.tsx +1 -1
  24. package/src/components/PackageBuildsPage/PackageBuildDetailsPage.tsx +0 -2
  25. package/src/components/PackageBuildsPage/build-preview-content.tsx +34 -5
  26. package/src/components/PackageCard.tsx +0 -1
  27. package/src/components/ViewPackagePage/components/ShikiCodeViewer.tsx +20 -11
  28. package/src/components/ViewPackagePage/components/important-files-view.tsx +75 -59
  29. package/src/components/ViewPackagePage/components/main-content-header.tsx +4 -4
  30. package/src/components/ViewPackagePage/components/main-content-view-selector.tsx +0 -1
  31. package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +1 -2
  32. package/src/components/ViewPackagePage/components/package-header.tsx +14 -17
  33. package/src/components/ViewPackagePage/components/preview-image-squares.tsx +0 -1
  34. package/src/components/ViewPackagePage/components/repo-page-content.tsx +21 -20
  35. package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +18 -2
  36. package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +1 -1
  37. package/src/components/ViewPackagePage/components/sidebar.tsx +0 -2
  38. package/src/components/ViewPackagePage/components/tab-views/files-view.tsx +18 -17
  39. package/src/components/ViewPackagePage/components/theme-toggle.tsx +0 -2
  40. package/src/components/ViewPackagePage/hooks/use-toast.tsx +0 -1
  41. package/src/components/package-port/CodeAndPreview.tsx +23 -40
  42. package/src/components/package-port/CodeEditor.tsx +24 -1
  43. package/src/components/package-port/CodeEditorHeader.tsx +5 -2
  44. package/src/components/preview/BuildsList.tsx +20 -9
  45. package/src/components/preview/ConnectedPackagesList.tsx +73 -60
  46. package/src/components/preview/ConnectedRepoOverview.tsx +160 -154
  47. package/src/components/preview/PackageReleasesDashboard.tsx +41 -30
  48. package/src/components/preview/index.tsx +16 -153
  49. package/src/hooks/use-current-package-id.ts +5 -30
  50. package/src/hooks/use-current-package-info.ts +29 -5
  51. package/src/hooks/use-global-store.ts +1 -1
  52. package/src/hooks/useFileManagement.ts +153 -34
  53. package/src/hooks/useOptimizedPackageFilesLoader.ts +149 -0
  54. package/src/hooks/useUpdatePackageFilesMutation.ts +2 -0
  55. package/src/index.css +24 -0
  56. package/src/lib/download-fns/download-circuit-png.ts +11 -3
  57. package/src/lib/download-fns/download-gltf-from-circuit-json.ts +44 -0
  58. package/src/lib/utils/isComponentExported.ts +9 -0
  59. package/src/lib/utils/transformFilesToTreeData.tsx +195 -0
  60. package/src/pages/404.tsx +3 -5
  61. package/src/pages/authorize.tsx +0 -2
  62. package/src/pages/landing.tsx +0 -1
  63. package/src/pages/preview-release.tsx +279 -0
  64. package/src/pages/release-builds.tsx +0 -8
  65. package/src/pages/release-detail.tsx +17 -15
  66. package/src/pages/releases.tsx +5 -1
  67. package/src/pages/view-package.tsx +14 -13
  68. package/src/components/Footer2.tsx +0 -100
  69. package/src/components/ShippingInformationForm.tsx +0 -423
  70. package/src/components/StaticViewSnippetHeader.tsx +0 -70
  71. package/src/components/ViewPackagePage/components/file-explorer.tsx +0 -67
  72. package/src/components/ViewPackagePage/components/readme-view.tsx +0 -58
  73. package/src/components/ViewPackagePage/components/repo-header-button.tsx +0 -36
  74. package/src/components/ViewPackagePage/components/repo-header.tsx +0 -4
  75. package/src/components/ViewPackagePage/components/sidebar-contributors-section.tsx +0 -31
  76. package/src/components/ViewSnippetHeader.tsx +0 -181
  77. package/src/components/ui/input-otp.tsx +0 -69
  78. package/src/hooks/use-snippets-base-api-url.ts +0 -3
  79. package/src/pages/preview-build.tsx +0 -380
  80. package/src/pages/settings.tsx +0 -25
@@ -1,13 +1,14 @@
1
1
  export { ConnectedRepoOverview } from "./ConnectedRepoOverview"
2
2
  export { BuildsList } from "./BuildsList"
3
3
  export { PackageReleasesDashboard } from "./PackageReleasesDashboard"
4
- import {
5
- Package,
6
- PackageBuild,
7
- PackageRelease,
8
- } from "fake-snippets-api/lib/db/schema"
4
+ import { PackageBuild } from "fake-snippets-api/lib/db/schema"
9
5
  import { Clock, CheckCircle, AlertCircle, Loader2 } from "lucide-react"
10
- export const getBuildStatus = (build: PackageBuild | null) => {
6
+ export const getBuildStatus = (
7
+ build?: PackageBuild | null,
8
+ ): {
9
+ status: "pending" | "building" | "success" | "error" | "queued"
10
+ label: string
11
+ } => {
11
12
  if (!build) {
12
13
  return { status: "pending", label: "No builds" }
13
14
  }
@@ -25,154 +26,20 @@ export const getBuildStatus = (build: PackageBuild | null) => {
25
26
  ) {
26
27
  return { status: "building", label: "Building" }
27
28
  }
28
- if (build?.build_completed_at && build?.transpilation_completed_at) {
29
+ if (
30
+ !build?.build_error &&
31
+ !build?.transpilation_error &&
32
+ !build?.circuit_json_build_error &&
33
+ !build?.build_in_progress &&
34
+ !build?.transpilation_in_progress &&
35
+ !build?.circuit_json_build_in_progress &&
36
+ build?.transpilation_completed_at
37
+ ) {
29
38
  return { status: "success", label: "Ready" }
30
39
  }
31
40
  return { status: "queued", label: "Queued" }
32
41
  }
33
42
 
34
- export const MOCK_PACKAGE_BUILDS: PackageBuild[] = [
35
- {
36
- package_build_id: "pb_1a2b3c4d",
37
- package_release_id: "pr_5e6f7g8h",
38
- created_at: new Date(Date.now() - 1000 * 60 * 30).toISOString(),
39
- transpilation_in_progress: false,
40
- transpilation_started_at: new Date(
41
- Date.now() - 1000 * 60 * 35,
42
- ).toISOString(),
43
- transpilation_completed_at: new Date(
44
- Date.now() - 1000 * 60 * 32,
45
- ).toISOString(),
46
- transpilation_logs: [],
47
- transpilation_error: null,
48
- circuit_json_build_in_progress: false,
49
- circuit_json_build_started_at: new Date(
50
- Date.now() - 1000 * 60 * 32,
51
- ).toISOString(),
52
- circuit_json_build_completed_at: new Date(
53
- Date.now() - 1000 * 60 * 30,
54
- ).toISOString(),
55
- circuit_json_build_logs: [],
56
- circuit_json_build_error: null,
57
- build_in_progress: false,
58
- build_started_at: new Date(Date.now() - 1000 * 60 * 30).toISOString(),
59
- build_completed_at: new Date(Date.now() - 1000 * 60 * 25).toISOString(),
60
- build_error: null,
61
- build_error_last_updated_at: new Date(
62
- Date.now() - 1000 * 60 * 25,
63
- ).toISOString(),
64
- build_logs: null,
65
- preview_url: "https://preview.tscircuit.com/pb_1a2b3c4d",
66
- branch_name: "main",
67
- commit_message: "Add new LED component with improved brightness control",
68
- commit_author: "john.doe",
69
- },
70
- {
71
- package_build_id: "pb_9i8j7k6l",
72
- package_release_id: "pr_5m4n3o2p",
73
- created_at: new Date(Date.now() - 1000 * 60 * 60 * 2).toISOString(),
74
- transpilation_in_progress: false,
75
- transpilation_started_at: new Date(
76
- Date.now() - 1000 * 60 * 60 * 2,
77
- ).toISOString(),
78
- transpilation_completed_at: new Date(
79
- Date.now() - 1000 * 60 * 60 * 2 + 1000 * 60 * 3,
80
- ).toISOString(),
81
- transpilation_logs: [],
82
- transpilation_error: null,
83
- circuit_json_build_in_progress: true,
84
- circuit_json_build_started_at: new Date(
85
- Date.now() - 1000 * 60 * 5,
86
- ).toISOString(),
87
- circuit_json_build_completed_at: null,
88
- circuit_json_build_logs: [],
89
- circuit_json_build_error: null,
90
- build_in_progress: false,
91
- build_started_at: null,
92
- build_completed_at: null,
93
- build_error: null,
94
- build_error_last_updated_at: new Date(
95
- Date.now() - 1000 * 60 * 60 * 2,
96
- ).toISOString(),
97
- build_logs: null,
98
- preview_url: null,
99
- branch_name: "feature/resistor-update",
100
- commit_message: "Update resistor component with new tolerance values",
101
- commit_author: "jane.smith",
102
- },
103
- {
104
- package_build_id: "pb_1q2w3e4r",
105
- package_release_id: "pr_5t6y7u8i",
106
- created_at: new Date(Date.now() - 1000 * 60 * 60 * 6).toISOString(),
107
- transpilation_in_progress: false,
108
- transpilation_started_at: new Date(
109
- Date.now() - 1000 * 60 * 60 * 6,
110
- ).toISOString(),
111
- transpilation_completed_at: new Date(
112
- Date.now() - 1000 * 60 * 60 * 6 + 1000 * 60 * 2,
113
- ).toISOString(),
114
- transpilation_logs: [],
115
- transpilation_error:
116
- "TypeScript compilation failed: Cannot find module 'missing-dependency'",
117
- circuit_json_build_in_progress: false,
118
- circuit_json_build_started_at: null,
119
- circuit_json_build_completed_at: null,
120
- circuit_json_build_logs: [],
121
- circuit_json_build_error: null,
122
- build_in_progress: false,
123
- build_started_at: null,
124
- build_completed_at: null,
125
- build_error: null,
126
- build_error_last_updated_at: new Date(
127
- Date.now() - 1000 * 60 * 60 * 6,
128
- ).toISOString(),
129
- build_logs: null,
130
- preview_url: null,
131
- branch_name: "hotfix/critical-bug",
132
- commit_message: "Fix critical issue with capacitor placement",
133
- commit_author: "alex.wilson",
134
- },
135
- {
136
- package_build_id: "pb_9o8i7u6y",
137
- package_release_id: "pr_5t4r3e2w",
138
- created_at: new Date(Date.now() - 1000 * 60 * 60 * 24).toISOString(),
139
- transpilation_in_progress: false,
140
- transpilation_started_at: new Date(
141
- Date.now() - 1000 * 60 * 60 * 24,
142
- ).toISOString(),
143
- transpilation_completed_at: new Date(
144
- Date.now() - 1000 * 60 * 60 * 24 + 1000 * 60 * 4,
145
- ).toISOString(),
146
- transpilation_logs: [],
147
- transpilation_error: null,
148
- circuit_json_build_in_progress: false,
149
- circuit_json_build_started_at: new Date(
150
- Date.now() - 1000 * 60 * 60 * 24 + 1000 * 60 * 4,
151
- ).toISOString(),
152
- circuit_json_build_completed_at: new Date(
153
- Date.now() - 1000 * 60 * 60 * 24 + 1000 * 60 * 8,
154
- ).toISOString(),
155
- circuit_json_build_logs: [],
156
- circuit_json_build_error: null,
157
- build_in_progress: false,
158
- build_started_at: new Date(
159
- Date.now() - 1000 * 60 * 60 * 24 + 1000 * 60 * 8,
160
- ).toISOString(),
161
- build_completed_at: new Date(
162
- Date.now() - 1000 * 60 * 60 * 24 + 1000 * 60 * 12,
163
- ).toISOString(),
164
- build_error: null,
165
- build_error_last_updated_at: new Date(
166
- Date.now() - 1000 * 60 * 60 * 24 + 1000 * 60 * 12,
167
- ).toISOString(),
168
- build_logs: null,
169
- preview_url: "https://preview.tscircuit.com/pb_9o8i7u6y",
170
- branch_name: "main",
171
- commit_message: "Initial project setup with basic components",
172
- commit_author: "sarah.johnson",
173
- },
174
- ]
175
-
176
43
  export const StatusIcon = ({ status }: { status: string }) => {
177
44
  switch (status) {
178
45
  case "success":
@@ -185,7 +52,3 @@ export const StatusIcon = ({ status }: { status: string }) => {
185
52
  return <Clock className="w-4 h-4 text-gray-500" />
186
53
  }
187
54
  }
188
-
189
- export const getLatestBuildForPackage = (pkg: Package): PackageBuild => {
190
- return MOCK_PACKAGE_BUILDS[0]
191
- }
@@ -1,41 +1,16 @@
1
- import { useEffect, useState } from "react"
2
- import { useParams } from "wouter"
3
- import { usePackageById } from "./use-package-by-package-id"
4
- import { usePackageByName } from "./use-package-by-package-name"
5
- import { useUrlParams } from "./use-url-params"
1
+ import { useCurrentPackageInfo } from "./use-current-package-info"
6
2
 
7
3
  export const useCurrentPackageId = (): {
8
4
  packageId: string | null
9
5
  isLoading: boolean
10
6
  error: (Error & { status: number }) | null
11
7
  } => {
12
- const urlParams = useUrlParams()
13
- const urlPackageId = urlParams.package_id
14
- const wouter = useParams()
15
- const [packageIdFromUrl, setPackageId] = useState<string | null>(urlPackageId)
16
-
17
- useEffect(() => {
18
- if (urlPackageId) {
19
- setPackageId(urlPackageId)
20
- }
21
- }, [urlPackageId])
22
-
23
- const packageName =
24
- wouter.author && wouter.packageName
25
- ? `${wouter.author}/${wouter.packageName}`
26
- : null
27
-
28
- const {
29
- data: packageByName,
30
- isLoading: isLoadingPackageByName,
31
- error: errorPackageByName,
32
- } = usePackageByName(packageName)
33
-
34
- const packageId = packageIdFromUrl ?? packageByName?.package_id ?? null
8
+ const { packageInfo, isLoading, error } = useCurrentPackageInfo()
9
+ const packageId = packageInfo?.package_id ?? null
35
10
 
36
11
  return {
37
12
  packageId,
38
- isLoading: isLoadingPackageByName,
39
- error: errorPackageByName,
13
+ isLoading,
14
+ error,
40
15
  }
41
16
  }
@@ -1,8 +1,32 @@
1
- import { useCurrentPackageId } from "./use-current-package-id"
1
+ import { useParams } from "wouter"
2
2
  import { usePackageById } from "./use-package-by-package-id"
3
+ import { usePackageByName } from "./use-package-by-package-name"
4
+ import { useUrlParams } from "./use-url-params"
5
+ import type { Package } from "fake-snippets-api/lib/db/schema"
3
6
 
4
- export const useCurrentPackageInfo = () => {
5
- const { packageId } = useCurrentPackageId()
6
- const { data: packageInfo, ...rest } = usePackageById(packageId)
7
- return { packageInfo, ...rest }
7
+ export const useCurrentPackageInfo = (): {
8
+ packageInfo: Package | undefined
9
+ isLoading: boolean
10
+ error: (Error & { status: number }) | null
11
+ refetch: () => Promise<unknown>
12
+ } => {
13
+ const urlParams = useUrlParams()
14
+ const packageIdFromQuery = urlParams.package_id ?? null
15
+
16
+ const { author, packageName } = useParams()
17
+ const packageSlug = author && packageName ? `${author}/${packageName}` : null
18
+
19
+ const queryById = usePackageById(packageIdFromQuery)
20
+ const queryByName = usePackageByName(packageSlug)
21
+
22
+ const data = queryById.data ?? queryByName.data
23
+ const isLoading = queryById.isLoading || queryByName.isLoading
24
+ const error =
25
+ (queryById.error as (Error & { status: number }) | null) ??
26
+ (queryByName.error as (Error & { status: number }) | null) ??
27
+ null
28
+
29
+ const refetch = packageIdFromQuery ? queryById.refetch : queryByName.refetch
30
+
31
+ return { packageInfo: data, isLoading, error, refetch }
8
32
  }
@@ -28,7 +28,7 @@ export const useGlobalStore = create<Store>()(
28
28
  ),
29
29
  )
30
30
 
31
- useGlobalStore.subscribe((state, prevState) => {
31
+ useGlobalStore.subscribe((state) => {
32
32
  ;(window as any).globalStore = state
33
33
  window.TSCIRCUIT_REGISTRY_TOKEN = state.session?.token ?? null
34
34
  })
@@ -6,7 +6,7 @@ import { Package } from "fake-snippets-api/lib/db/schema"
6
6
  import { usePackageFiles } from "./use-package-files"
7
7
  import { decodeUrlHashToText } from "@/lib/decodeUrlHashToText"
8
8
  import { decodeUrlHashToFsMap } from "@/lib/decodeUrlHashToFsMap"
9
- import { usePackageFilesLoader } from "./usePackageFilesLoader"
9
+ import { useOptimizedPackageFilesLoader } from "./useOptimizedPackageFilesLoader"
10
10
  import { useGlobalStore } from "./use-global-store"
11
11
  import { useToast } from "@/components/ViewPackagePage/hooks/use-toast"
12
12
  import { useUpdatePackageFilesMutation } from "./useUpdatePackageFilesMutation"
@@ -15,6 +15,7 @@ import { useCreatePackageMutation } from "./use-create-package-mutation"
15
15
  import { findTargetFile } from "@/lib/utils/findTargetFile"
16
16
  import { encodeFsMapToUrlHash } from "@/lib/encodeFsMapToUrlHash"
17
17
  import { isHiddenFile } from "@/components/ViewPackagePage/utils/is-hidden-file"
18
+ import { isComponentExported } from "@/lib/utils/isComponentExported"
18
19
 
19
20
  export interface ICreateFileProps {
20
21
  newFileName: string
@@ -47,27 +48,37 @@ export interface IRenameFileResult {
47
48
  export function useFileManagement({
48
49
  templateCode,
49
50
  currentPackage,
50
- fileChoosen,
51
51
  openNewPackageSaveDialog,
52
52
  updateLastUpdated,
53
+ urlParams,
53
54
  }: {
54
55
  templateCode?: string
55
56
  currentPackage?: Package
56
- fileChoosen: string | null
57
57
  openNewPackageSaveDialog: () => void
58
+ urlParams: Record<string, string>
58
59
  updateLastUpdated: () => void
59
60
  }) {
61
+ const fileChosen = urlParams.file_path ?? null
60
62
  const [localFiles, setLocalFiles] = useState<PackageFile[]>([])
61
63
  const [initialFiles, setInitialFiles] = useState<PackageFile[]>([])
62
64
  const [currentFile, setCurrentFile] = useState<string | null>(null)
63
65
  const isLoggedIn = useGlobalStore((s) => Boolean(s.session))
64
- const loggedInUser = useGlobalStore((s) => s.session)
65
66
  const { toast } = useToast()
66
67
  const debounceTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
67
68
  const {
68
- data: packageFilesWithContent,
69
- isLoading: isLoadingPackageFilesWithContent,
70
- } = usePackageFilesLoader(currentPackage)
69
+ priorityFile,
70
+ allFiles: packageFilesWithContent,
71
+ isPriorityLoading,
72
+ areAllFilesLoading: isLoadingPackageFilesWithContent,
73
+ totalFilesCount,
74
+ loadedFilesCount,
75
+ isPriorityFileFetched,
76
+ } = useOptimizedPackageFilesLoader(
77
+ currentPackage,
78
+ fileChosen,
79
+ urlParams.package_id,
80
+ )
81
+
71
82
  const { data: packageFilesMeta, isLoading: isLoadingPackageFiles } =
72
83
  usePackageFiles(currentPackage?.latest_package_release_id)
73
84
  const initialCodeContent = useMemo(() => {
@@ -127,7 +138,7 @@ export function useFileManagement({
127
138
  })
128
139
 
129
140
  useEffect(() => {
130
- if (!currentPackage || isLoadingPackageFilesWithContent) {
141
+ if (!currentPackage || isPriorityLoading) {
131
142
  const decodedFsMap = decodeUrlHashToFsMap(window.location.toString())
132
143
 
133
144
  if (decodedFsMap && Object.keys(decodedFsMap).length > 0) {
@@ -137,38 +148,114 @@ export function useFileManagement({
137
148
  content: String(content),
138
149
  }),
139
150
  )
140
- const targetFile = findTargetFile(filesFromUrl, fileChoosen)
151
+ const targetFile = findTargetFile(filesFromUrl, fileChosen)
141
152
  setLocalFiles(filesFromUrl)
142
153
  setInitialFiles([])
143
154
  setCurrentFile(targetFile?.path || filesFromUrl[0]?.path || null)
144
155
  return
145
156
  }
146
157
 
147
- setLocalFiles([
148
- {
149
- path: "index.tsx",
150
- content: initialCodeContent || "",
151
- },
152
- ])
153
- setInitialFiles([])
154
- setCurrentFile("index.tsx")
158
+ if (!urlParams.package_id) {
159
+ setLocalFiles([
160
+ {
161
+ path: "index.tsx",
162
+ content: initialCodeContent || "",
163
+ },
164
+ ])
165
+ setInitialFiles([])
166
+ setCurrentFile("index.tsx")
167
+ }
155
168
  return
156
- } else {
157
- const targetFile = findTargetFile(
158
- packageFilesWithContent || [],
159
- fileChoosen,
160
- )
161
- setLocalFiles(packageFilesWithContent || [])
162
- setInitialFiles(packageFilesWithContent || [])
163
- setCurrentFile(targetFile?.path || null)
164
169
  }
165
- }, [currentPackage, isLoadingPackageFilesWithContent])
170
+ }, [currentPackage, isPriorityLoading])
171
+
172
+ useEffect(() => {
173
+ if (priorityFile && !isPriorityLoading && currentPackage) {
174
+ setLocalFiles((prev) => {
175
+ const existingIndex = prev.findIndex(
176
+ (f) => f.path === priorityFile.path,
177
+ )
178
+ if (existingIndex >= 0) {
179
+ const updated = [...prev]
180
+ updated[existingIndex] = priorityFile
181
+ return updated
182
+ }
183
+ return [...prev, priorityFile]
184
+ })
185
+
186
+ setCurrentFile((prevCurrentFile) => {
187
+ if (fileChosen && priorityFile.path === fileChosen) {
188
+ return priorityFile.path
189
+ } else if (!prevCurrentFile) {
190
+ return priorityFile.path
191
+ } else {
192
+ // If priority file is index.tsx, always update to it
193
+ const isPriorityFileBetter = priorityFile.path === "index.tsx"
194
+ if (isPriorityFileBetter) {
195
+ return priorityFile.path
196
+ }
197
+ }
198
+ return prevCurrentFile
199
+ })
200
+ }
201
+ }, [priorityFile, isPriorityLoading, currentPackage, fileChosen])
202
+
203
+ useEffect(() => {
204
+ if (packageFilesWithContent.length > 0 && currentPackage) {
205
+ setLocalFiles(packageFilesWithContent)
206
+ setInitialFiles(packageFilesWithContent)
207
+
208
+ if (fileChosen) {
209
+ const targetFile =
210
+ packageFilesWithContent.find((f) => f.path === fileChosen) ||
211
+ findTargetFile(packageFilesWithContent, fileChosen)
212
+ if (targetFile) {
213
+ setCurrentFile((prevCurrentFile) => {
214
+ return targetFile.path !== prevCurrentFile
215
+ ? targetFile.path
216
+ : prevCurrentFile
217
+ })
218
+ }
219
+ } else {
220
+ setCurrentFile((prevCurrentFile) => {
221
+ if (!prevCurrentFile) {
222
+ // Wait for priority file to load before making selection to avoid flicker
223
+ // Only select if we have a good candidate (tsx/ts file) or priority file isn't loading
224
+ const targetFile = findTargetFile(
225
+ packageFilesWithContent,
226
+ fileChosen,
227
+ )
228
+ // Only consider it a "good enough" candidate if it's index.tsx
229
+ // Otherwise, wait for the actual priority file (index.tsx) to load
230
+ const isTopPriorityFile =
231
+ targetFile && targetFile.path === "index.tsx"
232
+ const shouldWaitForPriority =
233
+ isPriorityLoading && !isTopPriorityFile
234
+
235
+ if (shouldWaitForPriority) {
236
+ return prevCurrentFile // Keep null to avoid flicker
237
+ }
238
+
239
+ return targetFile ? targetFile.path : prevCurrentFile
240
+ }
241
+ return prevCurrentFile
242
+ })
243
+ }
244
+ }
245
+ }, [
246
+ packageFilesWithContent.length,
247
+ currentPackage,
248
+ fileChosen,
249
+ isPriorityLoading,
250
+ ])
166
251
 
167
252
  const isLoading = useMemo(() => {
168
- return (
169
- isLoadingPackageFilesWithContent || isLoadingPackageFiles || !localFiles
170
- )
171
- }, [isLoadingPackageFilesWithContent, localFiles, isLoadingPackageFiles])
253
+ return isPriorityLoading || isLoadingPackageFiles
254
+ }, [isPriorityLoading, isLoadingPackageFiles])
255
+
256
+ const isFullyLoaded = useMemo(() => {
257
+ return !isLoadingPackageFilesWithContent && !isLoadingPackageFiles
258
+ }, [isLoadingPackageFilesWithContent, isLoadingPackageFiles])
172
259
 
173
260
  const fsMap = useMemo(() => {
174
261
  const map = localFiles.reduce(
@@ -178,6 +265,7 @@ export function useFileManagement({
178
265
  },
179
266
  {} as Record<string, string>,
180
267
  )
268
+
181
269
  return map
182
270
  }, [localFiles, manualEditsFileContent])
183
271
 
@@ -215,7 +303,6 @@ export function useFileManagement({
215
303
  }
216
304
  }
217
305
 
218
- // Check if file already exists
219
306
  const fileExists = localFiles?.some((file) => file.path === newFileName)
220
307
  if (fileExists) {
221
308
  onError(new Error(`File '${newFileName}' already exists`))
@@ -224,7 +311,6 @@ export function useFileManagement({
224
311
  }
225
312
  }
226
313
 
227
- // Ensure file name is not empty after path construction
228
314
  const fileName = newFileName.split("/").pop() || ""
229
315
  if (!fileName.trim()) {
230
316
  onError(new Error("File name cannot be empty"))
@@ -282,7 +368,6 @@ export function useFileManagement({
282
368
  }
283
369
  }
284
370
 
285
- // Extract just the filename from the path for validation
286
371
  const fileNameOnly = newFilename.split("/").pop() || ""
287
372
  if (!isValidFileName(fileNameOnly)) {
288
373
  onError(new Error("Invalid file name"))
@@ -395,7 +480,6 @@ export function useFileManagement({
395
480
 
396
481
  const saveFiles = () => {
397
482
  if (!isLoggedIn) {
398
- // For non-logged-in users, trigger immediate URL save
399
483
  if (debounceTimeoutRef.current) {
400
484
  clearTimeout(debounceTimeoutRef.current)
401
485
  }
@@ -433,20 +517,55 @@ export function useFileManagement({
433
517
  isCreatingRelease,
434
518
  ])
435
519
 
520
+ const currentFileCode = useMemo(
521
+ () =>
522
+ localFiles.find((x) => x.path === currentFile)?.content ?? DEFAULT_CODE,
523
+ [localFiles, currentFile],
524
+ )
525
+ const mainComponentPath = useMemo(() => {
526
+ const isComponentExportedInCurrentFile =
527
+ isComponentExported(currentFileCode)
528
+
529
+ const selectedComponent =
530
+ (currentFile?.endsWith(".tsx") || currentFile?.endsWith(".ts")) &&
531
+ !!localFiles.some((x) => x.path === currentFile) &&
532
+ isComponentExportedInCurrentFile
533
+ ? currentFile
534
+ : localFiles.find((x) => {
535
+ const isComponentExportedInFile = isComponentExported(x.content)
536
+ return (
537
+ x.path.endsWith(".tsx") ||
538
+ (x.path.endsWith(".ts") && isComponentExportedInFile)
539
+ )
540
+ })?.path || localFiles[0]?.path
541
+
542
+ return selectedComponent
543
+ }, [currentFile, localFiles, currentFileCode, packageFilesWithContent])
544
+
545
+ const priorityFileFetched = useMemo(() => {
546
+ return urlParams.package_id && isPriorityFileFetched
547
+ }, [urlParams.package_id, isPriorityFileFetched])
548
+
436
549
  return {
437
550
  fsMap,
438
551
  createFile,
552
+ priorityFileFetched,
439
553
  deleteFile,
440
554
  renameFile,
441
555
  saveFiles,
442
556
  localFiles,
557
+ mainComponentPath,
558
+ currentFileCode,
443
559
  initialFiles,
444
560
  currentFile,
445
561
  setLocalFiles,
446
562
  onFileSelect,
447
563
  isLoading,
564
+ isFullyLoaded,
448
565
  isSaving,
449
566
  savePackage,
450
567
  packageFilesMeta,
568
+ totalFilesCount,
569
+ loadedFilesCount,
451
570
  }
452
571
  }