@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
@@ -0,0 +1,50 @@
1
+ export function normalizeSvgForSquareTile(svg: string): string {
2
+ try {
3
+ const openTagMatch = svg.match(/<svg[^>]*>/i)
4
+ if (!openTagMatch) return svg
5
+
6
+ const openTag = openTagMatch[0]
7
+
8
+ const hasViewBox = /viewBox="[^"]+"/i.test(openTag)
9
+ const widthMatch = openTag.match(/\swidth="([0-9.]+)"/i)
10
+ const heightMatch = openTag.match(/\sheight="([0-9.]+)"/i)
11
+
12
+ let newOpenTag = openTag
13
+
14
+ // Remove explicit width/height so CSS can control sizing
15
+ newOpenTag = newOpenTag.replace(/\swidth="[^"]*"/i, "")
16
+ newOpenTag = newOpenTag.replace(/\sheight="[^"]*"/i, "")
17
+
18
+ // Ensure viewBox is present for proper scaling
19
+ if (!hasViewBox && widthMatch && heightMatch) {
20
+ const w = widthMatch[1]
21
+ const h = heightMatch[1]
22
+ newOpenTag = newOpenTag.replace(/<svg/i, `<svg viewBox="0 0 ${w} ${h}"`)
23
+ }
24
+
25
+ // Force preserveAspectRatio to fit within square without distortion
26
+ if (/preserveAspectRatio="[^"]+"/i.test(newOpenTag)) {
27
+ newOpenTag = newOpenTag.replace(
28
+ /preserveAspectRatio="[^"]+"/i,
29
+ 'preserveAspectRatio="xMidYMid meet"',
30
+ )
31
+ } else {
32
+ newOpenTag = newOpenTag.replace(
33
+ /<svg(\s|>)/i,
34
+ (_m, p1) => `<svg preserveAspectRatio="xMidYMid meet"${p1}`,
35
+ )
36
+ }
37
+
38
+ return svg.replace(openTag, newOpenTag)
39
+ } catch {
40
+ return svg
41
+ }
42
+ }
43
+
44
+ export function svgToDataUrl(svg: string): string {
45
+ try {
46
+ return `data:image/svg+xml,${encodeURIComponent(svg)}`
47
+ } catch {
48
+ return `data:image/svg+xml,${svg}`
49
+ }
50
+ }
@@ -1,14 +1,16 @@
1
1
  import posthog from "posthog-js"
2
2
 
