@tscircuit/fake-snippets 0.0.107 → 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 (35) hide show
  1. package/api/generated-index.js +82 -22
  2. package/biome.json +7 -1
  3. package/bun-tests/fake-snippets-api/routes/package_builds/get.test.ts +0 -15
  4. package/bun-tests/fake-snippets-api/routes/package_builds/list.test.ts +0 -12
  5. package/dist/bundle.js +23 -23
  6. package/dist/index.d.ts +21 -15
  7. package/dist/index.js +17 -17
  8. package/dist/schema.d.ts +24 -24
  9. package/dist/schema.js +5 -5
  10. package/fake-snippets-api/lib/db/db-client.ts +10 -1
  11. package/fake-snippets-api/lib/db/schema.ts +3 -3
  12. package/fake-snippets-api/lib/db/seed.ts +6 -9
  13. package/fake-snippets-api/lib/public-mapping/public-map-package-build.ts +0 -3
  14. package/fake-snippets-api/lib/public-mapping/public-map-package-release.ts +3 -0
  15. package/package.json +1 -1
  16. package/src/App.tsx +12 -9
  17. package/src/components/FileSidebar.tsx +14 -159
  18. package/src/components/PackageBreadcrumb.tsx +1 -1
  19. package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +1 -1
  20. package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +18 -2
  21. package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +1 -1
  22. package/src/components/preview/BuildsList.tsx +20 -9
  23. package/src/components/preview/ConnectedPackagesList.tsx +73 -60
  24. package/src/components/preview/ConnectedRepoOverview.tsx +160 -154
  25. package/src/components/preview/PackageReleasesDashboard.tsx +11 -5
  26. package/src/components/preview/index.tsx +16 -153
  27. package/src/index.css +24 -0
  28. package/src/lib/utils/transformFilesToTreeData.tsx +195 -0
  29. package/src/pages/404.tsx +3 -5
  30. package/src/pages/preview-release.tsx +269 -0
  31. package/src/pages/release-builds.tsx +0 -8
  32. package/src/pages/release-detail.tsx +17 -15
  33. package/src/pages/releases.tsx +5 -1
  34. package/src/hooks/use-snippets-base-api-url.ts +0 -3
  35. package/src/pages/preview-build.tsx +0 -380
