@tscircuit/fake-snippets 0.0.88 → 0.0.89

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 (69) hide show
  1. package/api/generated-index.js +96 -14
  2. package/bun-tests/fake-snippets-api/routes/proxy.test.ts +42 -0
  3. package/bun.lock +187 -206
  4. package/dist/bundle.js +206 -100
  5. package/fake-snippets-api/routes/api/proxy.ts +128 -0
  6. package/package.json +56 -47
  7. package/renovate.json +2 -1
  8. package/src/App.tsx +22 -3
  9. package/src/ContextProviders.tsx +2 -0
  10. package/src/build-watcher.ts +52 -0
  11. package/src/components/CmdKMenu.tsx +533 -197
  12. package/src/components/DownloadButtonAndMenu.tsx +104 -26
  13. package/src/components/FileSidebar.tsx +11 -1
  14. package/src/components/Header2.tsx +7 -2
  15. package/src/components/PackageBuildsPage/LogContent.tsx +25 -22
  16. package/src/components/PackageBuildsPage/PackageBuildDetailsPage.tsx +6 -6
  17. package/src/components/PackageBuildsPage/build-preview-content.tsx +5 -5
  18. package/src/components/PackageBuildsPage/package-build-details-panel.tsx +15 -13
  19. package/src/components/PackageBuildsPage/package-build-header.tsx +17 -16
  20. package/src/components/PackageCard.tsx +66 -16
  21. package/src/components/SearchComponent.tsx +2 -2
  22. package/src/components/SuspenseRunFrame.tsx +14 -2
  23. package/src/components/ViewPackagePage/components/important-files-view.tsx +90 -17
  24. package/src/components/ViewPackagePage/components/main-content-header.tsx +26 -2
  25. package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +2 -2
  26. package/src/components/ViewPackagePage/components/repo-page-content.tsx +35 -30
  27. package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +2 -2
  28. package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +20 -12
  29. package/src/components/ViewPackagePage/components/tab-views/files-view.tsx +0 -7
  30. package/src/components/ViewPackagePage/utils/fuzz-search.ts +121 -0
  31. package/src/components/ViewPackagePage/utils/is-hidden-file.ts +4 -0
  32. package/src/components/dialogs/confirm-delete-package-dialog.tsx +1 -1
  33. package/src/components/dialogs/confirm-discard-changes-dialog.tsx +73 -0
  34. package/src/components/dialogs/edit-package-details-dialog.tsx +2 -2
  35. package/src/components/dialogs/view-ts-files-dialog.tsx +478 -42
  36. package/src/components/package-port/CodeAndPreview.tsx +16 -0
  37. package/src/components/package-port/CodeEditor.tsx +113 -11
  38. package/src/components/package-port/CodeEditorHeader.tsx +39 -4
  39. package/src/components/package-port/EditorNav.tsx +41 -15
  40. package/src/components/package-port/GlobalFindReplace.tsx +681 -0
  41. package/src/components/package-port/QuickOpen.tsx +241 -0
  42. package/src/components/ui/dialog.tsx +1 -1
  43. package/src/components/ui/tree-view.tsx +1 -1
  44. package/src/global.d.ts +3 -0
  45. package/src/hooks/use-ai-review.ts +31 -0
  46. package/src/hooks/use-current-package-release.ts +5 -1
  47. package/src/hooks/use-download-zip.ts +50 -0
  48. package/src/hooks/use-hotkey.ts +116 -0
  49. package/src/hooks/use-package-by-package-id.ts +1 -0
  50. package/src/hooks/use-package-by-package-name.ts +1 -0
  51. package/src/hooks/use-package-files.ts +3 -0
  52. package/src/hooks/use-package-release.ts +5 -1
  53. package/src/hooks/use-package.ts +1 -0
  54. package/src/hooks/use-request-ai-review-mutation.ts +14 -6
  55. package/src/hooks/use-snippet.ts +1 -0
  56. package/src/hooks/useFileManagement.ts +26 -8
  57. package/src/hooks/usePackageFilesLoader.ts +3 -1
  58. package/src/index.css +11 -0
  59. package/src/lib/decodeUrlHashToFsMap.ts +17 -0
  60. package/src/lib/download-fns/download-circuit-png.ts +88 -0
  61. package/src/lib/download-fns/download-png-utils.ts +31 -0
  62. package/src/lib/encodeFsMapToUrlHash.ts +13 -0
  63. package/src/lib/populate-query-cache-with-ssr-data.ts +7 -0
  64. package/src/lib/ts-lib-cache.ts +47 -0
  65. package/src/lib/types.ts +2 -0
  66. package/src/main.tsx +7 -0
  67. package/src/pages/dashboard.tsx +8 -5
  68. package/src/pages/view-package.tsx +15 -7
  69. package/vite.config.ts +100 -1
