@tscircuit/fake-snippets 0.0.108 → 0.0.110

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 (203) hide show
  1. package/.github/workflows/bun-formatcheck.yml +2 -2
  2. package/.github/workflows/bun-pver-release.yml +3 -3
  3. package/.github/workflows/bun-test.yml +1 -1
  4. package/.github/workflows/bun-typecheck.yml +2 -2
  5. package/.github/workflows/update-snapshots.yml +1 -1
  6. package/README.md +4 -0
  7. package/api/generated-index.js +37 -3
  8. package/biome.json +2 -1
  9. package/bun-tests/fake-snippets-api/fixtures/get-test-server.ts +31 -3
  10. package/bun-tests/fake-snippets-api/fixtures/preload.ts +18 -0
  11. package/bun-tests/fake-snippets-api/routes/orgs/add_member.test.ts +26 -0
  12. package/bun-tests/fake-snippets-api/routes/orgs/create.test.ts +37 -0
  13. package/bun-tests/fake-snippets-api/routes/orgs/get.test.ts +52 -0
  14. package/bun-tests/fake-snippets-api/routes/orgs/list.test.ts +17 -0
  15. package/bun-tests/fake-snippets-api/routes/orgs/list_members.test.ts +23 -0
  16. package/bun-tests/fake-snippets-api/routes/orgs/remove_member.test.ts +81 -0
  17. package/bun-tests/fake-snippets-api/routes/orgs/update.test.ts +99 -0
  18. package/bun-tests/fake-snippets-api/routes/package_builds/get.test.ts +1 -1
  19. package/bun-tests/fake-snippets-api/routes/package_files/create.test.ts +15 -13
  20. package/bun-tests/fake-snippets-api/routes/package_files/create_or_update.test.ts +26 -24
  21. package/bun-tests/fake-snippets-api/routes/package_files/delete.test.ts +9 -9
  22. package/bun-tests/fake-snippets-api/routes/package_files/download.test.ts +4 -4
  23. package/bun-tests/fake-snippets-api/routes/package_files/get.test.ts +38 -28
  24. package/bun-tests/fake-snippets-api/routes/package_files/list.test.ts +23 -15
  25. package/bun-tests/fake-snippets-api/routes/package_releases/create.test.ts +33 -0
  26. package/bun-tests/fake-snippets-api/routes/package_releases/get.test.ts +4 -4
  27. package/bun-tests/fake-snippets-api/routes/package_releases/get_image_generation_fields.test.ts +38 -0
  28. package/bun-tests/fake-snippets-api/routes/packages/create.test.ts +19 -0
  29. package/bun-tests/fake-snippets-api/routes/packages/fork.test.ts +3 -4
  30. package/bun-tests/fake-snippets-api/routes/packages/get.test.ts +30 -0
  31. package/bun-tests/fake-snippets-api/routes/packages/images.test.ts +4 -2
  32. package/bun-tests/fake-snippets-api/routes/packages/list-1.test.ts +34 -0
  33. package/bun.lock +389 -450
  34. package/bunfig.toml +2 -1
  35. package/dist/bundle.js +1255 -625
  36. package/dist/index.d.ts +296 -4
  37. package/dist/index.js +325 -24
  38. package/dist/schema.d.ts +282 -1
  39. package/dist/schema.js +54 -2
  40. package/fake-snippets-api/lib/db/autoload-dev-packages.ts +31 -20
  41. package/fake-snippets-api/lib/db/db-client.ts +214 -3
  42. package/fake-snippets-api/lib/db/schema.ts +62 -0
  43. package/fake-snippets-api/lib/db/seed.ts +100 -0
  44. package/fake-snippets-api/lib/middleware/with-session-auth.ts +1 -1
  45. package/fake-snippets-api/lib/package_file/get-package-file-id-from-file-descriptor.ts +2 -2
  46. package/fake-snippets-api/lib/public-mapping/public-map-org.ts +32 -0
  47. package/fake-snippets-api/lib/public-mapping/public-map-package-build.ts +10 -0
  48. package/fake-snippets-api/lib/public-mapping/public-map-package-release.ts +17 -0
  49. package/fake-snippets-api/routes/api/orgs/add_member.ts +52 -0
  50. package/fake-snippets-api/routes/api/orgs/create.ts +46 -0
  51. package/fake-snippets-api/routes/api/orgs/get.ts +39 -0
  52. package/fake-snippets-api/routes/api/orgs/list.ts +31 -0
  53. package/fake-snippets-api/routes/api/orgs/list_members.ts +67 -0
  54. package/fake-snippets-api/routes/api/orgs/remove_member.ts +46 -0
  55. package/fake-snippets-api/routes/api/orgs/update.ts +93 -0
  56. package/fake-snippets-api/routes/api/package_files/get.ts +3 -6
  57. package/fake-snippets-api/routes/api/package_files/list.ts +7 -4
  58. package/fake-snippets-api/routes/api/packages/create.ts +54 -10
  59. package/fake-snippets-api/routes/api/packages/get.ts +23 -0
  60. package/fake-snippets-api/routes/api/packages/images/[owner_github_username]/[unscoped_name]/[view_format].ts +13 -11
  61. package/fake-snippets-api/routes/api/packages/list.ts +29 -2
  62. package/fake-snippets-api/routes/api/packages/update_ai_description.ts +37 -0
  63. package/package.json +27 -24
  64. package/renovate.json +1 -1
  65. package/scripts/generate-sitemap.ts +1 -1
  66. package/src/App.tsx +29 -10
  67. package/src/ContextProviders.tsx +25 -2
  68. package/src/components/CircuitJsonImportDialog.tsx +1 -1
  69. package/src/components/CmdKMenu.tsx +281 -247
  70. package/src/components/DownloadButtonAndMenu.tsx +133 -36
  71. package/src/components/FileSidebar.tsx +41 -50
  72. package/src/components/Footer.tsx +8 -10
  73. package/src/components/Header.tsx +19 -32
  74. package/src/components/Header2.tsx +16 -32
  75. package/src/components/HeaderDropdown.tsx +13 -8
  76. package/src/components/HeaderLogin.tsx +44 -16
  77. package/src/components/HiddenFilesDropdown.tsx +0 -2
  78. package/src/components/NotFound.tsx +5 -5
  79. package/src/components/PackageBreadcrumb.tsx +6 -12
  80. package/src/components/PackageCard.tsx +0 -1
  81. package/src/components/PackageSearchResults.tsx +1 -1
  82. package/src/components/PrefetchPageLink.tsx +7 -1
  83. package/src/components/ProfileRouter.tsx +32 -0
  84. package/src/components/SearchComponent.tsx +12 -8
  85. package/src/components/UserCard.tsx +80 -0
  86. package/src/components/ViewPackagePage/components/ShikiCodeViewer.tsx +20 -11
  87. package/src/components/ViewPackagePage/components/build-status.tsx +1 -1
  88. package/src/components/ViewPackagePage/components/important-files-view.tsx +174 -87
  89. package/src/components/ViewPackagePage/components/main-content-header.tsx +8 -4
  90. package/src/components/ViewPackagePage/components/main-content-view-selector.tsx +1 -2
  91. package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +54 -20
  92. package/src/components/ViewPackagePage/components/package-header.tsx +26 -37
  93. package/src/components/ViewPackagePage/components/preview-image-squares.tsx +11 -19
  94. package/src/components/ViewPackagePage/components/repo-page-content.tsx +33 -25
  95. package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +16 -10
  96. package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +11 -11
  97. package/src/components/ViewPackagePage/components/sidebar.tsx +0 -2
  98. package/src/components/ViewPackagePage/components/tab-views/files-view.tsx +18 -17
  99. package/src/components/ViewPackagePage/components/tab-views/pcb-view.tsx +1 -2
  100. package/src/components/ViewPackagePage/components/tab-views/schematic-view.tsx +2 -1
  101. package/src/components/ViewPackagePage/components/theme-toggle.tsx +0 -2
  102. package/src/components/ViewPackagePage/hooks/use-toast.tsx +0 -1
  103. package/src/components/dialogs/GitHubRepositorySelector.tsx +56 -49
  104. package/src/components/dialogs/edit-package-details-dialog.tsx +5 -6
  105. package/src/components/dialogs/import-component-dialog.tsx +16 -9
  106. package/src/components/dialogs/import-package-dialog.tsx +3 -2
  107. package/src/components/dialogs/new-package-save-prompt-dialog.tsx +190 -0
  108. package/src/components/organization/OrganizationCard.tsx +204 -0
  109. package/src/components/organization/OrganizationCardSkeleton.tsx +55 -0
  110. package/src/components/organization/OrganizationHeader.tsx +154 -0
  111. package/src/components/organization/OrganizationMembers.tsx +146 -0
  112. package/src/components/package-port/CodeAndPreview.tsx +32 -46
  113. package/src/components/package-port/CodeEditor.tsx +28 -31
  114. package/src/components/package-port/CodeEditorHeader.tsx +128 -63
  115. package/src/components/package-port/EditorNav.tsx +32 -49
  116. package/src/components/preview/ConnectedPackagesList.tsx +8 -8
  117. package/src/components/preview/ConnectedRepoOverview.tsx +102 -2
  118. package/src/components/preview/PackageReleasesDashboard.tsx +53 -36
  119. package/src/components/ui/tree-view.tsx +6 -3
  120. package/src/hooks/use-add-org-member-mutation.ts +51 -0
  121. package/src/hooks/use-create-org-mutation.ts +38 -0
  122. package/src/hooks/use-create-package-mutation.ts +3 -0
  123. package/src/hooks/use-current-package-id.ts +5 -30
  124. package/src/hooks/use-current-package-info.ts +29 -5
  125. package/src/hooks/use-current-package-release.ts +4 -3
  126. package/src/hooks/use-download-zip.ts +2 -2
  127. package/src/hooks/use-global-store.ts +6 -4
  128. package/src/hooks/use-jlcpcb-component-import.tsx +164 -0
  129. package/src/hooks/use-list-org-members.ts +27 -0
  130. package/src/hooks/use-list-user-orgs.ts +25 -0
  131. package/src/hooks/use-org-by-github-handle.ts +26 -0
  132. package/src/hooks/use-org.ts +24 -0
  133. package/src/hooks/use-organization.ts +42 -0
  134. package/src/hooks/use-package-as-snippet.ts +4 -2
  135. package/src/hooks/use-package-builds.ts +6 -2
  136. package/src/hooks/use-package-files.ts +5 -3
  137. package/src/hooks/use-package-release-by-id-or-version.ts +29 -20
  138. package/src/hooks/use-package-release-images.ts +105 -0
  139. package/src/hooks/use-package-release.ts +2 -2
  140. package/src/hooks/use-package-stars.ts +80 -4
  141. package/src/hooks/use-preview-images.ts +6 -3
  142. package/src/hooks/use-remove-org-member-mutation.ts +32 -0
  143. package/src/hooks/use-update-ai-description-mutation.ts +42 -0
  144. package/src/hooks/use-update-org-mutation.ts +41 -0
  145. package/src/hooks/use-warn-user-on-page-change.ts +71 -4
  146. package/src/hooks/useFileManagement.ts +183 -35
  147. package/src/hooks/useOptimizedPackageFilesLoader.ts +136 -0
  148. package/src/hooks/usePackageFilesLoader.ts +2 -2
  149. package/src/hooks/useUpdatePackageFilesMutation.ts +15 -1
  150. package/src/lib/download-fns/download-circuit-png.ts +11 -3
  151. package/src/lib/download-fns/download-gltf-from-circuit-json.ts +44 -0
  152. package/src/lib/download-fns/download-kicad-files.ts +12 -11
  153. package/src/lib/normalize-svg-for-tile.ts +50 -0
  154. package/src/lib/posthog.ts +11 -9
  155. package/src/lib/react-query-api-failure-tracking.ts +148 -0
  156. package/src/lib/sentry.ts +14 -0
  157. package/src/lib/templates/blank-circuit-board-template.ts +0 -4
  158. package/src/lib/ts-lib-cache.ts +122 -7
  159. package/src/lib/utils/checkIfManualEditsImported.ts +4 -4
  160. package/src/lib/utils/findTargetFile.ts +45 -10
  161. package/src/lib/utils/isComponentExported.ts +10 -0
  162. package/src/main.tsx +2 -1
  163. package/src/pages/authorize.tsx +0 -2
  164. package/src/pages/create-organization.tsx +168 -0
  165. package/src/pages/dashboard.tsx +38 -6
  166. package/src/pages/datasheet.tsx +1 -1
  167. package/src/pages/datasheets.tsx +3 -3
  168. package/src/pages/editor.tsx +4 -6
  169. package/src/pages/landing.tsx +6 -7
  170. package/src/pages/latest.tsx +3 -0
  171. package/src/pages/organization-profile.tsx +199 -0
  172. package/src/pages/organization-settings.tsx +566 -0
  173. package/src/pages/package-editor.tsx +21 -21
  174. package/src/pages/preview-release.tsx +76 -136
  175. package/src/pages/quickstart.tsx +159 -123
  176. package/src/pages/release-detail.tsx +119 -31
  177. package/src/pages/search.tsx +192 -57
  178. package/src/pages/settings-redirect.tsx +44 -0
  179. package/src/pages/trending.tsx +29 -20
  180. package/src/pages/user-profile.tsx +58 -7
  181. package/src/pages/view-package.tsx +21 -26
  182. package/vite.config.ts +9 -0
  183. package/fake-snippets-api/routes/api/autocomplete/create_autocomplete.ts +0 -133
  184. package/src/components/Footer2.tsx +0 -100
  185. package/src/components/JLCPCBImportDialog.tsx +0 -280
  186. package/src/components/PackageBuildsPage/LogContent.tsx +0 -72
  187. package/src/components/PackageBuildsPage/PackageBuildDetailsPage.tsx +0 -115
  188. package/src/components/PackageBuildsPage/build-preview-content.tsx +0 -27
  189. package/src/components/PackageBuildsPage/collapsible-section.tsx +0 -63
  190. package/src/components/PackageBuildsPage/package-build-details-panel.tsx +0 -166
  191. package/src/components/PackageBuildsPage/package-build-header.tsx +0 -79
  192. package/src/components/PageSearchComponent.tsx +0 -148
  193. package/src/components/ShippingInformationForm.tsx +0 -423
  194. package/src/components/StaticViewSnippetHeader.tsx +0 -70
  195. package/src/components/ViewPackagePage/components/file-explorer.tsx +0 -67
  196. package/src/components/ViewPackagePage/components/readme-view.tsx +0 -58
  197. package/src/components/ViewPackagePage/components/repo-header-button.tsx +0 -36
  198. package/src/components/ViewPackagePage/components/repo-header.tsx +0 -4
  199. package/src/components/ViewPackagePage/components/sidebar-contributors-section.tsx +0 -31
  200. package/src/components/ViewSnippetHeader.tsx +0 -181
  201. package/src/components/ui/input-otp.tsx +0 -69
  202. package/src/pages/package-builds.tsx +0 -33
  203. package/src/pages/settings.tsx +0 -25