3
- if (
4
- !window.location.hostname.includes("localhost") &&
5
- !window.location.hostname.includes("127.0.0.1")
6
- ) {
7
- if (!posthog.__loaded) {
8
- posthog.init("phc_htd8AQjSfVEsFCLQMAiUooG4Q0DKBCjqYuQglc9V3Wo", {
9
- api_host: "https://postpig.tscircuit.com",
10
- person_profiles: "always",
11
- })
3
+ if (typeof window !== "undefined") {
4
+ if (
5
+ !window.location.hostname.includes("localhost") &&
6
+ !window.location.hostname.includes("127.0.0.1")
7
+ ) {
8
+ if (!posthog.__loaded) {
9
+ posthog.init("phc_htd8AQjSfVEsFCLQMAiUooG4Q0DKBCjqYuQglc9V3Wo", {
10
+ api_host: "https://postpig.tscircuit.com",
11
+ person_profiles: "always",
12
+ })
13
+ }
12
14
  }
13
15
  }
14
16
 
@@ -0,0 +1,148 @@
1
+ import type { QueryKey } from "react-query"
2
+ import { posthog } from "./posthog"
3
+
4
+ const TARGET_HOSTNAMES = new Set(["api.tscircuit.com"])
5
+
6
+ type FailureContext = {
7
+ operationType: "query" | "mutation"
8
+ queryKey?: QueryKey
9
+ mutationKey?: unknown
10
+ }
11
+
12
+ type RedaxiosConfig = {
13
+ url?: string
14
+ baseURL?: string
15
+ method?: string
16
+ }
17
+
18
+ type ResponseLike = {
19
+ url?: string
20
+ status?: number
21
+ statusText?: string
22
+ config?: RedaxiosConfig
23
+ }
24
+
25
+ const isPosthogLoaded = () => Boolean((posthog as any)?.__loaded)
26
+
27
+ const toUpperCaseMethod = (method?: string) => method?.toUpperCase() ?? "GET"
28
+
29
+ const resolveAbsoluteUrl = (url?: string, baseURL?: string) => {
30
+ if (!url) return undefined
31
+
32
+ try {
33
+ if (baseURL) {
34
+ return new URL(url, baseURL).toString()
35
+ }
36
+
37
+ if (typeof window !== "undefined") {
38
+ return new URL(url, window.location.origin).toString()
39
+ }
40
+
41
+ return new URL(url).toString()
42
+ } catch (error) {
43
+ console.warn("Failed to resolve API failure URL", error)
44
+ return undefined
45
+ }
46
+ }
47
+
48
+ const shouldTrackUrl = (resolvedUrl?: string) => {
49
+ if (!resolvedUrl) return false
50
+
51
+ try {
52
+ const { hostname } = new URL(resolvedUrl)
53
+ return TARGET_HOSTNAMES.has(hostname)
54
+ } catch (error) {
55
+ console.warn("Failed to parse URL for API failure tracking", error)
56
+ return false
57
+ }
58
+ }
59
+
60
+ const serializeKey = (key?: unknown) => {
61
+ if (!key) return undefined
62
+
63
+ try {
64
+ return JSON.stringify(key)
65
+ } catch {
66
+ return String(key)
67
+ }
68
+ }
69
+
70
+ const extractFromResponse = (error: unknown): ResponseLike | null => {
71
+ if (!error || typeof error !== "object") return null
72
+
73
+ const maybeResponse = error as Partial<ResponseLike>
74
+ if (typeof maybeResponse.status !== "number" || !("url" in maybeResponse)) {
75
+ return null
76
+ }
77
+
78
+ return {
79
+ url: maybeResponse.url,
80
+ status: maybeResponse.status,
81
+ statusText: maybeResponse.statusText,
82
+ config: maybeResponse.config,
83
+ }
84
+ }
85
+
86
+ const extractFromAxiosError = (error: unknown): ResponseLike | null => {
87
+ if (!error || typeof error !== "object") return null
88
+
89
+ const maybeAxiosError = error as {
90
+ response?: ResponseLike
91
+ config?: RedaxiosConfig
92
+ message?: string
93
+ }
94
+
95
+ if (!maybeAxiosError.response && !maybeAxiosError.config) {
96
+ return null
97
+ }
98
+
99
+ return {
100
+ url: maybeAxiosError.response?.url,
101
+ status: maybeAxiosError.response?.status,
102
+ statusText: maybeAxiosError.response?.statusText,
103
+ config: maybeAxiosError.response?.config ?? maybeAxiosError.config,
104
+ }
105
+ }
106
+
107
+ const captureApiFailure = (
108
+ response: ResponseLike,
109
+ error: unknown,
110
+ context: FailureContext,
111
+ ) => {
112
+ if (!isPosthogLoaded()) return
113
+
114
+ const resolvedUrl =
115
+ resolveAbsoluteUrl(response.url, response.config?.baseURL) ??
116
+ resolveAbsoluteUrl(response.config?.url, response.config?.baseURL)
117
+ if (!shouldTrackUrl(resolvedUrl)) return
118
+
119
+ const errorMessage =
120
+ error instanceof Error
121
+ ? error.message
122
+ : typeof error === "string"
123
+ ? error
124
+ : undefined
125
+
126
+ posthog.capture("api_request_failed", {
127
+ url: resolvedUrl,
128
+ method: toUpperCaseMethod(response.config?.method),
129
+ status: response.status,
130
+ statusText: response.statusText,
131
+ errorMessage,
132
+ operationType: context.operationType,
133
+ queryKey: serializeKey(context.queryKey),
134
+ mutationKey: serializeKey(context.mutationKey),
135
+ environment:
136
+ typeof window !== "undefined" ? window.location.hostname : undefined,
137
+ })
138
+ }
139
+
140
+ export const trackReactQueryApiFailure = (
141
+ error: unknown,
142
+ context: FailureContext,
143
+ ) => {
144
+ const response = extractFromResponse(error) ?? extractFromAxiosError(error)
145
+ if (!response) return
146
+
147
+ captureApiFailure(response, error, context)
148
+ }
@@ -0,0 +1,14 @@
1
+ import * as Sentry from "@sentry/react"
2
+
3
+ if (
4
+ typeof window !== "undefined" &&
5
+ import.meta.env.VITE_SENTRY_DSN &&
6
+ !window.location.hostname.includes("localhost") &&
7
+ !window.location.hostname.includes("127.0.0.1")
8
+ ) {
9
+ Sentry.init({
10
+ dsn: import.meta.env.VITE_SENTRY_DSN,
11
+ })
12
+ }
13
+
14
+ export { Sentry }
@@ -7,15 +7,11 @@ export default () => (
7
7
  resistance="1k"
8
8
  footprint="0402"
9
9
  name="R1"
10
- schX={3}
11
- pcbX={3}
12
10
  />
13
11
  <capacitor
14
12
  capacitance="1000pF"
15
13
  footprint="0402"
16
14
  name="C1"
17
- schX={-3}
18
- pcbX={-3}
19
15
  />
20
16
  <trace from=".R1 > .pin1" to=".C1 > .pin1" />
21
17
  </board>
@@ -8,6 +8,7 @@ import ts from "typescript"
8
8
 
9
9
  const TS_LIB_VERSION = "5.6.3"
10
10
  const CACHE_PREFIX = `ts-lib-${TS_LIB_VERSION}-`
11
+ const CACHE_TTL = 7 * 24 * 60 * 60 * 1000 // 7 days
11
12
 
12
13
  export async function loadDefaultLibMap(): Promise<Map<string, string>> {
13
14
  const fsMap = new Map<string, string>()
@@ -18,9 +19,23 @@ export async function loadDefaultLibMap(): Promise<Map<string, string>> {
18
19
  const missing: string[] = []
19
20
 
20
21
  for (const lib of libs) {
21
- const cached = await get(CACHE_PREFIX + lib)
22
- if (cached) {
23
- fsMap.set("/" + lib, decompressFromUTF16(cached as string))
22
+ const cacheKey = CACHE_PREFIX + lib
23
+ const cached = await get(cacheKey)
24
+ if (
25
+ cached &&
26
+ typeof cached === "object" &&
27
+ "compressedFileContent" in cached &&
28
+ "timestamp" in cached
29
+ ) {
30
+ const { compressedFileContent, timestamp } = cached as {
31
+ compressedFileContent: string
32
+ timestamp: number
33
+ }
34
+ if (Date.now() - timestamp < CACHE_TTL) {
35
+ fsMap.set("/" + lib, decompressFromUTF16(compressedFileContent))
36
+ } else {
37
+ missing.push(lib)
38
+ }
24
39
  } else {
25
40
  missing.push(lib)
26
41
  }
@@ -36,12 +51,112 @@ export async function loadDefaultLibMap(): Promise<Map<string, string>> {
36
51
  )
37
52
  for (const [filename, content] of fetched) {
38
53
  fsMap.set(filename, content)
39
- await set(
40
- CACHE_PREFIX + filename.replace(/^\//, ""),
41
- compressToUTF16(content),
42
- )
54
+ const cacheKey = CACHE_PREFIX + filename.replace(/^\//, "")
55
+ const compressed = compressToUTF16(content)
56
+ await set(cacheKey, {
57
+ compressedFileContent: compressed,
58
+ timestamp: Date.now(),
59
+ }).catch(() => {})
43
60
  }
44
61
  }
45
62
 
46
63
  return fsMap
47
64
  }
65
+
66
+ export async function fetchWithPackageCaching(
67
+ input: RequestInfo | URL,
68
+ init?: RequestInit,
69
+ ): Promise<Response> {
70
+ const url = typeof input === "string" ? input : input.toString()
71
+
72
+ // Only cache GET requests for packages
73
+ if (init?.method && init.method !== "GET") {
74
+ return fetch(input, init)
75
+ }
76
+
77
+ // Check if this should be cached
78
+ const shouldCache =
79
+ url.includes("jsdelivr.net") ||
80
+ url.includes("unpkg.com") ||
81
+ url.includes("@types/") ||
82
+ url.includes("@tsci/")
83
+
84
+ if (!shouldCache) {
85
+ return fetch(input, init)
86
+ }
87
+
88
+ const cacheKey = `package-cache-${url}`
89
+
90
+ // Check cache
91
+ const cached = await get(cacheKey).catch(() => null)
92
+ if (
93
+ cached &&
94
+ typeof cached === "object" &&
95
+ "compressedFileContent" in cached &&
96
+ "timestamp" in cached
97
+ ) {
98
+ const { compressedFileContent, timestamp } = cached as {
99
+ compressedFileContent: string
100
+ timestamp: number
101
+ }
102
+ if (Date.now() - timestamp < CACHE_TTL) {
103
+ return new Response(decompressFromUTF16(compressedFileContent), {
104
+ status: 200,
105
+ statusText: "OK",
106
+ })
107
+ }
108
+ }
109
+
110
+ // Handle @tsci packages
111
+ let fetchUrl = url
112
+ if (
113
+ url.includes("@tsci/") &&
114
+ (url.includes("jsdelivr.net") || url.includes("data.jsdelivr.com"))
115
+ ) {
116
+ let packagePath = ""
117
+ if (url.includes("jsdelivr.net")) {
118
+ packagePath = url.replace("https://cdn.jsdelivr.net/npm/@tsci/", "")
119
+ } else if (url.includes("/v1/package/resolve/npm/@tsci/")) {
120
+ const resolveIndex = url.indexOf("/v1/package/resolve/npm/@tsci/")
121
+ packagePath = url.substring(
122
+ resolveIndex + "/v1/package/resolve/npm/@tsci/".length,
123
+ )
124
+ } else if (url.includes("/v1/package/npm/@tsci/")) {
125
+ const npmIndex = url.indexOf("/v1/package/npm/@tsci/")
126
+ packagePath = url.substring(npmIndex + "/v1/package/npm/@tsci/".length)
127
+ }
128
+
129
+ if (packagePath) {
130
+ // Convert dots to slashes in the package name part (like original logic)
131
+ const parts = packagePath.split("/")
132
+ if (parts.length > 0) {
133
+ parts[0] = parts[0].replace(/\./, "/")
134
+ }
135
+ const transformedPackagePath = parts.join("/")
136
+
137
+ const apiUrl = import.meta.env.VITE_SNIPPETS_API_URL ?? "/api"
138
+ const isResolve = url.includes("/resolve/")
139
+ fetchUrl = `${apiUrl}/snippets/download?jsdelivr_resolve=${isResolve}&jsdelivr_path=${encodeURIComponent(
140
+ transformedPackagePath,
141
+ )}`
142
+ }
143
+ }
144
+
145
+ // Fetch and cache
146
+ const response = await fetch(fetchUrl, init)
147
+ if (response.ok) {
148
+ const text = await response.text()
149
+ const compressed = compressToUTF16(text)
150
+ await set(cacheKey, {
151
+ compressedFileContent: compressed,
152
+ timestamp: Date.now(),
153
+ }).catch(() => {})
154
+ return new Response(text, {
155
+ status: response.status,
156
+ statusText: response.statusText,
157
+ headers: response.headers,
158
+ })
159
+ }
160
+
161
+ return response
162
+ }
@@ -5,10 +5,10 @@ export const checkIfManualEditsImported = (
5
5
  file: string = "index.tsx",
6
6
  ) => {
7
7
  if (!files[file]) return false
8
- const targetFile = findTargetFile(
9
- Object.keys(files).map((f) => ({ path: f, content: files[f] })),
10
- null,
11
- )
8
+ const targetFile = findTargetFile({
9
+ files: Object.keys(files).map((f) => ({ path: f, content: files[f] })),
10
+ filePathFromUrl: null,
11
+ })
12
12
  if (targetFile && file !== targetFile.path) {
13
13
  return false
14
14
  }
@@ -1,4 +1,6 @@
1
+ import { isHiddenFile } from "@/components/ViewPackagePage/utils/is-hidden-file"
1
2
  import { PackageFile } from "@/types/package"
3
+ import { isComponentExported } from "./isComponentExported"
2
4
 
3
5
  export const findMainEntrypointFileFromTscircuitConfig = (
4
6
  files: PackageFile[],
@@ -24,37 +26,70 @@ export const findMainEntrypointFileFromTscircuitConfig = (
24
26
  return null
25
27
  }
26
28
 
27
- export const findTargetFile = (
28
- files: PackageFile[],
29
- filePathFromUrl: string | null,
30
- ): PackageFile | null => {
29
+ export const findTargetFile = ({
30
+ files,
31
+ filePathFromUrl,
32
+ fallbackToAnyFile = true,
33
+ }: {
34
+ files: PackageFile[]
35
+ filePathFromUrl: string | null
36
+ fallbackToAnyFile?: boolean
37
+ }): PackageFile | null => {
31
38
  if (files.length === 0) {
32
39
  return null
33
40
  }
34
41
 
35
- let targetFile: PackageFile | null = null
42
+ let targetFile: PackageFile | undefined | null = null
43
+ if (!filePathFromUrl) {
44
+ files = files.filter((x) => !isHiddenFile(x.path))
45
+ }
36
46
 
37
47
  if (filePathFromUrl) {
38
- targetFile = files.find((file) => file.path === filePathFromUrl) ?? null
48
+ const file = files.find((file) => file.path === filePathFromUrl)?.path
49
+ if (
50
+ file &&
51
+ !file.endsWith(".ts") &&
52
+ !file.endsWith(".tsx") &&
53
+ fallbackToAnyFile
54
+ ) {
55
+ targetFile = files.find((file) => file.path === filePathFromUrl) ?? null
56
+ } else {
57
+ const _isComponentExported = isComponentExported(
58
+ files.find((file) => file.path === filePathFromUrl)?.content || "",
59
+ )
60
+ if (_isComponentExported) {
61
+ targetFile = files.find((file) => file.path === filePathFromUrl) ?? null
62
+ }
63
+ }
39
64
  }
40
65
 
41
66
  if (!targetFile) {
42
67
  targetFile = findMainEntrypointFileFromTscircuitConfig(files)
43
68
  }
69
+ if (!targetFile) {
70
+ targetFile =
71
+ files.find(
72
+ (file) => file.path === "index.tsx" || file.path === "index.ts",
73
+ ) ?? null
74
+ }
44
75
 
45
76
  if (!targetFile) {
46
- targetFile = files.find((file) => file.path === "index.tsx") ?? null
77
+ targetFile =
78
+ files.find((file) => file.path.endsWith(".circuit.tsx")) ?? null
47
79
  }
48
80
 
49
81
  if (!targetFile) {
50
- targetFile = files.find((file) => file.path.endsWith(".tsx")) ?? null
82
+ targetFile =
83
+ files.find(
84
+ (file) => file.path === "main.tsx" || file.path === "main.ts",
85
+ ) ?? null
51
86
  }
52
87
 
53
88
  if (!targetFile) {
54
- targetFile = files.find((file) => file.path === "index.ts") ?? null
89
+ targetFile = files.find((file) => file.path.endsWith(".tsx")) ?? null
55
90
  }
56
91
 
57
- if (!targetFile && files[0]) {
92
+ if (!targetFile && files[0] && fallbackToAnyFile) {
58
93
  targetFile = files[0]
59
94
  }
60
95
 
@@ -0,0 +1,10 @@
1
+ export const isComponentExported = (code: string) => {
2
+ return (
3
+ /export function\s+\w+/.test(code) ||
4
+ /export const\s+\w+\s*=/.test(code) ||
5
+ /export default\s+\w+/.test(code) ||
6
+ /export default\s+function\s*(\w*)\s*\(/.test(code) ||
7
+ /export default\s*\(\s*\)\s*=>/.test(code) ||
8
+ /export default\s*\(.*?\)\s*=>/.test(code)
9
+ )
10
+ }
package/src/main.tsx CHANGED
@@ -1,11 +1,12 @@
1
1
  import { StrictMode } from "react"
2
2
  import { createRoot } from "react-dom/client"
3
3
  import App from "./App.tsx"
4
+ import "./lib/sentry"
5
+ import "./index.css"
4
6
 
5
7
  if (typeof window !== "undefined" && !window.__APP_LOADED_AT) {
6
8
  window.__APP_LOADED_AT = Date.now()
7
9
  }
8
- import "./index.css"
9
10
 
10
11
  createRoot(document.getElementById("root")!).render(
11
12
  <StrictMode>
@@ -1,5 +1,3 @@
1
- "use client"
2
-
3
1
  import Header from "@/components/Header"
4
2
  import { useGlobalStore } from "@/hooks/use-global-store"
5
3
  import { useEffect, useState } from "react"