@tscircuit/fake-snippets 0.0.86 → 0.0.88

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.
@@ -5,14 +5,16 @@ import { aiReviewSchema } from "fake-snippets-api/lib/db/schema"
5
5
  export default withRouteSpec({
6
6
  methods: ["POST"],
7
7
  auth: "session",
8
- queryParams: z.object({
9
- package_release_id: z.string().optional(),
10
- }),
8
+ jsonBody: z
9
+ .object({
10
+ package_release_id: z.string().optional(),
11
+ })
12
+ .optional(),
11
13
  jsonResponse: z.object({
12
14
  ai_review: aiReviewSchema,
13
15
  }),
14
16
  })(async (req, ctx) => {
15
- const { package_release_id } = req.query
17
+ const { package_release_id } = req.jsonBody ?? {}
16
18
 
17
19
  if (package_release_id) {
18
20
  const release = ctx.db.getPackageReleaseById(package_release_id)
@@ -28,7 +28,7 @@ export default withRouteSpec({
28
28
  package_name_with_version,
29
29
  } = req.jsonBody
30
30
 
31
- if (package_name_with_version && !version && !package_id) {
31
+ if (package_name_with_version && !package_id) {
32
32
  const [packageName, parsedVersion] = package_name_with_version.split("@")
33
33
  const pkg = ctx.db.packages.find((p) => p.name === packageName)
34
34
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/fake-snippets",
3
- "version": "0.0.86",
3
+ "version": "0.0.88",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -32,7 +32,7 @@
32
32
  "generate-images": "bun run scripts/generate-image-sizes.ts",
33
33
  "generate-sitemap": "bun run scripts/generate-sitemap.ts"
34
34
  },
35
- "dependencies": {
35
+ "devDependencies": {
36
36
  "@babel/preset-react": "^7.25.9",
37
37
  "@babel/preset-typescript": "^7.26.0",
38
38
  "@codemirror/autocomplete": "^6.18.1",
@@ -136,9 +136,7 @@
136
136
  "use-mouse-matrix-transform": "^1.3.0",
137
137
  "vaul": "^0.9.9",
138
138
  "vite-plugin-vercel": "^9.0.4",
139
- "wouter": "^3.3.5"
140
- },
141
- "devDependencies": {
139
+ "wouter": "^3.3.5",
142
140
  "@anthropic-ai/sdk": "^0.27.3",
143
141
  "@babel/standalone": "^7.26.2",
144
142
  "@biomejs/biome": "^1.9.2",
@@ -147,7 +145,7 @@
147
145
  "@tscircuit/core": "^0.0.433",
148
146
  "@tscircuit/eval": "^0.0.227",
149
147
  "@tscircuit/prompt-benchmarks": "^0.0.28",
150
- "@tscircuit/runframe": "^0.0.578",
148
+ "@tscircuit/runframe": "^0.0.582",
151
149
  "@types/babel__standalone": "^7.1.7",
152
150
  "@types/bun": "^1.1.10",
153
151
  "@types/country-list": "^2.1.4",
@@ -168,10 +168,14 @@ export default function Header() {
168
168
  Docs
169
169
  </HeaderButton>
170
170
  </li>
171
+ <li>
172
+ <HeaderButton className="w-full justify-start" href="/search">
173
+ Search
174
+ </HeaderButton>
175
+ </li>
171
176
  </ul>
172
177
  </nav>
173
178
  <div className="flex flex-col gap-4">
174
- <SearchComponent />
175
179
  <HeaderDropdown />
176
180
  <HeaderLogin />
177
181
  </div>
@@ -43,7 +43,7 @@ export const HeaderLogin = () => {
43
43
  </AvatarFallback>
44
44
  </Avatar>
45
45
  </DropdownMenuTrigger>
46
- <DropdownMenuContent>
46
+ <DropdownMenuContent className="ml-1 md:ml-0 md:mr-1">
47
47
  <DropdownMenuItem asChild className="text-gray-500 text-xs" disabled>
48
48
  <div>
49
49
  AI Usage $
@@ -1,18 +1,34 @@
1
1
  import { Link } from "wouter"
2
2
  import { useEffect } from "react"
3
3
  import { useInView } from "react-intersection-observer"
4
+ import { useQueryClient } from "react-query"
5
+ import { useAxios } from "@/hooks/use-axios"
4
6
 
5
- export /**
6
- * PrefetchPageLink component that loads page components when links become visible.
7
- * Routes are automatically mapped to their corresponding page components under @/pages.
8
- * The href path is used to determine which page to load:
9
- *
10
- * Example:
11
- * - href="/editor" -> loads "@/pages/editor.tsx"
12
- * - href="/" -> loads "@/pages/landing.tsx"
13
- * - href="/my-orders" -> loads "@/pages/my-orders.tsx"
14
- */
15
- const PrefetchPageLink = ({
7
+ // Whitelist of known pages that can be safely prefetched
8
+ const PREFETCHABLE_PAGES = new Set([
9
+ "landing",
10
+ "editor",
11
+ "search",
12
+ "trending",
13
+ "dashboard",
14
+ "latest",
15
+ "settings",
16
+ "quickstart",
17
+ ])
18
+
19
+ // Helper to check if a path is a package path (e.g. /username/package-name)
20
+ const isPackagePath = (path: string) => {
21
+ const parts = path.split("/").filter(Boolean)
22
+ return parts.length === 2
23
+ }
24
+
25
+ // Helper to check if a path is a user profile path
26
+ const isUserProfilePath = (path: string) => {
27
+ const parts = path.split("/").filter(Boolean)
28
+ return parts.length === 1 && !PREFETCHABLE_PAGES.has(parts[0])
29
+ }
30
+
31
+ export const PrefetchPageLink = ({
16
32
  href,
17
33
  children,
18
34
  className,
@@ -27,13 +43,48 @@ const PrefetchPageLink = ({
27
43
  triggerOnce: true,
28
44
  threshold: 0,
29
45
  })
46
+ const queryClient = useQueryClient()
47
+ const axios = useAxios()
30
48
 
31
49
  useEffect(() => {
32
- if (inView) {
33
- const link = href === "/" ? "landing" : href.slice(1)
34
- if (!link) return
35
- const pageName = link.split("?")[0]
50
+ if (!inView) return
51
+
52
+ const path = href === "/" ? "landing" : href.slice(1)
53
+ if (!path) return
54
+
55
+ // Handle user profile paths
56
+ if (isUserProfilePath(path)) {
57
+ const username = path.split("/")[0]
58
+ // Prefetch user profile data
59
+ queryClient.prefetchQuery(["account", username], async () => {
60
+ const { data } = await axios.post("/accounts/get", {
61
+ github_username: username,
62
+ })
63
+ return data.account
64
+ })
65
+ return
66
+ }
67
+
68
+ // Handle package paths
69
+ if (isPackagePath(path)) {
70
+ const [owner, name] = path.split("/")
71
+ const packageName = name.split("#")[0]
72
+ // Prefetch package data
73
+ queryClient.prefetchQuery(
74
+ ["package", `${owner}/${packageName}`],
75
+ async () => {
76
+ const { data } = await axios.get("/packages/get", {
77
+ params: { name: `${owner}/${packageName}` },
78
+ })
79
+ return data.package
80
+ },
81
+ )
82
+ return
83
+ }
36
84
 
85
+ // Handle regular pages
86
+ const pageName = path.split("?")[0]
87
+ if (PREFETCHABLE_PAGES.has(pageName)) {
37
88
  import(`@/pages/${pageName}.tsx`).catch((error) => {
38
89
  console.error(`Failed to prefetch page module ${pageName}:`, error)
39
90
  })
@@ -19,11 +19,13 @@ const LinkWithNewTabHandling = ({
19
19
  shouldOpenInNewTab,
20
20
  href,
21
21
  className,
22
+ onClick,
22
23
  children,
23
24
  }: {
24
25
  shouldOpenInNewTab: boolean
25
26
  href: string
26
27
  className?: string
28
+ onClick?: () => void
27
29
  children: React.ReactNode
28
30
  }) => {
29
31
  if (shouldOpenInNewTab) {
@@ -33,13 +35,14 @@ const LinkWithNewTabHandling = ({
33
35
  target="_blank"
34
36
  rel="noopener noreferrer"
35
37
  className={className}
38
+ onClick={onClick}
36
39
  >
37
40
  {children}
38
41
  </a>
39
42
  )
40
43
  }
41
44
  return (
42
- <PrefetchPageLink className={className} href={href}>
45
+ <PrefetchPageLink onClick={onClick} className={className} href={href}>
43
46
  {children}
44
47
  </PrefetchPageLink>
45
48
  )
@@ -171,6 +174,9 @@ const SearchComponent: React.FC<SearchComponentProps> = ({
171
174
  setLocation(href)
172
175
  }
173
176
  setShowResults(false)
177
+ if (closeOnClick) {
178
+ closeOnClick()
179
+ }
174
180
  }
175
181
  }
176
182
  }}
@@ -209,6 +215,10 @@ const SearchComponent: React.FC<SearchComponentProps> = ({
209
215
  }
210
216
  shouldOpenInNewTab={shouldOpenInNewTab}
211
217
  className="flex"
218
+ onClick={() => {
219
+ setShowResults(false)
220
+ if (closeOnClick) closeOnClick()
221
+ }}
212
222
  >
213
223
  <div className="w-12 h-12 overflow-hidden mr-2 flex-shrink-0 rounded-sm bg-gray-50 border flex items-center justify-center">
214
224
  <img
@@ -213,11 +213,11 @@ export default function ImportantFilesView({
213
213
  return (
214
214
  <div className="mt-4 border border-gray-200 dark:border-[#30363d] rounded-md overflow-hidden">
215
215
  <div className="flex items-center pl-2 pr-4 py-2 bg-gray-100 dark:bg-[#161b22] border-b border-gray-200 dark:border-[#30363d]">
216
- <div className="flex items-center space-x-2 overflow-x-auto no-scrollbar">
216
+ <div className="flex items-center space-x-2 overflow-x-auto no-scrollbar flex-1 min-w-0">
217
217
  {/* AI Description Tab */}
218
218
  {hasAiContent && (
219
219
  <button
220
- className={`flex items-center px-3 py-1.5 rounded-md text-xs ${
220
+ className={`flex items-center px-3 py-1.5 rounded-md text-xs flex-shrink-0 whitespace-nowrap ${
221
221
  activeTab === "ai"
222
222
  ? "bg-gray-200 dark:bg-[#30363d] font-medium"
223
223
  : "text-gray-500 dark:text-[#8b949e] hover:bg-gray-200 dark:hover:bg-[#30363d]"
@@ -234,7 +234,7 @@ export default function ImportantFilesView({
234
234
 
235
235
  {/* AI Review Tab */}
236
236
  <button
237
- className={`flex items-center px-3 py-1.5 rounded-md text-xs ${
237
+ className={`flex items-center px-3 py-1.5 rounded-md text-xs flex-shrink-0 whitespace-nowrap ${
238
238
  activeTab === "ai-review"
239
239
  ? "bg-gray-200 dark:bg-[#30363d] font-medium"
240
240
  : "text-gray-500 dark:text-[#8b949e] hover:bg-gray-200 dark:hover:bg-[#30363d]"
@@ -252,7 +252,7 @@ export default function ImportantFilesView({
252
252
  {importantFiles.map((file) => (
253
253
  <button
254
254
  key={file.package_file_id}
255
- className={`flex items-center px-3 py-1.5 rounded-md text-xs ${
255
+ className={`flex items-center px-3 py-1.5 rounded-md text-xs flex-shrink-0 whitespace-nowrap ${
256
256
  activeTab === "file" && activeFilePath === file.file_path
257
257
  ? "bg-gray-200 dark:bg-[#30363d] font-medium"
258
258
  : "text-gray-500 dark:text-[#8b949e] hover:bg-gray-200 dark:hover:bg-[#30363d]"
@@ -295,7 +295,9 @@ export default function ImportantFilesView({
295
295
  renderAiContent()
296
296
  ) : activeTab === "ai-review" ? (
297
297
  renderAiReviewContent()
298
- ) : activeFilePath && activeFilePath.endsWith(".md") ? (
298
+ ) : activeFilePath &&
299
+ (activeFilePath.endsWith(".md") ||
300
+ activeFilePath?.toLowerCase().endsWith("readme")) ? (
299
301
  <MarkdownViewer markdownContent={activeFileContent} />
300
302
  ) : activeFilePath &&
301
303
  (activeFilePath.endsWith(".js") ||
@@ -87,7 +87,7 @@ export default function MainContentHeader({
87
87
  </Button>
88
88
  </DropdownMenuTrigger>
89
89
  <DropdownMenuContent align="end" className="w-72">
90
- <DropdownMenuItem asChild>
90
+ <DropdownMenuItem disabled={!Boolean(packageInfo)} asChild>
91
91
  <a
92
92
  href={`/editor?package_id=${packageInfo?.package_id}`}
93
93
  className="cursor-pointer p-2 py-4"
@@ -12,7 +12,10 @@ import ThreeDView from "./tab-views/3d-view"
12
12
  import PCBView from "./tab-views/pcb-view"
13
13
  import SchematicView from "./tab-views/schematic-view"
14
14
  import BOMView from "./tab-views/bom-view"
15
- import { isPackageFileImportant } from "../utils/is-package-file-important"
15
+ import {
16
+ isPackageFileImportant,
17
+ scorePackageFileImportance,
18
+ } from "../utils/is-package-file-important"
16
19
  import Header from "@/components/Header"
17
20
  import Footer from "@/components/Footer"
18
21
  import PackageHeader from "./package-header"
@@ -90,9 +93,16 @@ export default function RepoPageContent({
90
93
  const importantFiles = useMemo(() => {
91
94
  if (!packageFiles || !importantFilePaths) return []
92
95
 
93
- return packageFiles.filter((file) =>
94
- importantFilePaths.some((path) => file.file_path.endsWith(path)),
95
- )
96
+ return packageFiles
97
+ .filter((file) =>
98
+ importantFilePaths.some((path) => file.file_path.endsWith(path)),
99
+ )
100
+ .sort((a, b) => {
101
+ const aImportance = scorePackageFileImportance(a.file_path)
102
+ const bImportance = scorePackageFileImportance(b.file_path)
103
+ return aImportance - bImportance
104
+ })
105
+ .reverse()
96
106
  }, [packageFiles, importantFilePaths])
97
107
 
98
108
  // Generate package name with version for file lookups
@@ -1,5 +1,6 @@
1
1
  const importanceMap = {
2
2
  "readme.md": 200,
3
+ readme: 200,
3
4
  license: 100,
4
5
  "license.md": 100,
5
6
  "index.ts": 90,
@@ -7,7 +8,23 @@ const importanceMap = {
7
8
  "circuit.tsx": 90,
8
9
  }
9
10
 
10
- export const scorePackageFileImportance = (filePath: string) => {
11
+ /**
12
+ * Determines if a file is considered "important" for display in the
13
+ * `ImportantFilesView` component.
14
+ *
15
+ * A file is deemed important if it resides in the root directory of the package
16
+ * and has a positive importance score. Nested paths are not considered important.
17
+ */
18
+ export const isPackageFileImportant = (filePath: string): boolean => {
19
+ const normalized = filePath.replace(/^\.\/?/, "").toLowerCase()
20
+ if (normalized.split("/").length > 1) {
21
+ return false
22
+ }
23
+ return scorePackageFileImportance(filePath) > 0
24
+ }
25
+
26
+ // Kept for backward compatibility with older imports
27
+ export const scorePackageFileImportance = (filePath: string): number => {
11
28
  const lowerCaseFilePath = filePath.toLowerCase()
12
29
  for (const [key, value] of Object.entries(importanceMap)) {
13
30
  if (lowerCaseFilePath.endsWith(key)) {
@@ -16,7 +33,3 @@ export const scorePackageFileImportance = (filePath: string) => {
16
33
  }
17
34
  return 0
18
35
  }
19
-
20
- export const isPackageFileImportant = (filePath: string) => {
21
- return scorePackageFileImportance(filePath) > 0
22
- }
@@ -12,8 +12,8 @@ export const useRequestAiReviewMutation = ({
12
12
 
13
13
  return useMutation(
14
14
  async ({ package_release_id }: { package_release_id: string }) => {
15
- await axios.post("/ai_reviews/create", null, {
16
- params: { package_release_id },
15
+ await axios.post("/ai_reviews/create", {
16
+ package_release_id,
17
17
  })
18
18
  const { data } = await axios.post(
19
19
  "/package_releases/get",
@@ -7,46 +7,40 @@ import type { QueryClient } from "react-query"
7
7
  export function populateQueryCacheWithSSRData(queryClient: QueryClient) {
8
8
  if (typeof window === "undefined") return
9
9
 
10
- const ssrPackage = (window as any).SSR_PACKAGE
11
- const ssrPackageRelease = (window as any).SSR_PACKAGE_RELEASE
12
- const ssrPackageFiles = (window as any).SSR_PACKAGE_FILES
10
+ const windowAny = window as any
11
+ const ssrPackage = windowAny.SSR_PACKAGE
12
+ const ssrPackageRelease = windowAny.SSR_PACKAGE_RELEASE
13
+ const ssrPackageFiles = windowAny.SSR_PACKAGE_FILES
13
14
 
14
- if (ssrPackage) {
15
- // Cache package data with all possible query keys
16
- queryClient.setQueryData(["package", ssrPackage.package_id], ssrPackage)
17
- queryClient.setQueryData(["package", ssrPackage.name], ssrPackage)
18
- queryClient.setQueryData(["packages", ssrPackage.package_id], ssrPackage)
19
- }
15
+ if (!ssrPackage || !ssrPackageRelease) return
20
16
 
21
- if (ssrPackageRelease && ssrPackage) {
22
- // Cache package release with various query patterns
23
- queryClient.setQueryData(
24
- [
25
- "packageRelease",
26
- { package_id: ssrPackage.package_id, is_latest: true },
27
- ],
28
- ssrPackageRelease,
29
- )
17
+ queryClient.setQueryData(["package", ssrPackage.name], ssrPackage)
18
+ queryClient.setQueryData(["packages", ssrPackage.package_id], ssrPackage)
19
+
20
+ queryClient.setQueryData(
21
+ [
22
+ "packageRelease",
23
+ {
24
+ is_latest: true,
25
+ package_name: ssrPackage.name,
26
+ include_ai_review: true,
27
+ },
28
+ ],
29
+ ssrPackageRelease,
30
+ )
31
+
32
+ queryClient.setQueryData(
33
+ [
34
+ "packageRelease",
35
+ { package_release_id: ssrPackageRelease.package_release_id },
36
+ ],
37
+ ssrPackageRelease,
38
+ )
39
+
40
+ if (ssrPackageFiles && ssrPackageRelease.package_release_id) {
30
41
  queryClient.setQueryData(
31
- ["packageRelease", { package_name: ssrPackage.name, is_latest: true }],
32
- ssrPackageRelease,
42
+ ["packageFiles", ssrPackageRelease.package_release_id],
43
+ ssrPackageFiles,
33
44
  )
34
- if (ssrPackageRelease.package_release_id) {
35
- queryClient.setQueryData(
36
- [
37
- "packageRelease",
38
- { package_release_id: ssrPackageRelease.package_release_id },
39
- ],
40
- ssrPackageRelease,
41
- )
42
- }
43
-
44
- // Cache package files if available
45
- if (ssrPackageFiles && ssrPackageRelease.package_release_id) {
46
- queryClient.setQueryData(
47
- ["packageFiles", ssrPackageRelease.package_release_id],
48
- ssrPackageFiles,
49
- )
50
- }
51
45
  }
52
46
  }
@@ -55,7 +55,7 @@ export const UserProfilePage = () => {
55
55
  )
56
56
 
57
57
  // use the username stored in the database so the correct case is displayed
58
- const githubUsername = account?.account.github_username || username
58
+ const githubUsername = account?.account?.github_username || username
59
59
  const isCurrentUserProfile = githubUsername === session?.github_username
60
60
 
61
61
  const { Dialog: DeleteDialog, openDialog: openDeleteDialog } =