@@ -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) {
@@ -77,7 +77,7 @@ export function PackageBreadcrumb({
77
77
  {currentPage === "builds" ? (
78
78
  <BreadcrumbLink asChild>
79
79
  <PrefetchPageLink
80
- href={`/${packageName}/release/${releaseVersion}`}
80
+ href={`/${packageName}/releases/${releaseVersion}`}
81
81
  >
82
82
  {releaseVersion}
83
83
  </PrefetchPageLink>
@@ -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>
@@ -110,7 +110,7 @@ export default function SidebarReleasesSection() {
110
110
  ))}
111
111
  {latestBuild && (
112
112
  <PrefetchPageLink
113
- href={`/build/${latestBuild.package_build_id}`}
113
+ href={`/${packageInfo?.name}/releases`}
114
114
  className="flex items-center gap-2 text-sm text-gray-500 dark:text-[#8b949e]"
115
115
  >
116
116
  <StatusIcon status={status} />
@@ -19,16 +19,17 @@ import {
19
19
  } from "@/components/ui/table"
20
20
  import { getBuildStatus, StatusIcon } from "."
21
21
  import { formatTimeAgo } from "@/lib/utils/formatTimeAgo"
22
- import { Package } from "fake-snippets-api/lib/db/schema"
22
+ import { Package, PackageBuild } from "fake-snippets-api/lib/db/schema"
23
23
  import { usePackageReleasesByPackageId } from "@/hooks/use-package-release"
24
24
  import { useQueries } from "react-query"
25
25
  import { useAxios } from "@/hooks/use-axios"
26
+ import { useLocation } from "wouter"
26
27
 
27
28
  export const BuildsList = ({ pkg }: { pkg: Package }) => {
28
29
  const { data: releases, isLoading: isLoadingReleases } =
29
30
  usePackageReleasesByPackageId(pkg.package_id)
30
31
  const axios = useAxios()
31
-
32
+ const [, setLocation] = useLocation()
32
33
  // Get the latest build for each release to show status
33
34
  const latestBuildQueries = useQueries(
34
35
  (releases || [])
@@ -52,7 +53,8 @@ export const BuildsList = ({ pkg }: { pkg: Package }) => {
52
53
  isLoadingReleases || latestBuildQueries.some((q) => q.isLoading)
53
54
 
54
55
  // Create a map of release ID to latest build for easy access
55
- const latestBuildsMap = new Map()
56
+ const latestBuildsMap = new Map<string, PackageBuild>()
57
+
56
58
  latestBuildQueries.forEach((query, index) => {
57
59
  if (query.data && releases?.[index]) {
58
60
  latestBuildsMap.set(releases[index].package_release_id, query.data)
@@ -110,15 +112,15 @@ export const BuildsList = ({ pkg }: { pkg: Package }) => {
110
112
  const latestBuild = latestBuildsMap.get(
111
113
  release.package_release_id,
112
114
  )
113
- const { status, label } = latestBuild
114
- ? getBuildStatus(latestBuild)
115
- : { status: "unknown", label: "No builds" }
115
+ const { status, label } = getBuildStatus(latestBuild)
116
116
  return (
117
117
  <TableRow
118
118
  key={release.package_release_id}
119
119
  className="cursor-pointer hover:bg-gray-50 no-scrollbar"
120
120
  onClick={() => {
121
- window.location.href = `/${pkg.name}/release/${release.package_release_id}`
121
+ setLocation(
122
+ `/${pkg.name}/releases/${release.package_release_id}`,
123
+ )
122
124
  }}
123
125
  >
124
126
  <TableCell>
@@ -196,14 +198,23 @@ export const BuildsList = ({ pkg }: { pkg: Package }) => {
196
198
  <DropdownMenuContent align="end">
197
199
  <DropdownMenuItem
198
200
  onClick={() => {
199
- window.location.href = `/${pkg.name}/release/${release.package_release_id}`
201
+ window.location.href = `/${pkg.name}/releases/${release.package_release_id}`
200
202
  }}
201
203
  >
202
204
  View Release
203
205
  </DropdownMenuItem>
206
+ {status !== "error" && (
207
+ <DropdownMenuItem>
208
+ <a
209
+ href={`/${pkg.name}/releases/${latestBuild?.package_release_id}/preview`}
210
+ >
211
+ Preview Release
212
+ </a>
213
+ </DropdownMenuItem>
214
+ )}
204
215
  <DropdownMenuItem
205
216
  onClick={() => {
206
- window.location.href = `/${pkg.name}/release/${release.package_release_id}/builds`
217
+ window.location.href = `/${pkg.name}/releases/${release.package_release_id}/builds`
207
218
  }}
208
219
  >
209
220
  View All Builds
@@ -1,49 +1,63 @@
1
- import { useState } from "react"
2
- import { Card, CardContent, CardHeader } from "@/components/ui/card"
1
+ import { Card } from "@/components/ui/card"
3
2
  import { Badge } from "@/components/ui/badge"
4
3
  import { Button } from "@/components/ui/button"
5
4
  import { GitBranch, Rocket, Github } from "lucide-react"
6
5
  import { cn } from "@/lib/utils"
7
6
  import { PrefetchPageLink } from "../PrefetchPageLink"
8
7
  import { formatTimeAgo } from "@/lib/utils/formatTimeAgo"
9
- import {
10
- getBuildStatus,
11
- getLatestBuildForPackage,
12
- MOCK_PACKAGE_BUILDS,
13
- StatusIcon,
14
- } from "."
15
- import { Package, PackageBuild } from "fake-snippets-api/lib/db/schema"
8
+ import { getBuildStatus, StatusIcon } from "."
9
+ import { Package } from "fake-snippets-api/lib/db/schema"
16
10
  import { usePackageBuild } from "@/hooks/use-package-builds"
17
- import { useLatestPackageRelease } from "@/hooks/use-package-release"
11
+ import {
12
+ useLatestPackageRelease,
13
+ usePackageReleaseById,
14
+ } from "@/hooks/use-package-release"
18
15
 
19
16
  export const ConnectedPackageCardSkeleton = () => {
20
17
  return (
21
- <Card className="animate-pulse">
22
- <CardHeader className="pb-3">
23
- <div className="flex items-start justify-between">
24
- <div className="flex items-center gap-2 flex-1">
25
- <div className="w-4 h-4 bg-gray-200 rounded" />
26
- <div className="space-y-2 flex-1">
27
- <div className="flex items-center gap-2">
28
- <div className="w-12 h-4 bg-gray-200 rounded" />
29
- <div className="w-16 h-3 bg-gray-200 rounded" />
30
- </div>
31
- <div className="w-20 h-3 bg-gray-200 rounded" />
32
- </div>
33
- </div>
18
+ <Card
19
+ className={cn(
20
+ "group relative overflow-hidden",
21
+ "border border-gray-200",
22
+ "hover:border-gray-300",
23
+ "bg-white shadow-none",
24
+ "p-6",
25
+ "flex flex-col",
26
+ "min-h-[200px]",
27
+ "animate-pulse",
28
+ )}
29
+ >
30
+ <div className="flex items-start justify-between mb-4">
31
+ <div className="flex items-center gap-3">
32
+ <div className="h-6 w-32 bg-gray-200 rounded" />
34
33
  </div>
35
- </CardHeader>
36
- <CardContent className="pt-0 space-y-3">
37
- <div className="w-full h-4 bg-gray-200 rounded" />
38
- <div className="flex gap-2">
39
- <div className="w-16 h-3 bg-gray-200 rounded" />
40
- <div className="w-20 h-3 bg-gray-200 rounded" />
34
+
35
+ <div className="flex items-center justify-center gap-2">
36
+ <div className="w-16 h-5 bg-gray-200 rounded-full" />
37
+ <div className="w-4 h-4 bg-gray-200 rounded-full" />
41
38
  </div>
42
- <div className="flex gap-2 pt-2">
43
- <div className="flex-1 h-8 bg-gray-200 rounded" />
44
- <div className="flex-1 h-8 bg-gray-200 rounded" />
39
+ </div>
40
+
41
+ <div className="flex items-center gap-2 mb-4">
42
+ <div className="w-4 h-4 bg-gray-200 rounded" />
43
+ <div className="w-48 h-5 bg-gray-200 rounded" />
44
+ </div>
45
+
46
+ <div className="mb-6 flex-1">
47
+ <div className="h-5 w-3/4 bg-gray-200 rounded mb-2" />
48
+ <div className="flex items-center gap-2">
49
+ <div className="w-32 h-4 bg-gray-200 rounded" />
50
+ <div className="flex items-center gap-1">
51
+ <div className="w-3 h-3 bg-gray-200 rounded" />
52
+ <div className="w-16 h-4 bg-gray-200 rounded-full" />
53
+ </div>
45
54
  </div>
46
- </CardContent>
55
+ </div>
56
+
57
+ <div className="flex gap-2 w-full mt-auto">
58
+ <div className="w-full h-9 bg-gray-200 rounded" />
59
+ <div className="w-full h-9 bg-gray-200 rounded" />
60
+ </div>
47
61
  </Card>
48
62
  )
49
63
  }
@@ -75,6 +89,10 @@ export const ConnectedPackageCard = ({
75
89
  pkg.package_id,
76
90
  )
77
91
 
92
+ const { data: packageRelease } = usePackageReleaseById(
93
+ latestBuildInfo?.package_release_id,
94
+ )
95
+
78
96
  if (isLoading && !latestBuildInfo) {
79
97
  return <ConnectedPackageCardSkeleton />
80
98
  }
@@ -89,7 +107,7 @@ export const ConnectedPackageCard = ({
89
107
  "group relative overflow-hidden",
90
108
  "border border-gray-200",
91
109
  "hover:border-gray-300",
92
- "bg-white",
110
+ "bg-white shadow-none",
93
111
  "p-6",
94
112
  "flex flex-col",
95
113
  "min-h-[200px]",
@@ -135,46 +153,41 @@ export const ConnectedPackageCard = ({
135
153
  </a>
136
154
  </div>
137
155
 
138
- {latestBuildInfo?.commit_message && (
139
- <div className="mb-6 flex-1">
156
+ <div className="mb-6 flex-1">
157
+ {packageRelease?.commit_message && (
140
158
  <h4
141
- title={latestBuildInfo.commit_message}
159
+ title={packageRelease.commit_message}
142
160
  className="text-sm font-medium truncate text-gray-900 mb-2"
143
161
  >
144
- {latestBuildInfo.commit_message}
162
+ {packageRelease.commit_message}
145
163
  </h4>
146
- <div className="flex items-center gap-2 text-xs text-gray-500">
147
- <span>{formatTimeAgo(latestBuildInfo.created_at)} on</span>
148
- <div className="flex items-center gap-1">
149
- <GitBranch className="w-3 h-3" />
150
- <span className="bg-blue-100 text-blue-800 px-2 py-0.5 rounded-full font-medium">
151
- {latestBuildInfo.branch_name || "main"}
152
- </span>
153
- </div>
164
+ )}
165
+ <div className="flex items-center gap-2 text-xs text-gray-500">
166
+ <span>{formatTimeAgo(String(latestBuildInfo?.created_at))} on</span>
167
+ <div className="flex items-center gap-1">
168
+ <GitBranch className="w-3 h-3" />
169
+ <span className="bg-blue-100 text-blue-800 px-2 py-0.5 rounded-full font-medium">
170
+ {packageRelease?.branch_name || "main"}
171
+ </span>
154
172
  </div>
155
173
  </div>
156
- )}
174
+ </div>
157
175
 
158
176
  <div className="flex gap-2 w-full mt-auto">
159
- {latestBuildInfo?.package_build_id && (
160
- <PrefetchPageLink
161
- className="w-full"
162
- href={`/build/${latestBuildInfo.package_build_id}`}
177
+ <PrefetchPageLink className="w-full" href={`/${pkg.name}/releases`}>
178
+ <Button
179
+ size="sm"
180
+ className="bg-blue-600 w-full hover:bg-blue-700 text-white px-4 py-2"
163
181
  >
164
- <Button
165
- size="sm"
166
- className="bg-blue-600 w-full hover:bg-blue-700 text-white px-4 py-2"
167
- >
168
- View
169
- </Button>
170
- </PrefetchPageLink>
171
- )}
182
+ View
183
+ </Button>
184
+ </PrefetchPageLink>
172
185
  {latestBuildInfo?.preview_url &&
173
186
  latestBuildInfo?.package_build_id &&
174
187
  status === "success" && (
175
188
  <PrefetchPageLink
176
189
  className="w-full"
177
- href={`/build/${latestBuildInfo.package_build_id}/preview`}
190
+ href={`/${pkg.name}/releases/${latestBuildInfo.package_release_id}/preview`}
178
191
  >
179
192
  <Button size="sm" variant="outline" className="px-4 py-2 w-full">
180
193
  Preview