@tscircuit/fake-snippets 0.0.106 → 0.0.108

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 (43) hide show
  1. package/CLAUDE.md +92 -0
  2. package/api/generated-index.js +84 -25
  3. package/biome.json +7 -1
  4. package/bun-tests/fake-snippets-api/routes/package_builds/get.test.ts +0 -15
  5. package/bun-tests/fake-snippets-api/routes/package_builds/list.test.ts +0 -12
  6. package/dist/bundle.js +360 -434
  7. package/dist/index.d.ts +26 -15
  8. package/dist/index.js +40 -21
  9. package/dist/schema.d.ts +32 -24
  10. package/dist/schema.js +7 -5
  11. package/fake-snippets-api/lib/db/db-client.ts +19 -1
  12. package/fake-snippets-api/lib/db/schema.ts +6 -3
  13. package/fake-snippets-api/lib/db/seed.ts +23 -12
  14. package/fake-snippets-api/lib/public-mapping/public-map-package-build.ts +0 -3
  15. package/fake-snippets-api/lib/public-mapping/public-map-package-release.ts +3 -0
  16. package/fake-snippets-api/routes/api/package_builds/list.ts +0 -1
  17. package/package.json +3 -2
  18. package/src/App.tsx +27 -9
  19. package/src/components/FileSidebar.tsx +14 -159
  20. package/src/components/PackageBreadcrumb.tsx +111 -0
  21. package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +1 -1
  22. package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +18 -2
  23. package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +18 -8
  24. package/src/components/preview/BuildsList.tsx +84 -167
  25. package/src/components/preview/ConnectedPackagesList.tsx +92 -62
  26. package/src/components/preview/ConnectedRepoOverview.tsx +171 -155
  27. package/src/components/preview/{ConnectedRepoDashboard.tsx → PackageReleasesDashboard.tsx} +31 -69
  28. package/src/components/preview/index.tsx +20 -154
  29. package/src/hooks/use-package-builds.ts +0 -48
  30. package/src/hooks/use-package-release-by-id-or-version.ts +36 -0
  31. package/src/hooks/use-package-release.ts +32 -0
  32. package/src/index.css +24 -0
  33. package/src/lib/utils/isUuid.ts +5 -0
  34. package/src/lib/utils/transformFilesToTreeData.tsx +195 -0
  35. package/src/pages/404.tsx +3 -5
  36. package/src/pages/preview-release.tsx +269 -0
  37. package/src/pages/release-builds.tsx +99 -0
  38. package/src/pages/release-detail.tsx +120 -0
  39. package/src/pages/releases.tsx +55 -0
  40. package/fake-snippets-api/routes/api/package_builds/latest.ts +0 -109
  41. package/src/hooks/use-snippets-base-api-url.ts +0 -3
  42. package/src/pages/preview-build.tsx +0 -380
  43. package/src/pages/view-connected-repo.tsx +0 -49
package/src/App.tsx CHANGED
@@ -79,10 +79,10 @@ const PackageEditorPage = lazyImport(async () => {
79
79
  ])
80
80
  return editorModule
81
81
  })
82
- const ViewConnectedRepoPage = lazyImport(
83
- () => import("@/pages/view-connected-repo"),
84
- )
85
- const PreviewBuildPage = lazyImport(() => import("@/pages/preview-build"))
82
+ const ReleasesPage = lazyImport(() => import("@/pages/releases"))
83
+ const ReleaseDetailPage = lazyImport(() => import("@/pages/release-detail"))
84
+ const ReleaseBuildsPage = lazyImport(() => import("@/pages/release-builds"))
85
+ const ReleasePreviewPage = lazyImport(() => import("@/pages/preview-release"))
86
86
 
87
87
  class ErrorBoundary extends React.Component<
88
88
  { children: React.ReactNode },
@@ -109,7 +109,7 @@ class ErrorBoundary extends React.Component<
109
109
  )
110
110
  ) {
111
111
  const loadedAt = window.__APP_LOADED_AT || Date.now()
112
- if (Date.now() - loadedAt >= 10_000) {
112
+ if (Date.now() - loadedAt >= 180_000) {
113
113
  this.performReload()
114
114
  }
115
115
  }
@@ -144,6 +144,7 @@ class ErrorBoundary extends React.Component<
144
144
  this.setState({ reloading: true })
145
145
  this.reloadTimeout = window.setTimeout(() => {
146
146
  if (window?.location.href.includes("localhost:")) return
147
+ if (window?.location.href.includes("127.0.0.1:")) return
147
148
  window.location.reload()
148
149
  }, 500)
149
150
  }