@@ -1,4 +1,4 @@
1
- import React, { useState, useCallback } from "react"
1
+ import React, { useState, useCallback, useMemo } from "react"
2
2
  import { Button } from "@/components/ui/button"
3
3
  import { handleManualEditsImportWithSupportForMultipleFiles } from "@/lib/handleManualEditsImportWithSupportForMultipleFiles"
4
4
  import { useImportComponentDialog } from "@/components/dialogs/import-component-dialog"
@@ -25,11 +25,14 @@ import {
25
25
  TooltipProvider,
26
26
  TooltipTrigger,
27
27
  } from "@/components/ui/tooltip"
28
- import { convertRawEasyToTsx, fetchEasyEDAComponent } from "easyeda/browser"
29
- import { ComponentSearchResult } from "@tscircuit/runframe/runner"
30
- import { useApiBaseUrl } from "@/hooks/use-packages-base-api-url"
28
+ import {
29
+ JlcpcbComponentTsxLoadedPayload,
30
+ KicadStringSelectedPayload,
31
+ TscircuitPackageSelectedPayload,
32
+ } from "@tscircuit/runframe/runner"
31
33
  import { ICreateFileProps, ICreateFileResult } from "@/hooks/useFileManagement"
32
34
  import { useGlobalStore } from "@/hooks/use-global-store"