@@ -184,7 +184,7 @@ const SearchComponent: React.FC<SearchComponentProps> = ({
184
184
  role="searchbox"
185
185
  />
186
186
  {isLoading && (
187
- <div className="absolute top-full w-lg left-0 right-0 mt-1 bg-white shadow-lg rounded-lg border w-80 grid place-items-center py-4 z-10 p-3">
187
+ <div className="absolute top-full w-lg left-0 right-0 mt-1 bg-white shadow-lg rounded-lg border w-80 grid place-items-center py-4 z-50 p-3">
188
188
  <div className="flex items-center space-x-2">
189
189
  <div className="w-4 h-4 border-2 border-blue-500 border-t-transparent rounded-full animate-spin"></div>
190
190
  <span className="text-gray-600 text-sm">Searching...</span>
@@ -195,7 +195,7 @@ const SearchComponent: React.FC<SearchComponentProps> = ({
195
195
  {showResults && searchResults && (
196
196
  <div
197
197
  ref={resultsRef}
198
- className="absolute top-full md:left-0 right-0 mt-2 bg-white shadow-lg rounded-md z-10 w-80 max-h-screen overflow-y-auto overflow-x-visible"
198
+ className="absolute top-full md:left-0 right-0 mt-2 bg-white shadow-lg rounded-md z-50 w-80 max-h-screen overflow-y-auto overflow-x-visible"
199
199
  >
200
200
  {searchResults.length > 0 ? (
201
201
  <ul className="divide-y divide-gray-200">
@@ -9,8 +9,20 @@ export const SuspenseRunFrame = (
9
9
  props: React.ComponentProps<typeof RunFrame>,
10
10
  ) => {
11
11
  return (
12
- <Suspense fallback={<div>Loading...</div>}>
13
- <RunFrame {...props} />
12
+ <Suspense
13
+ fallback={
14
+ <div className="flex justify-center items-center h-full">
15
+ <div className="w-48">
16
+ <div className="loading">
17
+ <div className="loading-bar"></div>
18
+ </div>
19
+ </div>
20
+ </div>
21
+ }
22
+ >
23
+ <div className="h-[98vh]">
24
+ <RunFrame {...props} />
25
+ </div>
14
26
  </Suspense>
15
27
  )
16
28
  }
@@ -1,12 +1,22 @@
1
1
  "use client"
2
2
 
3
3
  import { useState, useEffect } from "react"
4
- import { Edit, FileText, Code, Copy, CopyCheck } from "lucide-react"
4
+ import {
5
+ Edit,
6
+ FileText,
7
+ Code,
8
+ Copy,
9
+ CopyCheck,
10
+ Loader2,
11
+ RefreshCcwIcon,
12
+ } from "lucide-react"
5
13
  import { Skeleton } from "@/components/ui/skeleton"
14
+ import { Button } from "@/components/ui/button"
6
15
  import { usePackageFile, usePackageFileByPath } from "@/hooks/use-package-files"
7
16
  import { ShikiCodeViewer } from "./ShikiCodeViewer"
8
17
  import { SparklesIcon } from "lucide-react"
9
18
  import MarkdownViewer from "./markdown-viewer"
19
+ import { useGlobalStore } from "@/hooks/use-global-store"
10
20
 
11
21
  interface PackageFile {
12
22
  package_file_id: string
@@ -20,7 +30,7 @@ interface ImportantFilesViewProps {
20
30
  importantFiles?: PackageFile[]
21
31
  isLoading?: boolean
22
32
  onEditClicked?: (file_path?: string | null) => void
23
-
33
+ packageAuthorOwner?: string | null
24
34
  aiDescription?: string
25
35
  aiUsageInstructions?: string
26
36
  aiReviewText?: string | null
@@ -37,12 +47,20 @@ export default function ImportantFilesView({
37
47
  onRequestAiReview,
38
48
  isLoading = false,
39
49
  onEditClicked,
50
+ packageAuthorOwner,
40
51
  }: ImportantFilesViewProps) {
41
52
  const [activeFilePath, setActiveFilePath] = useState<string | null>(null)
42
53
  const [activeTab, setActiveTab] = useState<string | null>(null)
43
54
  const [copyState, setCopyState] = useState<"copy" | "copied">("copy")
55
+ const { session: user } = useGlobalStore()
44
56
 
45
57
  const handleCopy = () => {
58
+ if (activeTab === "ai-review" && aiReviewText) {
59
+ navigator.clipboard.writeText(aiReviewText || "")
60
+ setCopyState("copied")
61
+ setTimeout(() => setCopyState("copy"), 500)
62
+ return
63
+ }
46
64
  navigator.clipboard.writeText(activeFileContent)
47
65
  setCopyState("copied")
48
66
  setTimeout(() => setCopyState("copy"), 500)
@@ -131,20 +149,59 @@ export default function ImportantFilesView({
131
149
  }
132
150
 
133
151
  const renderAiReviewContent = () => {
152
+ const isOwner = user?.github_username === packageAuthorOwner
153
+
134
154
  if (!aiReviewText && !aiReviewRequested) {
135
155
  return (
136
- <button
137
- className="text-sm text-blue-600 dark:text-[#58a6ff] underline"
138
- onClick={onRequestAiReview}
139
- >
140
- Request AI Review
141
- </button>
156
+ <div className="flex flex-col items-center justify-center py-8 px-4">
157
+ <div className="text-center space-y-4 max-w-md">
158
+ <div className="flex justify-center">
159
+ <div className="w-12 h-12 bg-blue-100 rounded-full flex items-center justify-center">
160
+ <SparklesIcon className="h-6 w-6 text-blue-600" />
161
+ </div>
162
+ </div>
163
+ <div className="space-y-2">
164
+ <p className="text-sm text-gray-600">
165
+ Get detailed feedback and suggestions for improving your package
166
+ from our AI assistant.
167
+ </p>
168
+ </div>
169
+ {isOwner ? (
170
+ <Button
171
+ onClick={onRequestAiReview}
172
+ size="sm"
173
+ className="bg-blue-600 hover:bg-blue-700 text-white shadow-sm transition-all duration-200 hover:shadow-md"
174
+ >
175
+ <SparklesIcon className="h-4 w-4 mr-2" />
176
+ Request AI Review
177
+ </Button>
178
+ ) : (
179
+ <p className="text-sm text-gray-500">
180
+ Only the package owner can generate an AI review
181
+ </p>
182
+ )}
183
+ </div>
184
+ </div>
142
185
  )
143
186
  }
144
187
 
145
188
  if (!aiReviewText && aiReviewRequested) {
146
189
  return (
147
- <p className="text-sm">AI review requested. Please check back later.</p>
190
+ <div className="flex flex-col items-center justify-center py-8 px-4">
191
+ <div className="text-center space-y-4 max-w-md">
192
+ <div className="flex justify-center">
193
+ <div className="w-12 h-12 bg-gray-200 rounded-full flex items-center justify-center">
194
+ <Loader2 className="h-6 w-6 text-gray-600 animate-spin" />
195
+ </div>
196
+ </div>
197
+ <div className="space-y-2">
198
+ <p className="text-sm text-gray-600">
199
+ Our AI is analyzing your package. This usually takes a few
200
+ minutes. Please check back shortly.
201
+ </p>
202
+ </div>
203
+ </div>
204
+ </div>
148
205
  )
149
206
  }
150
207
 
@@ -162,6 +219,7 @@ export default function ImportantFilesView({
162
219
  package_release_id: partialActiveFile.package_release_id,
163
220
  }
164
221
  : null,
222
+ { keepPreviousData: true },
165
223
  )
166
224
  const activeFileContent = activeFileFull?.content_text || ""
167
225
 
@@ -210,6 +268,8 @@ export default function ImportantFilesView({
210
268
  )
211
269
  }
212
270
 
271
+ const isOwner = user?.github_username === packageAuthorOwner
272
+
213
273
  return (
214
274
  <div className="mt-4 border border-gray-200 dark:border-[#30363d] rounded-md overflow-hidden">
215
275
  <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]">
@@ -268,7 +328,8 @@ export default function ImportantFilesView({
268
328
  ))}
269
329
  </div>
270
330
  <div className="ml-auto flex items-center">
271
- {activeFileContent && (
331
+ {((activeTab === "file" && activeFileContent) ||
332
+ (activeTab === "ai-review" && aiReviewText)) && (
272
333
  <button
273
334
  className="hover:bg-gray-200 dark:hover:bg-[#30363d] p-1 rounded-md transition-all duration-300"
274
335
  onClick={handleCopy}
@@ -281,13 +342,25 @@ export default function ImportantFilesView({
281
342
  <span className="sr-only">Copy</span>
282
343
  </button>
283
344
  )}
284
- <button
285
- className="hover:bg-gray-200 dark:hover:bg-[#30363d] p-1 rounded-md"
286
- onClick={() => onEditClicked?.(activeFilePath)}
287
- >
288
- <Edit className="h-4 w-4" />
289
- <span className="sr-only">Edit</span>
290
- </button>
345
+ {activeTab === "ai-review" && aiReviewText && isOwner && (
346
+ <button
347
+ className="hover:bg-gray-200 dark:hover:bg-[#30363d] p-1 rounded-md ml-1"
348
+ onClick={onRequestAiReview}
349
+ title="Re-request AI Review"
350
+ >
351
+ <RefreshCcwIcon className="h-4 w-4" />
352
+ <span className="sr-only">Re-request AI Review</span>
353
+ </button>
354
+ )}
355
+ {activeTab === "file" && (
356
+ <button
357
+ className="hover:bg-gray-200 dark:hover:bg-[#30363d] p-1 rounded-md"
358
+ onClick={() => onEditClicked?.(activeFilePath)}
359
+ >
360
+ <Edit className="h-4 w-4" />
361
+ <span className="sr-only">Edit</span>
362
+ </button>
363
+ )}
291
364
  </div>
292
365
  </div>
293
366
  <div className="p-4 bg-white dark:bg-[#0d1117]">
@@ -12,6 +12,7 @@ import {
12
12
  Pencil,
13
13
  GitForkIcon,
14
14
  DownloadIcon,
15
+ Package2,
15
16
  } from "lucide-react"
16
17
  import MainContentViewSelector from "./main-content-view-selector"
17
18
  import {
@@ -24,8 +25,11 @@ import {
24
25
  import { DownloadButtonAndMenu } from "@/components/DownloadButtonAndMenu"
25
26
  import { useCurrentPackageCircuitJson } from "../hooks/use-current-package-circuit-json"
26
27
  import { useLocation } from "wouter"
27
- import { Package } from "fake-snippets-api/lib/db/schema"
28
+ import { Package, PackageFile } from "fake-snippets-api/lib/db/schema"
29
+ import { usePackageFiles } from "@/hooks/use-package-files"
30
+ import { useDownloadZip } from "@/hooks/use-download-zip"
28
31
  interface MainContentHeaderProps {
32
+ packageFiles: PackageFile[]
29
33
  activeView: string
30
34
  onViewChange: (view: string) => void
31
35
  onExportClicked?: (exportType: string) => void
@@ -33,6 +37,7 @@ interface MainContentHeaderProps {
33
37
  }
34
38
 
35
39
  export default function MainContentHeader({
40
+ packageFiles,
36
41
  activeView,
37
42
  onViewChange,
38
43
  packageInfo,
@@ -59,6 +64,14 @@ export default function MainContentHeader({
59
64
  setTimeout(() => setCopyCloneState("copy"), 2000)
60
65
  }
61
66
 
67
+ const { downloadZip } = useDownloadZip()
68
+
69
+ const handleDownloadZip = () => {
70
+ if (packageInfo && packageFiles) {
71
+ downloadZip(packageInfo, packageFiles)
72
+ }
73
+ }
74
+
62
75
  const { circuitJson } = useCurrentPackageCircuitJson()
63
76
 
64
77
  return (
@@ -70,7 +83,9 @@ export default function MainContentHeader({
70
83
 
71
84
  <div className="flex space-x-2">
72
85
  <DownloadButtonAndMenu
73
- snippetUnscopedName={packageInfo?.unscoped_name}
86
+ unscopedName={packageInfo?.unscoped_name}
87
+ desiredImageType={activeView}
88
+ author={packageInfo?.owner_github_username ?? undefined}
74
89
  circuitJson={circuitJson}
75
90
  />
76
91
 
@@ -96,6 +111,15 @@ export default function MainContentHeader({
96
111
  Edit Online
97
112
  </a>
98
113
  </DropdownMenuItem>
114
+
115
+ <DropdownMenuItem
116
+ disabled={!Boolean(packageInfo)}
117
+ onClick={handleDownloadZip}
118
+ className="cursor-pointer p-2 py-4"
119
+ >
120
+ <Package2 className="h-4 w-4 mx-3" />
121
+ Download ZIP
122
+ </DropdownMenuItem>
99
123
  <DropdownMenuSeparator />
100
124
 
101
125
  {/* Install Option */}
@@ -41,8 +41,8 @@ const MobileSidebar = ({
41
41
  const isLoggedIn = useGlobalStore((s) => Boolean(s.session))
42
42
  const isOwner =
43
43
  isLoggedIn &&
44
- packageInfo?.creator_account_id ===
45
- useGlobalStore((s) => s.session?.account_id)
44
+ packageInfo?.owner_github_username ===
45
+ useGlobalStore((s) => s.session?.github_username)
46
46
 
47
47
  const {
48
48
  Dialog: EditPackageDetailsDialog,
@@ -24,6 +24,9 @@ import { useLocation } from "wouter"
24
24
  import { Package } from "fake-snippets-api/lib/db/schema"
25
25
  import { useCurrentPackageCircuitJson } from "../hooks/use-current-package-circuit-json"
26
26
  import { useRequestAiReviewMutation } from "@/hooks/use-request-ai-review-mutation"
27
+ import { useAiReview } from "@/hooks/use-ai-review"
28
+ import { useQueryClient } from "react-query"
29
+ import SidebarReleasesSection from "./sidebar-releases-section"
27
30
 
28
31
  interface PackageFile {
29
32
  package_file_id: string
@@ -51,14 +54,38 @@ export default function RepoPageContent({
51
54
  onEditClicked,
52
55
  }: RepoPageContentProps) {
53
56
  const [activeView, setActiveView] = useState<string>("files")
57
+ const [pendingAiReviewId, setPendingAiReviewId] = useState<string | null>(
58
+ null,
59
+ )
60
+ const queryClient = useQueryClient()
61
+ const { data: aiReview } = useAiReview(pendingAiReviewId, {
62
+ refetchInterval: (data) => (data && !data.ai_review_text ? 2000 : false),
63
+ })
64
+ useEffect(() => {
65
+ if (aiReview?.ai_review_text) {
66
+ queryClient.invalidateQueries(["packageRelease"])
67
+ setPendingAiReviewId(null)
68
+ }
69
+ }, [aiReview?.ai_review_text, queryClient])
54
70
  const session = useGlobalStore((s) => s.session)
55
71
  const { circuitJson, isLoading: isCircuitJsonLoading } =
56
72
  useCurrentPackageCircuitJson()
57
- const { mutate: requestAiReview } = useRequestAiReviewMutation()
73
+ const { mutate: requestAiReview, isLoading: isRequestingAiReview } =
74
+ useRequestAiReviewMutation({
75
+ onSuccess: (_packageRelease, aiReview) => {
76
+ setPendingAiReviewId(aiReview.ai_review_id)
77
+ },
78
+ })
79
+
80
+ const aiReviewRequested =
81
+ Boolean(packageRelease?.ai_review_requested) ||
82
+ Boolean(pendingAiReviewId) ||
83
+ isRequestingAiReview
58
84
 
59
85
  // Handle initial view selection and hash-based view changes
60
86
  useEffect(() => {
61
87
  if (isCircuitJsonLoading) return
88
+ if (!packageInfo) return
62
89
  const hash = window.location.hash.slice(1)
63
90
  const validViews = ["files", "3d", "pcb", "schematic", "bom"]
64
91
  const circuitDependentViews = ["3d", "pcb", "schematic", "bom"]
@@ -105,34 +132,6 @@ export default function RepoPageContent({
105
132
  .reverse()
106
133
  }, [packageFiles, importantFilePaths])
107
134
 
108
- // Generate package name with version for file lookups
109
- const packageNameWithVersion = useMemo(() => {
110
- if (!packageInfo) return ""
111
-
112
- // Format: @scope/packageName@version or packageName@version
113
- const name = packageInfo.name
114
-
115
- // Extract the latest version from the files (assuming version information is available)
116
- const versionFile = packageFiles?.find(
117
- (file) => file.file_path === "package.json",
118
- )
119
- let version = "latest"
120
-
121
- if (versionFile) {
122
- try {
123
- const content =
124
- versionFile.file_content || versionFile.content_text || "{}"
125
- const packageJson = JSON.parse(content)
126
- if (packageJson.version) {
127
- version = packageJson.version
128
- }
129
- } catch (e) {
130
- // If package.json can't be parsed, use "latest"
131
- }
132
- }
133
-
134
- return `${name}@${version}`
135
- }, [packageInfo, packageFiles])
136
135
  // Render the appropriate content based on the active view
137
136
  const renderContent = () => {
138
137
  switch (activeView) {
@@ -194,6 +193,7 @@ export default function RepoPageContent({
194
193
  {/* Main Content Header with Tabs */}
195
194
  <MainContentHeader
196
195
  activeView={activeView}
196
+ packageFiles={packageFiles ?? []}
197
197
  onViewChange={(view) => {
198
198
  setActiveView(view)
199
199
  // Update URL hash when view changes
@@ -210,10 +210,11 @@ export default function RepoPageContent({
210
210
  importantFiles={importantFiles}
211
211
  isLoading={!packageFiles}
212
212
  onEditClicked={onEditClicked}
213
+ packageAuthorOwner={packageInfo?.owner_github_username}
213
214
  aiDescription={packageInfo?.ai_description ?? ""}
214
215
  aiUsageInstructions={packageInfo?.ai_usage_instructions ?? ""}
215
216
  aiReviewText={packageRelease?.ai_review_text ?? null}
216
- aiReviewRequested={packageRelease?.ai_review_requested ?? false}
217
+ aiReviewRequested={aiReviewRequested}
217
218
  onRequestAiReview={() => {
218
219
  if (packageRelease) {
219
220
  requestAiReview({
@@ -236,6 +237,10 @@ export default function RepoPageContent({
236
237
  }}
237
238
  />
238
239
  </div>
240
+ {/* Releases section - Only visible on small screens */}
241
+ <div className="block md:hidden w-full px-5">
242
+ <SidebarReleasesSection />
243
+ </div>
239
244
  </div>
240
245
  </div>
241
246
  <Footer />
@@ -39,8 +39,8 @@ export default function SidebarAboutSection() {
39
39
  const isLoggedIn = useGlobalStore((s) => Boolean(s.session))
40
40
  const isOwner =
41
41
  isLoggedIn &&
42
- packageInfo?.creator_account_id ===
43
- useGlobalStore((s) => s.session?.account_id)
42
+ packageInfo?.owner_github_username ===
43
+ useGlobalStore((s) => s.session?.github_username)
44
44
 
45
45
  // Local state to store updated values before the query refetches
46
46
  const [localDescription, setLocalDescription] = useState<string>("")
@@ -9,21 +9,29 @@ import type { PackageRelease } from "fake-snippets-api/lib/db/schema"
9
9
  function getTranspilationStatus(
10
10
  pr?: PackageRelease | null,
11
11
  ): BuildStep["status"] {
12
- if (!pr) return "pending"
13
- if (pr.transpilation_error) return "error"
14
- if (pr.transpilation_in_progress) return "running"
15
- if (pr.transpilation_completed_at) return "success"
16
- if (pr.transpilation_started_at) return "running"
17
- return "pending"
12
+ switch (pr?.transpilation_display_status) {
13
+ case "complete":
14
+ return "success"
15
+ case "error":
16
+ return "error"
17
+ case "building":
18
+ return "running"
19
+ default:
20
+ return "pending"
21
+ }
18
22
  }
19
23
 
20
24
  function getCircuitJsonStatus(pr?: PackageRelease | null): BuildStep["status"] {
21
- if (!pr) return "pending"
22
- if (pr.circuit_json_build_error) return "error"
23
- if (pr.circuit_json_build_in_progress) return "running"
24
- if (pr.circuit_json_build_completed_at) return "success"
25
- if (pr.circuit_json_build_started_at) return "running"
26
- return "pending"
25
+ switch (pr?.circuit_json_build_display_status) {
26
+ case "complete":
27
+ return "success"
28
+ case "error":
29
+ return "error"
30
+ case "building":
31
+ return "running"
32
+ default:
33
+ return "pending"
34
+ }
27
35
  }
28
36
 
29
37
  export default function SidebarReleasesSection() {
@@ -5,13 +5,6 @@ import { FileText, Folder } from "lucide-react"
5
5
  import { useMemo, useState } from "react"
6
6
  import { isHiddenFile } from "../../utils/is-hidden-file"
7
7
  import { isWithinDirectory } from "../../utils/is-within-directory"
8
- import {
9
- DropdownMenu,
10
- DropdownMenuContent,
11
- DropdownMenuItem,
12
- DropdownMenuTrigger,
13
- } from "@/components/ui/dropdown-menu"
14
- import { Settings } from "lucide-react"
15
8
  import HiddenFilesDropdown from "@/components/HiddenFilesDropdown"
16
9
 
17
10
  interface Directory {
@@ -0,0 +1,121 @@
1
+ export const fuzzyMatch = (
2
+ pattern: string,
3
+ text: string,
4
+ ): { score: number; matches: number[] } => {
5
+ if (!pattern) return { score: 0, matches: [] }
6
+
7
+ const normalizePattern = (pat: string) => {
8
+ return pat.toLowerCase().split(" ").join("")
9
+ }
10
+
11
+ const patternLower = normalizePattern(pattern)
12
+ const textLower = text.toLowerCase()
13
+ const matches: number[] = []
14
+
15
+ let patternIdx = 0
16
+ let score = 0
17
+ let consecutiveMatches = 0
18
+ let spaceBonus = 0
19
+
20
+ const spaceSegments = pattern.toLowerCase().trim().split(/\s+/)
21
+ const isSpaceSeparated = spaceSegments.length > 1
22
+
23
+ if (isSpaceSeparated) {
24
+ let segmentIdx = 0
25
+ let currentSegment = spaceSegments[0]
26
+ let segmentCharIdx = 0
27
+
28
+ for (
29
+ let i = 0;
30
+ i < textLower.length && segmentIdx < spaceSegments.length;
31
+ i++
32
+ ) {
33
+ const char = textLower[i]
34
+ const targetChar = currentSegment[segmentCharIdx]
35
+
36
+ if (char === targetChar) {
37
+ matches.push(i)
38
+ segmentCharIdx++
39
+ consecutiveMatches++
40
+ score += 1 + consecutiveMatches * 0.5
41
+
42
+ if (i === 0 || /[/\-_.]/.test(text[i - 1])) {
43
+ score += 2
44
+ }
45
+
46
+ if (segmentCharIdx >= currentSegment.length) {
47
+ segmentIdx++
48
+ spaceBonus += 3
49
+
50
+ if (segmentIdx < spaceSegments.length) {
51
+ currentSegment = spaceSegments[segmentIdx]
52
+ segmentCharIdx = 0
53
+ consecutiveMatches = 0
54
+ }
55
+ }
56
+ } else {
57
+ if (
58
+ segmentCharIdx > 0 &&
59
+ /[/\-_.]/.test(char) &&
60
+ segmentIdx < spaceSegments.length - 1
61
+ ) {
62
+ segmentIdx++
63
+ if (segmentIdx < spaceSegments.length) {
64
+ currentSegment = spaceSegments[segmentIdx]
65
+ segmentCharIdx = 0
66
+ consecutiveMatches = 0
67
+ if (char === currentSegment[0]) {
68
+ matches.push(i)
69
+ segmentCharIdx = 1
70
+ score += 2
71
+ }
72
+ }
73
+ } else {
74
+ consecutiveMatches = 0
75
+ }
76
+ }
77
+ }
78
+
79
+ if (
80
+ segmentIdx < spaceSegments.length ||
81
+ (segmentIdx === spaceSegments.length - 1 &&
82
+ segmentCharIdx < currentSegment.length)
83
+ ) {
84
+ return { score: -1, matches: [] }
85
+ }
86
+
87
+ score += spaceBonus
88
+ } else {
89
+ for (
90
+ let i = 0;
91
+ i < textLower.length && patternIdx < patternLower.length;
92
+ i++
93
+ ) {
94
+ if (textLower[i] === patternLower[patternIdx]) {
95
+ matches.push(i)
96
+ patternIdx++
97
+ consecutiveMatches++
98
+
99
+ score += 1 + consecutiveMatches * 0.5
100
+
101
+ if (i === 0 || /[/\-_.]/.test(text[i - 1])) {
102
+ score += 2
103
+ }
104
+ } else {
105
+ consecutiveMatches = 0
106
+ }
107
+ }
108
+
109
+ if (patternIdx !== patternLower.length) return { score: -1, matches: [] }
110
+ }
111
+
112
+ score += Math.max(0, 100 - text.length) * 0.1
113
+
114
+ const fileName = text.split("/").pop() || text
115
+ const queryFileName = pattern.replace(/\s+/g, "")
116
+ if (fileName.toLowerCase().includes(queryFileName.toLowerCase())) {
117
+ score += 5
118
+ }
119
+
120
+ return { score, matches }
121
+ }
@@ -1,4 +1,8 @@
1
1
  export const isHiddenFile = (filePath: string): boolean => {
2
+ if (filePath.startsWith("/")) {
3
+ filePath = filePath.slice(1)
4
+ }
5
+
2
6
  // Normalize the path to handle both Unix and Windows paths
3
7
  const normalizedPath = filePath.replace(/\\/g, "/")
4
8
 
@@ -33,7 +33,7 @@ export const ConfirmDeletePackageDialog = ({
33
33
 
34
34
  return (
35
35
  <Dialog open={open} onOpenChange={onOpenChange}>
36
- <DialogContent>
36
+ <DialogContent className="w-[90vw]">
37
37
  <DialogHeader>
38
38
  <DialogTitle>Confirm Delete Package</DialogTitle>
39
39
  </DialogHeader>