@@ -152,10 +153,12 @@ class ErrorBoundary extends React.Component<
152
153
  this.cleanup() // Clean up any existing handlers
153
154
 
154
155
  this.visibilityHandler = () => {
156
+ const loadedAt = window.__APP_LOADED_AT || Date.now()
155
157
  if (
156
158
  document.visibilityState === "visible" &&
157
159
  this.state.hasError &&
158
- !this.state.reloading
160
+ !this.state.reloading &&
161
+ Date.now() - loadedAt >= 180_000
159
162
  ) {
160
163
  this.performReload()
161
164
  }
@@ -258,10 +261,25 @@ function App() {
258
261
  <Route path="/my-orders" component={MyOrdersPage} />
259
262
  <Route path="/dev-login" component={DevLoginPage} />
260
263
  <Route path="/:username" component={UserProfilePage} />
261
- <Route path="/build/:buildId" component={ViewConnectedRepoPage} />
262
264
  <Route
263
- path="/build/:buildId/preview"
264
- component={PreviewBuildPage}
265
+ path="/:author/:packageName/releases/:releaseId/builds"
266
+ component={ReleaseBuildsPage}
267
+ />
268
+ <Route
269
+ path="/:author/:packageName/releases/:releaseId"
270
+ component={ReleaseDetailPage}
271
+ />
272
+ <Route
273
+ path="/:author/:packageName/releases/:packageReleaseId/preview"
274
+ component={ReleasePreviewPage}
275
+ />
276
+ <Route
277
+ path="/:author/:packageName/releases/:packageReleaseId"
278
+ component={ReleaseDetailPage}
279
+ />
280
+ <Route
281
+ path="/:author/:packageName/releases"
282
+ component={ReleasesPage}
265
283
  />
266
284
  <Route path="/:author/:packageName" component={ViewPackagePage} />
267
285
  <Route
@@ -12,6 +12,7 @@ import {
12
12
  import { TreeView, TreeDataItem } from "@/components/ui/tree-view"
13
13
  import { isHiddenFile } from "./ViewPackagePage/utils/is-hidden-file"
14
14
  import { Input } from "@/components/ui/input"
15
+ import { transformFilesToTreeData } from "@/lib/utils/transformFilesToTreeData"
15
16
  import {
16
17
  DropdownMenu,
17
18
  DropdownMenuContent,
@@ -75,165 +76,19 @@ const FileSidebar: React.FC<FileSidebarProps> = ({
75
76
  setSelectedFolderForCreation(folderPath)
76
77
  }
77
78
 
78
- const transformFilesToTreeData = (
79
- files: Record<FileName, string>,
80
- ): TreeDataItem[] => {
81
- type TreeNode = Omit<TreeDataItem, "children"> & {
82
- children?: Record<string, TreeNode>
83
- }
84
- const root: Record<string, TreeNode> = {}
85
-
86
- Object.keys(files).forEach((filePath) => {
87
- const hasLeadingSlash = filePath.startsWith("/")
88
- const pathSegments = (hasLeadingSlash ? filePath.slice(1) : filePath)
89
- .trim()
90
- .split("/")
91
- let currentNode: Record<string, TreeNode> = root
92
-
93
- pathSegments.forEach((segment, segmentIndex) => {
94
- const isLeafNode = segmentIndex === pathSegments.length - 1
95
- const ancestorPath = pathSegments.slice(0, segmentIndex).join("/")
96
- const relativePath = ancestorPath
97
- ? `${ancestorPath}/${segment}`
98
- : segment
99
- const absolutePath = hasLeadingSlash ? `/${relativePath}` : relativePath
100
- const itemId = absolutePath
101
-
102
- if (
103
- !currentNode[segment] &&
104
- (!isHiddenFile(relativePath) ||
105
- isHiddenFile(
106
- currentFile?.startsWith("/")
107
- ? currentFile.slice(1)
108
- : currentFile || "",
109
- ))
110
- ) {
111
- currentNode[segment] = {
112
- id: itemId,
113
- name: segment,
114
- isRenaming: renamingFile === itemId,
115
- onRename: (newFilename: string) => {
116
- const oldPath = itemId
117
- const pathParts = oldPath.split("/").filter((part) => part !== "")
118
- let newPath: string
119
-
120
- if (pathParts.length > 1) {
121
- const folderPath = pathParts.slice(0, -1).join("/")
122
- newPath = folderPath + "/" + newFilename
123
- } else {
124
- newPath = newFilename
125
- }
126
-
127
- if (oldPath.startsWith("/") && !newPath.startsWith("/")) {
128
- newPath = "/" + newPath
129
- }
130
-
131
- const { fileRenamed } = handleRenameFile({
132
- oldFilename: itemId,
133
- newFilename: newPath,
134
- onError: (error) => {
135
- toast({
136
- title: `Error renaming file`,
137
- description: error.message,
138
- variant: "destructive",
139
- })
140
- },
141
- })
142
- if (fileRenamed) {
143
- setRenamingFile(null)
144
- }
145
- },
146
- onCancelRename: () => {
147
- setRenamingFile(null)
148
- },
149
- icon: isLeafNode ? File : Folder,
150
- onClick: isLeafNode
151
- ? () => {
152
- onFileSelect(absolutePath)
153
- setSelectedFolderForCreation(null)
154
- }
155
- : () => onFolderSelect(absolutePath),
156
- draggable: false,
157
- droppable: !isLeafNode,
158
- children: isLeafNode ? undefined : {},
159
- actions: canModifyFiles ? (
160
- <>
161
- <DropdownMenu key={itemId}>
162
- <DropdownMenuTrigger asChild>
163
- <MoreVertical className="w-4 h-4 text-gray-500 hover:text-gray-700" />
164
- </DropdownMenuTrigger>
165
- <DropdownMenuContent
166
- className="w-fit bg-white shadow-lg rounded-md border-4 z-[100] border-white"
167
- style={{
168
- position: "absolute",
169
- top: "100%",
170
- left: "0",
171
- marginTop: "0.5rem",
172
- width: "8rem",
173
- padding: "0.01rem",
174
- }}
175
- >
176
- <DropdownMenuGroup>
177
- {isLeafNode && (
178
- <DropdownMenuItem
179
- onClick={() => {
180
- setRenamingFile(itemId)
181
- }}
182
- className="flex items-center px-3 py-1 text-xs text-black hover:bg-gray-100 cursor-pointer"
183
- >
184
- <Pencil className="mr-2 h-3 w-3" />
185
- Rename
186
- </DropdownMenuItem>
187
- )}
188
- <DropdownMenuItem
189
- onClick={() => {
190
- const { fileDeleted } = handleDeleteFile({
191
- filename: itemId,
192
- onError: (error) => {
193
- toast({
194
- title: `Error deleting file ${itemId}`,
195
- description: error.message,
196
- })
197
- },
198
- })
199
- if (fileDeleted) {
200
- setErrorMessage("")
201
- }
202
- }}
203
- className="flex items-center px-3 py-1 text-xs text-red-600 hover:bg-gray-100 cursor-pointer"
204
- >
205
- <Trash2 className="mr-2 h-3 w-3" />
206
- Delete
207
- </DropdownMenuItem>
208
- </DropdownMenuGroup>
209
- </DropdownMenuContent>
210
- </DropdownMenu>
211
- </>
212
- ) : undefined,
213
- }
214
- }
215
-
216
- if (!isLeafNode && currentNode[segment].children) {
217
- currentNode = currentNode[segment].children
218
- }
219
- })
220
- })
221
-
222
- const convertToArray = (
223
- items: Record<string, TreeNode>,
224
- ): TreeDataItem[] => {
225
- return Object.values(items).map((item) => ({
226
- ...item,
227
- children: item.children ? convertToArray(item.children) : undefined,
228
- }))
229
- }
230
- return convertToArray(root).filter((x) => {
231
- if (x.children?.length === 0) return false
232
- return true
233
- })
234
- }
235
-
236
- const treeData = transformFilesToTreeData(files)
79
+ const treeData = transformFilesToTreeData({
80
+ files,
81
+ currentFile,
82
+ renamingFile,
83
+ handleRenameFile,
84
+ handleDeleteFile,
85
+ setRenamingFile,
86
+ onFileSelect,
87
+ onFolderSelect,
88
+ canModifyFiles,
89
+ setErrorMessage,
90
+ setSelectedFolderForCreation,
91
+ })
237
92
 
238
93
  const getCurrentFolderPath = (): string => {
239
94
  if (selectedFolderForCreation) {
@@ -0,0 +1,111 @@
1
+ import { PrefetchPageLink } from "@/components/PrefetchPageLink"
2
+ import {
3
+ Breadcrumb,
4
+ BreadcrumbList,
5
+ BreadcrumbItem,
6
+ BreadcrumbLink,
7
+ BreadcrumbPage,
8
+ BreadcrumbSeparator,
9
+ } from "@/components/ui/breadcrumb"
10
+
11
+ interface PackageBreadcrumbProps {
12
+ author: string
13
+ packageName: string
14
+ unscopedName?: string
15
+ currentPage?: "releases" | "builds" | string
16
+ releaseVersion?: string
17
+ }
18
+
19
+ export function PackageBreadcrumb({
20
+ author,
21
+ packageName,
22
+ unscopedName,
23
+ currentPage,
24
+ releaseVersion,
25
+ }: PackageBreadcrumbProps) {
26
+ return (
27
+ <Breadcrumb className="mb-4">
28
+ <BreadcrumbList>
29
+ {/* Author */}
30
+ <BreadcrumbItem>
31
+ <BreadcrumbLink asChild>
32
+ <PrefetchPageLink href={`/${author}`}>{author}</PrefetchPageLink>
33
+ </BreadcrumbLink>
34
+ </BreadcrumbItem>
35
+ <BreadcrumbSeparator>
36
+ <span>/</span>
37
+ </BreadcrumbSeparator>
38
+
39
+ {/* Package */}
40
+ <BreadcrumbItem>
41
+ <BreadcrumbLink asChild>
42
+ <PrefetchPageLink href={`/${packageName}`}>
43
+ {unscopedName || packageName}
44
+ </PrefetchPageLink>
45
+ </BreadcrumbLink>
46
+ </BreadcrumbItem>
47
+ <BreadcrumbSeparator>
48
+ <span>/</span>
49
+ </BreadcrumbSeparator>
50
+
51
+ {/* Releases */}
52
+ {(currentPage === "releases" ||
53
+ releaseVersion ||
54
+ currentPage === "builds") && (
55
+ <>
56
+ <BreadcrumbItem>
57
+ {currentPage === "releases" ? (
58
+ <BreadcrumbPage>releases</BreadcrumbPage>
59
+ ) : (
60
+ <BreadcrumbLink asChild>
61
+ <PrefetchPageLink href={`/${packageName}/releases`}>
62
+ releases
63
+ </PrefetchPageLink>
64
+ </BreadcrumbLink>
65
+ )}
66
+ </BreadcrumbItem>
67
+ <BreadcrumbSeparator>
68
+ <span>/</span>
69
+ </BreadcrumbSeparator>
70
+ </>
71
+ )}
72
+
73
+ {/* Release Version */}
74
+ {releaseVersion && (
75
+ <>
76
+ <BreadcrumbItem>
77
+ {currentPage === "builds" ? (
78
+ <BreadcrumbLink asChild>
79
+ <PrefetchPageLink
80
+ href={`/${packageName}/releases/${releaseVersion}`}
81
+ >
82
+ {releaseVersion}
83
+ </PrefetchPageLink>
84
+ </BreadcrumbLink>
85
+ ) : (
86
+ <BreadcrumbPage>{releaseVersion}</BreadcrumbPage>
87
+ )}
88
+ </BreadcrumbItem>
89
+ {currentPage === "builds" && (
90
+ <>
91
+ <BreadcrumbSeparator>
92
+ <span>/</span>
93
+ </BreadcrumbSeparator>
94
+ <BreadcrumbItem>
95
+ <BreadcrumbPage>builds</BreadcrumbPage>
96
+ </BreadcrumbItem>
97
+ </>
98
+ )}
99
+ </>
100
+ )}
101
+
102
+ {/* Other current pages */}
103
+ {currentPage && !["releases", "builds"].includes(currentPage) && (
104
+ <BreadcrumbItem>
105
+ <BreadcrumbPage>{currentPage}</BreadcrumbPage>
106
+ </BreadcrumbItem>
107
+ )}
108
+ </BreadcrumbList>
109
+ </Breadcrumb>
110
+ )
111
+ }
@@ -112,7 +112,7 @@ const MobileSidebar = ({
112
112
  {localDescription ||
113
113
  packageInfo?.description ||
114
114
  packageInfo?.ai_description ||
115
- "A Default 60 keyboard created with tscircuit"}
115
+ ""}
116
116
  </p>
117
117
  {isOwner && (
118
118
  <Button
@@ -1,5 +1,5 @@
1
1
  import { Badge } from "@/components/ui/badge"
2
- import { GitFork, Star, Settings, LinkIcon, Github } from "lucide-react"
2
+ import { GitFork, Star, Settings, LinkIcon, Github, Plus } from "lucide-react"
3
3
  import { Skeleton } from "@/components/ui/skeleton"
4
4
  import { useCurrentPackageInfo } from "@/hooks/use-current-package-info"
5
5
  import { usePackageReleaseById } from "@/hooks/use-package-release"
@@ -164,7 +164,7 @@ export default function SidebarAboutSection({
164
164
  <GitFork className="h-4 w-4 mr-2 text-gray-500 dark:text-[#8b949e]" />
165
165
  <span>{(packageInfo as any)?.fork_count ?? "0"} forks</span>
166
166
  </div>
167
- {packageInfo?.github_repo_full_name && (
167
+ {packageInfo?.github_repo_full_name ? (
168
168
  <a
169
169
  target="_blank"
170
170
  href={`https://github.com/${packageInfo.github_repo_full_name}`}
@@ -173,6 +173,22 @@ export default function SidebarAboutSection({
173
173
  <Github className="h-4 w-4 mr-2 text-gray-500 dark:text-[#8b949e]" />
174
174
  <span>{packageInfo?.github_repo_full_name.split("/")[1]}</span>
175
175
  </a>
176
+ ) : (
177
+ <>
178
+ {isOwner && (
179
+ <div
180
+ className="flex items-center hover:underline hover:underline-offset-2 cursor-pointer hover:decoration-gray-500"
181
+ onClick={openEditPackageDetailsDialog}
182
+ title="Connect GitHub"
183
+ >
184
+ <div className="relative mr-2">
185
+ <Github className="h-4 w-4 text-gray-500 dark:text-[#8b949e]" />
186
+ <Plus className="h-2 w-2 absolute -bottom-0.5 -right-0.5 text-gray-500 dark:text-[#8b949e] bg-white dark:bg-[#0d1117] rounded-full" />
187
+ </div>
188
+ <span>Connect GitHub</span>
189
+ </div>
190
+ )}
191
+ </>
176
192
  )}
177
193
  </div>
178
194
  </div>
@@ -7,7 +7,7 @@ import { BuildStatus, BuildStep } from "./build-status"
7
7
  import type { PackageRelease } from "fake-snippets-api/lib/db/schema"
8
8
  import { getBuildStatus, StatusIcon } from "@/components/preview"
9
9
  import { PrefetchPageLink } from "@/components/PrefetchPageLink"
10
- import { useLatestPackageBuildByReleaseId } from "@/hooks/use-package-builds"
10
+ import { usePackageBuild } from "@/hooks/use-package-builds"
11
11
 
12
12
  function getTranspilationStatus(
13
13
  pr?: PackageRelease | null,
@@ -42,8 +42,8 @@ export default function SidebarReleasesSection() {
42
42
  const { data: packageRelease } = usePackageReleaseById(
43
43
  packageInfo?.latest_package_release_id,
44
44
  )
45
- const { data: latestBuild } = useLatestPackageBuildByReleaseId(
46
- packageRelease?.package_release_id,
45
+ const { data: latestBuild } = usePackageBuild(
46
+ packageRelease?.latest_package_build_id ?? null,
47
47
  )
48
48
 
49
49
  const buildSteps: BuildStep[] = [
@@ -79,12 +79,22 @@ export default function SidebarReleasesSection() {
79
79
  : { status: "pending", label: "pending" }
80
80
  return (
81
81
  <div className="mb-6">
82
- <h2 className="text-lg font-semibold mb-2">Releases</h2>
82
+ <h2 className="text-lg font-semibold mb-2">
83
+ <PrefetchPageLink
84
+ href={`/${packageInfo?.owner_github_username}/${packageInfo?.unscoped_name}/releases`}
85
+ className="hover:underline"
86
+ >
87
+ Releases
88
+ </PrefetchPageLink>
89
+ </h2>
83
90
  <div className="flex flex-col space-y-2">
84
- <div className="flex items-center">
91
+ <PrefetchPageLink
92
+ href={`/${packageInfo?.owner_github_username}/${packageInfo?.unscoped_name}/releases`}
93
+ className="flex items-center hover:underline"
94
+ >
85
95
  <Tag className="h-4 w-4 mr-2 text-gray-500 dark:text-[#8b949e]" />
86
96
  <span className="text-sm font-medium">v{packageRelease.version}</span>
87
- </div>
97
+ </PrefetchPageLink>
88
98
  <div className="flex items-center">
89
99
  <Clock className="h-4 w-4 mr-2 text-gray-500 dark:text-[#8b949e]" />
90
100
  <span className="text-sm text-gray-500 dark:text-[#8b949e]">
@@ -100,11 +110,11 @@ export default function SidebarReleasesSection() {
100
110
  ))}
101
111
  {latestBuild && (
102
112
  <PrefetchPageLink
103
- href={`/build/${latestBuild.package_build_id}`}
113
+ href={`/${packageInfo?.name}/releases`}
104
114
  className="flex items-center gap-2 text-sm text-gray-500 dark:text-[#8b949e]"
105
115
  >
106
116
  <StatusIcon status={status} />
107
- <span>Package preview {label}</span>
117
+ <span>Package Preview {label}</span>
108
118
  </PrefetchPageLink>
109
119
  )}
110
120
  </div>