35
+ import { openJlcpcbImportIssue } from "@/hooks/use-jlcpcb-component-import"
33
36
 
34
37
  export type FileName = string
35
38
 
@@ -41,6 +44,7 @@ interface CodeEditorHeaderProps {
41
44
  handleFileChange: (filename: FileName) => void
42
45
  entrypointFileName?: string
43
46
  appendNewFile: (path: string, content: string) => void
47
+ isLoadingFiles: boolean
44
48
  createFile: (props: ICreateFileProps) => ICreateFileResult
45
49
  aiAutocompleteState: [boolean, React.Dispatch<React.SetStateAction<boolean>>]
46
50
  }
@@ -49,8 +53,8 @@ export const CodeEditorHeader: React.FC<CodeEditorHeaderProps> = ({
49
53
  currentFile,
50
54
  files,
51
55
  updateFileContent,
52
- appendNewFile,
53
56
  fileSidebarState,
57
+ isLoadingFiles = true,
54
58
  handleFileChange,
55
59
  entrypointFileName = "index.tsx",
56
60
  createFile,
@@ -58,12 +62,18 @@ export const CodeEditorHeader: React.FC<CodeEditorHeaderProps> = ({
58
62
  }) => {
59
63
  const { Dialog: ImportComponentDialog, openDialog: openImportDialog } =
60
64
  useImportComponentDialog()
61
- const { toast, toastLibrary } = useToast()
65
+ const { toast } = useToast()
62
66
  const [sidebarOpen, setSidebarOpen] = fileSidebarState
63
- const API_BASE = useApiBaseUrl()
64
67
  const [aiAutocompleteEnabled, setAiAutocompleteEnabled] = aiAutocompleteState
65
68
  const session = useGlobalStore((s) => s.session)
66
69
 
70
+ const jlcpcbProxyRequestHeaders = useMemo(() => {
71
+ if (!session?.token) return undefined
72
+ return {
73
+ Authorization: `Bearer ${session.token}`,
74
+ }
75
+ }, [session?.token])
76
+
67
77
  const handleFormatFile = useCallback(() => {
68
78
  if (!window.prettier || !window.prettierPlugins) return
69
79
  if (!currentFile) return
@@ -153,54 +163,114 @@ export const CodeEditorHeader: React.FC<CodeEditorHeaderProps> = ({
153
163
  }
154
164
  }, [currentFile, files, toast, updateFileContent])
155
165
 
156
- const handleComponentImport = async (component: ComponentSearchResult) => {
157
- if (component.source == "tscircuit.com") {
158
- const newContent = `import {} from "@tsci/${component.owner}.${component.name}"\n${files[currentFile || ""]}`
159
- updateFileContent(currentFile, newContent)
160
- }
161
- if (component.source == "jlcpcb") {
162
- if (!session?.token) {
163
- throw new Error("You need to be logged in to import jlcpcb component")
166
+ const handleTscircuitPackageSelected = useCallback(
167
+ async ({ fullPackageName }: TscircuitPackageSelectedPayload) => {
168
+ if (!currentFile) {
169
+ const message = "Select a file before importing a component."
170
+ toast({
171
+ title: "No file selected",
172
+ description: message,
173
+ variant: "destructive",
174
+ })
175
+ throw new Error(message)
164
176
  }
165
177
 
166
- const jlcpcbComponent = await fetchEasyEDAComponent(
167
- component.partNumber ?? component.name,
168
- {
169
- fetch: ((url, options: any) => {
170
- return fetch(`${API_BASE}/proxy`, {
171
- body: options.body,
172
- method: options.method,
173
- headers: {
174
- authority: options.headers.authority,
175
- Authorization: `Bearer ${session?.token}`,
176
- "X-Target-Url": url.toString(),
177
- "X-Sender-Host": options.headers.origin,
178
- "X-Sender-Origin": options.headers.origin,
179
- "content-type": options.headers["content-type"],
180
- },
181
- })
182
- }) as typeof fetch,
183
- },
184
- )
185
- const tsxComponent = await convertRawEasyToTsx(jlcpcbComponent)
186
- let componentName = component.name.replace(/ /g, "-")
187
- let componentPath = `imports/${componentName}.tsx`
188
- if (files[componentPath] || files[`./${componentPath}`]) {
189
- componentName = `${componentName}-1`
190
- componentPath = `imports/${componentName}.tsx`
191
- }
192
- const createFileResult = createFile({
193
- newFileName: componentPath,
194
- content: tsxComponent,
195
- onError: (error) => {
196
- throw error
197
- },
178
+ const existingContent = files[currentFile] ?? ""
179
+ const newContent = `import {} from "${fullPackageName}"\n${existingContent}`
180
+ updateFileContent(currentFile, newContent)
181
+ toast({
182
+ title: "Component imported",
183
+ description: `Added ${fullPackageName} to ${currentFile}.`,
198
184
  })
199
- if (!createFileResult.newFileCreated) {
200
- throw new Error("Failed to create component file")
185
+ },
186
+ [currentFile, files, toast, updateFileContent],
187
+ )
188
+
189
+ const handleJlcpcbComponentTsxLoaded = useCallback(
190
+ async ({ result, tsx }: JlcpcbComponentTsxLoadedPayload) => {
191
+ const partNumber = result.component.partNumber || "component"
192
+
193
+ try {
194
+ const sanitizedBaseName = partNumber
195
+ .toLowerCase()
196
+ .replace(/[^a-z0-9_-]/gi, "-")
197
+ let componentPath = `imports/${sanitizedBaseName}.tsx`
198
+ let suffix = 1
199
+ while (files[componentPath] || files[`./${componentPath}`]) {
200
+ componentPath = `imports/${sanitizedBaseName}-${suffix}.tsx`
201
+ suffix += 1
202
+ }
203
+
204
+ const createFileResult = createFile({
205
+ newFileName: componentPath,
206
+ content: tsx,
207
+ onError: (error) => {
208
+ throw error
209
+ },
210
+ })
211
+
212
+ if (!createFileResult.newFileCreated) {
213
+ throw new Error("Failed to create component file")
214
+ }
215
+
216
+ toast({
217
+ title: "Component imported",
218
+ description: `${partNumber} saved to ${componentPath}.`,
219
+ })
220
+ } catch (error) {
221
+ const message =
222
+ error instanceof Error
223
+ ? error.message
224
+ : "Failed to import component from JLCPCB"
225
+
226
+ toast({
227
+ title: "JLCPCB import failed",
228
+ description: (
229
+ <div className="space-y-2">
230
+ <p>{message}</p>
231
+ <button
232
+ className="text-sm text-blue-500 hover:underline"
233
+ onClick={(event) => {
234
+ event.preventDefault()
235
+ openJlcpcbImportIssue(partNumber, message)
236
+ }}
237
+ >
238
+ File issue on GitHub
239
+ </button>
240
+ </div>
241
+ ),
242
+ variant: "destructive",
243
+ })
244
+
245
+ throw new Error(message)
201
246
  }
202
- }
203
- }
247
+ },
248
+ [createFile, files, toast],
249
+ )
250
+
251
+ const handleKicadStringSelected = useCallback(
252
+ async ({ footprint, result }: KicadStringSelectedPayload) => {
253
+ try {
254
+ await navigator.clipboard.writeText(footprint)
255
+ toast({
256
+ title: "KiCad footprint copied",
257
+ description: `${result.footprint.qualifiedName} copied to clipboard.`,
258
+ })
259
+ } catch (error) {
260
+ const message =
261
+ error instanceof Error
262
+ ? error.message
263
+ : "Failed to copy KiCad footprint to clipboard"
264
+ toast({
265
+ title: "KiCad import failed",
266
+ description: message,
267
+ variant: "destructive",
268
+ })
269
+ throw new Error(message)
270
+ }
271
+ },
272
+ [toast],
273
+ )
204
274
 
205
275
  return (
206
276
  <>
@@ -230,7 +300,9 @@ export const CodeEditorHeader: React.FC<CodeEditorHeaderProps> = ({
230
300
  (filename) => !isHiddenFile(filename),
231
301
  ).length > 0
232
302
  ? "Select file"
233
- : "No files"
303
+ : isLoadingFiles
304
+ ? "Loading files..."
305
+ : "No files"
234
306
  }
235
307
  />
236
308
  </SelectTrigger>
@@ -342,17 +414,10 @@ export const CodeEditorHeader: React.FC<CodeEditorHeaderProps> = ({
342
414
  </Button>
343
415
  </div>
344
416
  <ImportComponentDialog
345
- onComponentSelected={async (component) => {
346
- toastLibrary.promise(handleComponentImport(component), {
347
- loading: "Importing component...",
348
- success: <p>Component imported successfully!</p>,
349
- error: (error) => (
350
- <p>
351
- Error importing component: {error.message || String(error)}
352
- </p>
353
- ),
354
- })
355
- }}
417
+ onTscircuitPackageSelected={handleTscircuitPackageSelected}
418
+ onJlcpcbComponentTsxLoaded={handleJlcpcbComponentTsxLoaded}
419
+ onKicadStringSelected={handleKicadStringSelected}
420
+ jlcpcbProxyRequestHeaders={jlcpcbProxyRequestHeaders}
356
421
  />
357
422
  </div>
358
423
  </>
@@ -1,5 +1,5 @@
1
1
  import { Button } from "@/components/ui/button"
2
- import { GitFork, Star } from "lucide-react"
2
+ import { GitFork, Loader2, Star } from "lucide-react"
3
3
  import {
4
4
  DropdownMenu,
5
5
  DropdownMenuContent,
@@ -42,7 +42,6 @@ import { useViewTsFilesDialog } from "@/components/dialogs/view-ts-files-dialog"
42
42
  import { DownloadButtonAndMenu } from "@/components/DownloadButtonAndMenu"
43
43
  import { TypeBadge } from "@/components/TypeBadge"
44
44
  import { useForkPackageMutation } from "@/hooks/useForkPackageMutation"
45
- import tscircuitCorePkg from "@tscircuit/core/package.json"
46
45
  import { useRenamePackageDialog } from "../dialogs/rename-package-dialog"
47
46
  import { useUpdatePackageDescriptionDialog } from "../dialogs/update-package-description-dialog"
48
47
  import { useCreateReleaseDialog } from "@/hooks/use-create-release-dialog"
@@ -295,52 +294,37 @@ export default function EditorNav({
295
294
  Not logged in, can't save
296
295
  </div>
297
296
  )}
298
- <Button
299
- variant="outline"
300
- size="sm"
301
- className={"ml-1 h-6 px-2 text-xs save-button"}
302
- disabled={canSavePackage && pkg ? !hasUnsavedChanges : !isLoggedIn}
303
- onClick={canSavePackage ? onSave : () => forkSnippet()}
304
- >
305
- {canSavePackage ? (
306
- <>
307
- <Save className="mr-1 h-3 w-3" />
308
- Save
309
- </>
310
- ) : (
311
- <>
312
- <GitFork className="mr-1 h-3 w-3" />
313
- Fork
314
- </>
315
- )}
316
- </Button>
297
+ {(canSavePackage || (isLoggedIn && pkg)) && (
298
+ <Button
299
+ variant="outline"
300
+ size="sm"
301
+ className={"ml-1 h-6 px-2 text-xs save-button"}
302
+ disabled={
303
+ canSavePackage && pkg ? !hasUnsavedChanges : !isLoggedIn
304
+ }
305
+ onClick={canSavePackage ? onSave : () => forkSnippet()}
306
+ >
307
+ {canSavePackage ? (
308
+ <>
309
+ <Save className="mr-1 h-3 w-3" />
310
+ Save
311
+ </>
312
+ ) : (
313
+ <>
314
+ <GitFork className="mr-1 h-3 w-3" />
315
+ Fork
316
+ </>
317
+ )}
318
+ </Button>
319
+ )}
317
320
  {isSaving && (
318
- <div className="animate-fadeIn bg-blue-100 text-blue-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
319
- <svg
320
- className="animate-spin h-3 w-3 mr-2 text-blue-600"
321
- xmlns="http://www.w3.org/2000/svg"
322
- fill="none"
323
- viewBox="0 0 24 24"
324
- >
325
- <circle
326
- className="opacity-25"
327
- cx="12"
328
- cy="12"
329
- r="10"
330
- stroke="currentColor"
331
- strokeWidth="4"
332
- ></circle>
333
- <path
334
- className="opacity-75"
335
- fill="currentColor"
336
- d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
337
- ></path>
338
- </svg>
321
+ <div className="animate-fadeIn bg-blue-100 select-none text-blue-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
322
+ <Loader2 className="animate-spin h-3 w-3 mr-2 text-blue-600" />
339
323
  Saving...
340
324
  </div>
341
325
  )}
342
326
  {hasUnsavedChanges && !isSaving && isLoggedIn && (
343
- <div className="animate-fadeIn bg-yellow-100 text-yellow-800 text-xs font-medium px-2.5 py-0.5 rounded">
327
+ <div className="animate-fadeIn bg-yellow-100 select-none text-yellow-800 text-xs font-medium px-2.5 py-0.5 rounded">
344
328
  {pkg ? "unsaved changes" : "unsaved"}
345
329
  </div>
346
330
  )}
@@ -359,7 +343,6 @@ export default function EditorNav({
359
343
  </div>
360
344
  <div className="flex items-center justify-end -space-x-1">
361
345
  <div className="flex mx-2 items-center space-x-1">
362
- {pkg && <TypeBadge type={`${packageType ?? pkg.snippet_type}`} />}
363
346
  {/* <Button
364
347
  variant="ghost"
365
348
  size="sm"
@@ -481,9 +464,6 @@ export default function EditorNav({
481
464
  </DropdownMenuItem>
482
465
  </>
483
466
  )}
484
- <DropdownMenuItem className="text-xs text-gray-500" disabled>
485
- @tscircuit/core@{tscircuitCorePkg.version}
486
- </DropdownMenuItem>
487
467
  </DropdownMenuContent>
488
468
  </DropdownMenu>
489
469
 
@@ -505,7 +485,7 @@ export default function EditorNav({
505
485
  )}
506
486
  </Button>
507
487
  </div>
508
- <div className="flex items-center ">
488
+ <div className="flex items-center">
509
489
  <DropdownMenu>
510
490
  <DropdownMenuTrigger asChild>
511
491
  <div className="md:hidden rounded-full p-1 hover:bg-gray-100 cursor-pointer">
@@ -514,7 +494,7 @@ export default function EditorNav({
514
494
  </Button>
515
495
  </div>
516
496
  </DropdownMenuTrigger>
517
- <DropdownMenuContent>
497
+ <DropdownMenuContent className="z-[101]">
518
498
  {hasUnsavedChanges && onDiscard && (
519
499
  <DropdownMenuItem
520
500
  className="text-xs text-red-600"
@@ -601,6 +581,9 @@ export default function EditorNav({
601
581
  <RenameDialog
602
582
  packageId={pkg?.package_id ?? ""}
603
583
  currentName={pkg?.unscoped_name ?? ""}
584
+ onRename={() => {
585
+ qc.invalidateQueries({ queryKey: ["package", pkg?.package_id] })
586
+ }}
604
587
  />
605
588
  <DeleteDialog
606
589
  packageId={pkg?.package_id ?? ""}
@@ -3,7 +3,7 @@ import { Badge } from "@/components/ui/badge"
3
3
  import { Button } from "@/components/ui/button"
4
4
  import { GitBranch, Rocket, Github } from "lucide-react"
5
5
  import { cn } from "@/lib/utils"
6
- import { PrefetchPageLink } from "../PrefetchPageLink"
6
+ import { Link } from "wouter"
7
7
  import { formatTimeAgo } from "@/lib/utils/formatTimeAgo"
8
8
  import { getBuildStatus, StatusIcon } from "."
9
9
  import { Package } from "fake-snippets-api/lib/db/schema"
@@ -116,12 +116,12 @@ export const ConnectedPackageCard = ({
116
116
  >
117
117
  <div className="flex items-start justify-between mb-4">
118
118
  <div className="flex items-center gap-3">
119
- <a
120
- href="#"
119
+ <Link
120
+ href={`/${pkg.owner_github_username}/${pkg.unscoped_name}`}
121
121
  className="text-lg font-semibold text-gray-900 hover:text-blue-600 transition-colors"
122
122
  >
123
123
  {pkg.unscoped_name}
124
- </a>
124
+ </Link>
125
125
  </div>
126
126
 
127
127
  <div className="flex items-center justify-center gap-2">
@@ -174,25 +174,25 @@ export const ConnectedPackageCard = ({
174
174
  </div>
175
175
 
176
176
  <div className="flex gap-2 w-full mt-auto">
177
- <PrefetchPageLink className="w-full" href={`/${pkg.name}/releases`}>
177
+ <Link className="w-full" href={`/${pkg.name}/releases`}>
178
178
  <Button
179
179
  size="sm"
180
180
  className="bg-blue-600 w-full hover:bg-blue-700 text-white px-4 py-2"
181
181
  >
182
182
  View
183
183
  </Button>
184
- </PrefetchPageLink>
184
+ </Link>
185
185
  {latestBuildInfo?.preview_url &&
186
186
  latestBuildInfo?.package_build_id &&
187
187
  status === "success" && (
188
- <PrefetchPageLink
188
+ <Link
189
189
  className="w-full"
190
190
  href={`/${pkg.name}/releases/${latestBuildInfo.package_release_id}/preview`}
191
191
  >
192
192
  <Button size="sm" variant="outline" className="px-4 py-2 w-full">
193
193
  Preview
194
194
  </Button>
195
- </PrefetchPageLink>
195
+ </Link>
196
196
  )}
197
197
  </div>
198
198
  </Card>
@@ -41,6 +41,7 @@ export const ConnectedRepoOverview = ({
41
41
  const [openSections, setOpenSections] = useState({
42
42
  transpilation: false,
43
43
  circuitJson: false,
44
+ imageGeneration: false,
44
45
  })
45
46
 
46
47
  // Gracefully handle when there is no build yet
@@ -120,15 +121,25 @@ export const ConnectedRepoOverview = ({
120
121
  1000,
121
122
  )
122
123
  : 0
124
+ const imageGenerationDuration = packageBuild?.image_generation_started_at
125
+ ? Math.floor(
126
+ (new Date(
127
+ packageBuild.image_generation_completed_at || new Date(),
128
+ ).getTime() -
129
+ new Date(packageBuild.image_generation_started_at).getTime()) /
130
+ 1000,
131
+ )
132
+ : 0
123
133
 
124
134
  if (
125
135
  !packageBuild?.transpilation_started_at &&
126
- !packageBuild?.circuit_json_build_started_at
136
+ !packageBuild?.circuit_json_build_started_at &&
137
+ !packageBuild?.image_generation_started_at
127
138
  ) {
128
139
  return null
129
140
  }
130
141
 
131
- return transpilationDuration + circuitJsonDuration
142
+ return transpilationDuration + circuitJsonDuration + imageGenerationDuration
132
143
  })()
133
144
  const toggleSection = (section: keyof typeof openSections) => {
134
145
  setOpenSections((prev) => ({ ...prev, [section]: !prev[section] }))
@@ -482,6 +493,95 @@ export const ConnectedRepoOverview = ({
482
493
  </div>
483
494
  </CollapsibleContent>
484
495
  </Collapsible>
496
+
497
+ <Collapsible
498
+ open={openSections.imageGeneration}
499
+ onOpenChange={() => toggleSection("imageGeneration")}
500
+ >
501
+ <CollapsibleTrigger asChild>
502
+ <div className="flex items-center justify-between p-4 bg-white border border-gray-200 rounded-lg cursor-pointer hover:bg-gray-50">
503
+ <div className="flex items-center gap-3">
504
+ <ChevronRight
505
+ className={`w-4 h-4 transition-transform ${openSections.imageGeneration ? "rotate-90" : ""}`}
506
+ />
507
+ {packageBuild.image_generation_error ? (
508
+ <AlertCircle className="w-5 h-5 text-red-500" />
509
+ ) : packageBuild.image_generation_completed_at ? (
510
+ <CheckCircle className="w-5 h-5 text-green-500" />
511
+ ) : packageBuild.image_generation_in_progress ? (
512
+ <Loader2 className="w-5 h-5 text-blue-500 animate-spin" />
513
+ ) : (
514
+ <Clock className="w-5 h-5 text-gray-400" />
515
+ )}
516
+ <span className="font-medium">Image Generation</span>
517
+ </div>
518
+ <div className="flex items-center gap-2">
519
+ {getStepDuration(
520
+ packageBuild.image_generation_started_at,
521
+ packageBuild.image_generation_completed_at,
522
+ ) && (
523
+ <span className="text-sm text-gray-600">
524
+ {getStepDuration(
525
+ packageBuild.image_generation_started_at,
526
+ packageBuild.image_generation_completed_at,
527
+ )}
528
+ </span>
529
+ )}
530
+ <Badge
531
+ variant={
532
+ getStepStatus(
533
+ packageBuild.image_generation_error,
534
+ packageBuild.image_generation_completed_at,
535
+ packageBuild.image_generation_in_progress,
536
+ ) === "success"
537
+ ? "default"
538
+ : getStepStatus(
539
+ packageBuild.image_generation_error,
540
+ packageBuild.image_generation_completed_at,
541
+ packageBuild.image_generation_in_progress,
542
+ ) === "error"
543
+ ? "destructive"
544
+ : "secondary"
545
+ }
546
+ className="text-xs"
547
+ >
548
+ {packageBuild.image_generation_error
549
+ ? "Failed"
550
+ : packageBuild.image_generation_completed_at
551
+ ? "Completed"
552
+ : packageBuild.image_generation_in_progress
553
+ ? "Running"
554
+ : "Queued"}
555
+ </Badge>
556
+ </div>
557
+ </div>
558
+ </CollapsibleTrigger>
559
+ <CollapsibleContent>
560
+ <div className="bg-white border-x border-b border-gray-200 rounded-b-lg p-4">
561
+ <div className="font-mono text-xs space-y-1">
562
+ {packageBuild.image_generation_error ? (
563
+ <div className="text-red-600 whitespace-pre-wrap">
564
+ {packageBuild.image_generation_error}
565
+ </div>
566
+ ) : packageBuild.image_generation_logs &&
567
+ packageBuild.image_generation_logs.length > 0 ? (
568
+ packageBuild.image_generation_logs.map(
569
+ (log: any, i: number) => (
570
+ <div
571
+ key={i}
572
+ className="text-gray-600 whitespace-pre-wrap"
573
+ >
574
+ {log.msg || log.message || JSON.stringify(log)}
575
+ </div>
576
+ ),
577
+ )
578
+ ) : (
579
+ <div className="text-gray-500">No logs available</div>
580
+ )}
581
+ </div>
582
+ </div>
583
+ </CollapsibleContent>
584
+ </Collapsible>
485
585
  </div>
486
586
  </div>
487
587
  )