@tscircuit/fake-snippets 0.0.72 → 0.0.74

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 (40) hide show
  1. package/bun.lock +33 -17
  2. package/dist/bundle.js +296 -200
  3. package/dist/index.d.ts +87 -4
  4. package/dist/index.js +71 -8
  5. package/dist/schema.d.ts +120 -0
  6. package/dist/schema.js +19 -1
  7. package/fake-snippets-api/lib/db/db-client.ts +58 -7
  8. package/fake-snippets-api/lib/db/schema.ts +26 -0
  9. package/fake-snippets-api/lib/package_file/generate-fs-sha.ts +20 -0
  10. package/fake-snippets-api/lib/public-mapping/public-map-package.ts +1 -0
  11. package/fake-snippets-api/routes/api/package_files/create.ts +3 -0
  12. package/fake-snippets-api/routes/api/package_files/create_or_update.ts +6 -0
  13. package/fake-snippets-api/routes/api/package_files/delete.ts +3 -0
  14. package/fake-snippets-api/routes/api/package_releases/rebuild.ts +32 -0
  15. package/fake-snippets-api/routes/api/packages/create.ts +1 -0
  16. package/package.json +7 -7
  17. package/src/App.tsx +5 -0
  18. package/src/components/JLCPCBImportDialog.tsx +1 -1
  19. package/src/components/PackageBuildsPage/PackageBuildDetailsPage.tsx +56 -0
  20. package/src/components/PackageBuildsPage/build-preview-content.tsx +11 -0
  21. package/src/components/PackageBuildsPage/collapsible-section.tsx +70 -0
  22. package/src/components/PackageBuildsPage/package-build-details-panel.tsx +84 -0
  23. package/src/components/PackageBuildsPage/package-build-header.tsx +75 -0
  24. package/src/components/PackageCard.tsx +1 -10
  25. package/src/components/TrendingPackagesCarousel.tsx +5 -16
  26. package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +1 -1
  27. package/src/components/ViewPackagePage/components/preview-image-squares.tsx +2 -6
  28. package/src/components/package-port/CodeAndPreview.tsx +3 -2
  29. package/src/components/package-port/CodeEditor.tsx +5 -3
  30. package/src/components/package-port/EditorNav.tsx +17 -13
  31. package/src/hooks/use-create-package-mutation.ts +0 -7
  32. package/src/hooks/use-current-package-id.ts +2 -8
  33. package/src/hooks/use-current-package-release.ts +28 -0
  34. package/src/hooks/use-preview-images.ts +3 -15
  35. package/src/hooks/useFileManagement.ts +26 -21
  36. package/src/lib/utils/checkIfManualEditsImported.ts +9 -0
  37. package/src/pages/editor.tsx +2 -10
  38. package/src/pages/package-builds.tsx +33 -0
  39. package/src/pages/package-editor.tsx +2 -14
  40. package/src/hooks/use-get-fsmap-hash-for-package.ts +0 -19
@@ -15,6 +15,7 @@ import {
15
15
  type Package,
16
16
  type PackageFile,
17
17
  type PackageRelease,
18
+ packageReleaseSchema,
18
19
  type Session,
19
20
  type Snippet,
20
21
  databaseSchema,
@@ -22,6 +23,7 @@ import {
22
23
  type snippetSchema,
23
24
  } from "./schema.ts"
24
25
  import { seed as seedFn } from "./seed"
26
+ import { generateFsSha } from "../package_file/generate-fs-sha"
25
27
 
26
28
  export const createDatabase = ({ seed }: { seed?: boolean } = {}) => {
27
29
  const db = hoist(createStore(initializer))
@@ -271,10 +273,11 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({
271
273
  is_public: true,
272
274
  is_unlisted: false,
273
275
  latest_package_release_id: `package_release_${nextId}`,
276
+ latest_package_release_fs_sha: null,
274
277
  }
275
278
 
276
279
  // Create package release
277
- const newPackageRelease = {
280
+ const newPackageRelease = packageReleaseSchema.parse({
278
281
  package_release_id: `package_release_${nextId}`,
279
282
  package_id: newPackage.package_id,
280
283
  version: "0.0.1",
@@ -284,7 +287,7 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({
284
287
  updated_at: currentTime,
285
288
  has_transpiled: true,
286
289
  transpilation_error: null,
287
- }
290
+ })
288
291
 
289
292
  // Add all the files
290
293
  const packageFiles: PackageFile[] = []
@@ -341,6 +344,27 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({
341
344
  idCounter: fileIdCounter,
342
345
  }))
343
346
 
347
+ // Update fs_sha for the new package release
348
+ const dbState = get()
349
+ const releaseFiles = dbState.packageFiles.filter(
350
+ (pf) => pf.package_release_id === newPackageRelease.package_release_id,
351
+ )
352
+ const fsSha = generateFsSha(releaseFiles)
353
+
354
+ set((state) => ({
355
+ ...state,
356
+ packageReleases: state.packageReleases.map((pr) =>
357
+ pr.package_release_id === newPackageRelease.package_release_id
358
+ ? { ...pr, fs_sha: fsSha }
359
+ : pr,
360
+ ),
361
+ packages: state.packages.map((pkg) =>
362
+ pkg.latest_package_release_id === newPackageRelease.package_release_id
363
+ ? { ...pkg, latest_package_release_fs_sha: fsSha }
364
+ : pkg,
365
+ ),
366
+ }))
367
+
344
368
  // Return in the same format as create endpoint
345
369
  return {
346
370
  snippet_id: newPackage.package_id,
@@ -1215,16 +1239,19 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({
1215
1239
  )
1216
1240
  },
1217
1241
  addPackageRelease: (
1218
- packageRelease: Omit<PackageRelease, "package_release_id">,
1242
+ packageRelease: Omit<
1243
+ z.input<typeof packageReleaseSchema>,
1244
+ "package_release_id"
1245
+ >,
1219
1246
  ): PackageRelease => {
1220
- const newPackageRelease = {
1247
+ const parsed = packageReleaseSchema.parse({
1221
1248
  package_release_id: `package_release_${Date.now()}`,
1222
1249
  ...packageRelease,
1223
- }
1250
+ })
1224
1251
  set((state) => ({
1225
- packageReleases: [...state.packageReleases, newPackageRelease],
1252
+ packageReleases: [...state.packageReleases, parsed],
1226
1253
  }))
1227
- return newPackageRelease
1254
+ return parsed
1228
1255
  },
1229
1256
  updatePackageRelease: (packageRelease: PackageRelease): void => {
1230
1257
  set((state) => ({
@@ -1287,4 +1314,28 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({
1287
1314
  (pf) => pf.package_release_id === packageReleaseId,
1288
1315
  )
1289
1316
  },
1317
+ /**
1318
+ * Update fs_sha for a package release based on its files
1319
+ */
1320
+ updatePackageReleaseFsSha: (packageReleaseId: string): void => {
1321
+ const state = get()
1322
+ const packageFiles = state.packageFiles.filter(
1323
+ (pf) => pf.package_release_id === packageReleaseId,
1324
+ )
1325
+ const fsSha = generateFsSha(packageFiles)
1326
+
1327
+ set((currentState) => ({
1328
+ ...currentState,
1329
+ packageReleases: currentState.packageReleases.map((pr) =>
1330
+ pr.package_release_id === packageReleaseId
1331
+ ? { ...pr, fs_sha: fsSha }
1332
+ : pr,
1333
+ ),
1334
+ packages: currentState.packages.map((pkg) =>
1335
+ pkg.latest_package_release_id === packageReleaseId
1336
+ ? { ...pkg, latest_package_release_fs_sha: fsSha }
1337
+ : pkg,
1338
+ ),
1339
+ }))
1340
+ },
1290
1341
  }))
@@ -176,6 +176,31 @@ export const packageReleaseSchema = z.object({
176
176
  has_transpiled: z.boolean().default(false),
177
177
  transpilation_error: z.string().nullable().optional(),
178
178
  fs_sha: z.string().nullable().optional(),
179
+ // Build Status and Display
180
+ display_status: z
181
+ .enum(["pending", "building", "successful", "failed"])
182
+ .default("pending"),
183
+ total_build_duration_ms: z.number().nullable().optional(),
184
+
185
+ // Transpilation Process
186
+ transpilation_display_status: z
187
+ .enum(["pending", "building", "successful", "failed"])
188
+ .default("pending"),
189
+ transpilation_in_progress: z.boolean().default(false),
190
+ transpilation_started_at: z.string().datetime().nullable().optional(),
191
+ transpilation_completed_at: z.string().datetime().nullable().optional(),
192
+ transpilation_logs: z.array(z.any()).default([]),
193
+ transpilation_is_stale: z.boolean().default(false),
194
+
195
+ // Circuit JSON Build Process
196
+ circuit_json_build_display_status: z
197
+ .enum(["pending", "building", "successful", "failed"])
198
+ .default("pending"),
199
+ circuit_json_build_in_progress: z.boolean().default(false),
200
+ circuit_json_build_started_at: z.string().datetime().nullable().optional(),
201
+ circuit_json_build_completed_at: z.string().datetime().nullable().optional(),
202
+ circuit_json_build_logs: z.array(z.any()).default([]),
203
+ circuit_json_build_is_stale: z.boolean().default(false),
179
204
  })
180
205
  export type PackageRelease = z.infer<typeof packageReleaseSchema>
181
206
 
@@ -219,6 +244,7 @@ export const packageSchema = z.object({
219
244
  ai_description: z.string().nullable(),
220
245
  latest_license: z.string().nullable().optional(),
221
246
  ai_usage_instructions: z.string().nullable(),
247
+ latest_package_release_fs_sha: z.string().nullable().default(null),
222
248
  default_view: z
223
249
  .enum(["files", "3d", "pcb", "schematic"])
224
250
  .default("files")
@@ -0,0 +1,20 @@
1
+ import md5 from "md5"
2
+ import type { PackageFile } from "../db/schema"
3
+
4
+ /**
5
+ * Generate an MD5 hash from package files content
6
+ * Uses the same format as existing tests: creates a map of file_path -> content_text
7
+ * and generates an MD5 hash of the JSON stringified map
8
+ */
9
+ export function generateFsSha(packageFiles: PackageFile[]): string {
10
+ const fsMap: Record<string, string> = {}
11
+
12
+ packageFiles
13
+ .filter((file) => file.content_text) // Only include files with content
14
+ .forEach((file) => {
15
+ fsMap[file.file_path] = file.content_text || ""
16
+ })
17
+
18
+ const hash = md5(JSON.stringify(fsMap))
19
+ return `md5-${hash}`
20
+ }
@@ -25,6 +25,7 @@ export const publicMapPackage = (internalPackage: {
25
25
  is_footprint: boolean
26
26
  is_private: boolean | null
27
27
  is_unlisted: boolean | null
28
+ latest_package_release_fs_sha: string | null
28
29
  }): zt.Package => {
29
30
  return {
30
31
  ...internalPackage,
@@ -125,6 +125,9 @@ export default withRouteSpec(routeSpec)(async (req, ctx) => {
125
125
  // Add to the test database
126
126
  ctx.db.addPackageFile(newPackageFile)
127
127
 
128
+ // Update fs_sha for the package release
129
+ ctx.db.updatePackageReleaseFsSha(packageReleaseId)
130
+
128
131
  return ctx.json({
129
132
  ok: true,
130
133
  package_file: newPackageFile,
@@ -146,6 +146,9 @@ export default withRouteSpec(routeSpec)(async (req, ctx) => {
146
146
  },
147
147
  )
148
148
 
149
+ // Update fs_sha for the package release
150
+ ctx.db.updatePackageReleaseFsSha(packageReleaseId)
151
+
149
152
  return ctx.json({
150
153
  ok: true,
151
154
  package_file,
@@ -181,6 +184,9 @@ export default withRouteSpec(routeSpec)(async (req, ctx) => {
181
184
  // Add to the test database
182
185
  ctx.db.addPackageFile(newPackageFile)
183
186
 
187
+ // Update fs_sha for the package release
188
+ ctx.db.updatePackageReleaseFsSha(packageReleaseId)
189
+
184
190
  return ctx.json({
185
191
  ok: true,
186
192
  package_file: newPackageFile,
@@ -100,6 +100,9 @@ export default withRouteSpec({
100
100
  })
101
101
  }
102
102
 
103
+ // Update fs_sha for the package release
104
+ ctx.db.updatePackageReleaseFsSha(packageReleaseId)
105
+
103
106
  return ctx.json({
104
107
  ok: true,
105
108
  })
@@ -0,0 +1,32 @@
1
+ import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
2
+ import { packageReleaseSchema } from "fake-snippets-api/lib/db/schema"
3
+ import { z } from "zod"
4
+
5
+ export default withRouteSpec({
6
+ methods: ["POST"],
7
+ auth: "session",
8
+ jsonBody: z.object({
9
+ package_release_id: z.string(),
10
+ }),
11
+ jsonResponse: z.object({
12
+ ok: z.boolean(),
13
+ package_release: packageReleaseSchema,
14
+ }),
15
+ })(async (req, ctx) => {
16
+ const { package_release_id } = req.jsonBody
17
+
18
+ const release = ctx.db.getPackageReleaseById(package_release_id)
19
+
20
+ if (!release) {
21
+ return ctx.error(404, {
22
+ error_code: "package_release_not_found",
23
+ message: "Package release not found",
24
+ })
25
+ }
26
+
27
+ // In a real API this would trigger a rebuild. Here we simply return the release.
28
+ return ctx.json({
29
+ ok: true,
30
+ package_release: release,
31
+ })
32
+ })
@@ -42,6 +42,7 @@ export default withRouteSpec({
42
42
  owner_org_id: ctx.auth.personal_org_id,
43
43
  owner_github_username: ctx.auth.github_username,
44
44
  latest_package_release_id: null,
45
+ latest_package_release_fs_sha: null,
45
46
  latest_version: null,
46
47
  license: null,
47
48
  website: null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/fake-snippets",
3
- "version": "0.0.72",
3
+ "version": "0.0.74",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -26,7 +26,7 @@
26
26
  "lint": "biome format .",
27
27
  "build:vite:analyze": "VITE_BUNDLE_ANALYZE=true vite build",
28
28
  "build:fake-api:tsup": "tsup-node ./fake-snippets-api/lib/index.ts --format esm --dts",
29
- "build:fake-api:bundle": "winterspec bundle -o dist/bundle.js",
29
+ "build:fake-api:bundle": "winterspec2 bundle -o dist/bundle.js",
30
30
  "build:fake-api": "bun run build:fake-api:tsup && bun run build:fake-api:bundle && bun run build:fake-api:schema",
31
31
  "build:fake-api:schema": "tsup-node ./fake-snippets-api/lib/db/schema.ts --format esm --dts",
32
32
  "generate-images": "bun run scripts/generate-image-sizes.ts",
@@ -127,7 +127,7 @@
127
127
  "recharts": "^2.12.7",
128
128
  "remark-gfm": "^4.0.1",
129
129
  "rollup-plugin-visualizer": "^5.12.0",
130
- "schematic-symbols": "^0.0.132",
130
+ "schematic-symbols": "^0.0.140",
131
131
  "sitemap": "^8.0.0",
132
132
  "sonner": "^1.5.0",
133
133
  "states-us": "^1.1.1",
@@ -145,7 +145,7 @@
145
145
  "@biomejs/biome": "^1.9.2",
146
146
  "@playwright/test": "^1.48.0",
147
147
  "@tailwindcss/typography": "^0.5.16",
148
- "@tscircuit/core": "^0.0.428",
148
+ "@tscircuit/core": "^0.0.433",
149
149
  "@tscircuit/prompt-benchmarks": "^0.0.28",
150
150
  "@tscircuit/runframe": "^0.0.507",
151
151
  "@types/babel__standalone": "^7.1.7",
@@ -161,7 +161,7 @@
161
161
  "@typescript/vfs": "^1.6.0",
162
162
  "@vitejs/plugin-react": "^4.3.1",
163
163
  "autoprefixer": "^10.4.20",
164
- "circuit-to-svg": "^0.0.131",
164
+ "circuit-to-svg": "^0.0.136",
165
165
  "get-port": "^7.1.0",
166
166
  "globals": "^15.9.0",
167
167
  "he": "^1.2.0",
@@ -175,11 +175,11 @@
175
175
  "shiki": "^3.2.1",
176
176
  "tailwindcss": "^3.4.13",
177
177
  "terser": "^5.27.0",
178
- "tsup": "^8.3.5",
178
+ "tsup": "^8.5.0",
179
179
  "typescript": "^5.6.3",
180
180
  "vite": "^6.3.4",
181
181
  "vite-plugin-image-optimizer": "^1.1.8",
182
- "winterspec": "^0.0.94",
182
+ "winterspec": "^0.0.107",
183
183
  "zod": "^3.23.8",
184
184
  "zustand": "^4.5.5",
185
185
  "zustand-hoist": "^2.0.1"
package/src/App.tsx CHANGED
@@ -68,6 +68,7 @@ const UserProfilePage = lazyImport(() => import("@/pages/user-profile"))
68
68
  const DevLoginPage = lazyImport(() => import("@/pages/dev-login"))
69
69
  const BetaPage = lazyImport(() => import("@/pages/beta"))
70
70
  const ViewPackagePage = lazyImport(() => import("@/pages/view-package"))
71
+ const PackageBuildsPage = lazyImport(() => import("@/pages/package-builds"))
71
72
  const TrendingPage = lazyImport(() => import("@/pages/trending"))
72
73
  const PackageEditorPage = lazyImport(async () => {
73
74
  const [editorModule] = await Promise.all([
@@ -124,6 +125,10 @@ function App() {
124
125
  <Route path="/dev-login" component={DevLoginPage} />
125
126
  <Route path="/:username" component={UserProfilePage} />
126
127
  <Route path="/:author/:packageName" component={ViewPackagePage} />
128
+ <Route
129
+ path="/:author/:packageName/builds"
130
+ component={PackageBuildsPage}
131
+ />
127
132
  <Route component={lazyImport(() => import("@/pages/404"))} />
128
133
  </Switch>
129
134
  </Suspense>
@@ -50,7 +50,7 @@ export function JLCPCBImportDialog({
50
50
  setHasBeenImportedToAccountAlready(false)
51
51
 
52
52
  try {
53
- const apiUrl = `/snippets/get?owner_name=${session?.github_username}&unscoped_name=${partNumber}`
53
+ const apiUrl = `/packages/get?name=${session?.github_username}/${partNumber}`
54
54
 
55
55
  const existingPackageRes = await axios.get(apiUrl, {
56
56
  validateStatus: (status) => true,
@@ -0,0 +1,56 @@
1
+ "use client"
2
+
3
+ import { useState } from "react"
4
+ import { BuildPreviewContent } from "./build-preview-content"
5
+ import { PackageBuildDetailsPanel } from "./package-build-details-panel"
6
+ import { PackageBuildHeader } from "./package-build-header"
7
+ import { CollapsibleSection } from "./collapsible-section"
8
+
9
+ export const PackageBuildDetailsPage = () => {
10
+ const [openSections, setOpenSections] = useState<Record<string, boolean>>({})
11
+
12
+ const toggleSection = (section: string) => {
13
+ setOpenSections((prev) => ({
14
+ ...prev,
15
+ [section]: !prev[section],
16
+ }))
17
+ }
18
+
19
+ return (
20
+ <div className="min-h-screen bg-gray-50 text-gray-900">
21
+ <PackageBuildHeader />
22
+
23
+ <div className="px-6 py-6 container mx-auto">
24
+ {/* Main Content */}
25
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8 items-start">
26
+ {/* Preview Section */}
27
+ <div className="lg:col-span-2">
28
+ <div className="bg-white border border-gray-200 rounded-lg p-4 flex items-center justify-center max-h-[420px]">
29
+ <BuildPreviewContent />
30
+ </div>
31
+ </div>
32
+
33
+ {/* Details Panel */}
34
+ <PackageBuildDetailsPanel />
35
+ </div>
36
+
37
+ {/* Collapsible Sections */}
38
+ <div className="space-y-4 mb-8">
39
+ <CollapsibleSection
40
+ title="Transpilation Logs"
41
+ duration="1m 15s"
42
+ isOpen={openSections.summary}
43
+ onToggle={() => toggleSection("summary")}
44
+ />
45
+
46
+ <CollapsibleSection
47
+ title="Circuit JSON Build Logs"
48
+ duration="2m 29s"
49
+ isOpen={openSections.logs}
50
+ onToggle={() => toggleSection("logs")}
51
+ />
52
+ </div>
53
+ </div>
54
+ </div>
55
+ )
56
+ }
@@ -0,0 +1,11 @@
1
+ export function BuildPreviewContent() {
2
+ return (
3
+ <div className="flex items-center justify-center">
4
+ <img
5
+ src="https://placehold.co/600x400/000/31343C"
6
+ alt="Package build preview"
7
+ className="object-contain rounded p-2 max-h-[400px]"
8
+ />
9
+ </div>
10
+ )
11
+ }
@@ -0,0 +1,70 @@
1
+ import type React from "react"
2
+ import { ChevronRight, CheckCircle2 } from "lucide-react"
3
+ import { Badge } from "@/components/ui/badge"
4
+ import {
5
+ Collapsible,
6
+ CollapsibleContent,
7
+ CollapsibleTrigger,
8
+ } from "@/components/ui/collapsible"
9
+
10
+ interface CollapsibleSectionProps {
11
+ title: string
12
+ duration?: string
13
+ isOpen: boolean
14
+ onToggle: () => void
15
+ badges?: Array<{
16
+ text: string
17
+ icon?: React.ReactNode
18
+ variant?: "default" | "secondary"
19
+ className?: string
20
+ }>
21
+ children?: React.ReactNode
22
+ }
23
+
24
+ export function CollapsibleSection({
25
+ title,
26
+ duration,
27
+ isOpen,
28
+ onToggle,
29
+ badges = [],
30
+ children,
31
+ }: CollapsibleSectionProps) {
32
+ return (
33
+ <Collapsible open={isOpen} onOpenChange={onToggle}>
34
+ <CollapsibleTrigger asChild>
35
+ <div className="flex items-center justify-between p-4 bg-white border border-gray-200 rounded-lg cursor-pointer hover:bg-gray-100">
36
+ <div className="flex items-center gap-2">
37
+ <ChevronRight
38
+ className={`w-4 h-4 transition-transform ${isOpen ? "rotate-90" : ""}`}
39
+ />
40
+ <span className="font-medium">{title}</span>
41
+ </div>
42
+ <div className="flex items-center gap-2">
43
+ {badges.map((badge, index) => (
44
+ <Badge
45
+ key={index}
46
+ variant={badge.variant || "secondary"}
47
+ className={
48
+ badge.className ||
49
+ "bg-gray-200 text-gray-700 flex items-center gap-1"
50
+ }
51
+ >
52
+ {badge.icon}
53
+ {badge.text}
54
+ </Badge>
55
+ ))}
56
+ {duration && (
57
+ <span className="text-sm text-gray-600">{duration}</span>
58
+ )}
59
+ <CheckCircle2 className="w-4 h-4 text-green-500" />
60
+ </div>
61
+ </div>
62
+ </CollapsibleTrigger>
63
+ <CollapsibleContent>
64
+ <div className="p-4 bg-white border-x border-b border-gray-200 rounded-b-lg">
65
+ {children || `${title} details would go here...`}
66
+ </div>
67
+ </CollapsibleContent>
68
+ </Collapsible>
69
+ )
70
+ }
@@ -0,0 +1,84 @@
1
+ import { Globe, GitBranch, GitCommit, Clock } from "lucide-react"
2
+ import { Badge } from "@/components/ui/badge"
3
+
4
+ export function PackageBuildDetailsPanel() {
5
+ return (
6
+ <div className="space-y-6 bg-white p-4 border border-gray-200 rounded-lg">
7
+ {/* Created */}
8
+ <div>
9
+ <h3 className="text-sm font-medium text-gray-600 mb-2">Created</h3>
10
+ <div className="flex items-center gap-2">
11
+ <div className="w-6 h-6 bg-orange-500 rounded-full flex items-center justify-center text-xs font-bold">
12
+ I
13
+ </div>
14
+ <span className="text-sm">imrishabh18</span>
15
+ <span className="text-sm text-gray-500">48m ago</span>
16
+ </div>
17
+ </div>
18
+
19
+ {/* Status */}
20
+ <div>
21
+ <h3 className="text-sm font-medium text-gray-600 mb-2">Status</h3>
22
+ <div className="flex items-center gap-2">
23
+ <div className="w-2 h-2 bg-green-500 rounded-full"></div>
24
+ <span className="text-sm">Ready</span>
25
+ <Badge
26
+ variant="secondary"
27
+ className="bg-gray-200 text-gray-700 text-xs"
28
+ >
29
+ Latest
30
+ </Badge>
31
+ </div>
32
+ </div>
33
+
34
+ {/* Time to Ready */}
35
+ <div>
36
+ <h3 className="text-sm font-medium text-gray-600 mb-2">
37
+ Time to Ready
38
+ </h3>
39
+ <div className="flex items-center gap-2">
40
+ <Clock className="w-4 h-4 text-gray-500" />
41
+ <span className="text-sm">1m 3s</span>
42
+ <span className="text-sm text-gray-500">47m ago</span>
43
+ </div>
44
+ </div>
45
+
46
+ {/* Version */}
47
+ <div>
48
+ <h3 className="text-sm font-medium text-gray-600 mb-2">Version</h3>
49
+ <div className="flex items-center gap-2">
50
+ <Globe className="w-4 h-4 text-gray-500" />
51
+ <span className="text-sm">v1.0.3</span>
52
+ <Badge variant="default" className="bg-blue-600 text-white text-xs">
53
+ Current
54
+ </Badge>
55
+ </div>
56
+ </div>
57
+
58
+ {/* Outputs */}
59
+ <div>
60
+ <h3 className="text-sm font-medium text-gray-600 mb-2">Outputs</h3>
61
+ <div>
62
+ <span className="text-sm text-gray-400">None</span>
63
+ </div>
64
+ </div>
65
+
66
+ {/* Source */}
67
+ <div>
68
+ <h3 className="text-sm font-medium text-gray-600 mb-2">Source</h3>
69
+ <div className="space-y-2">
70
+ <div className="flex items-center gap-2">
71
+ <GitBranch className="w-4 h-4 text-gray-500" />
72
+ <span className="text-sm">main</span>
73
+ </div>
74
+ <div className="flex items-center gap-2">
75
+ <GitCommit className="w-4 h-4 text-gray-500" />
76
+ <span className="text-sm text-gray-500">
77
+ edfdc67 support empty file creation (#356)
78
+ </span>
79
+ </div>
80
+ </div>
81
+ </div>
82
+ </div>
83
+ )
84
+ }
@@ -0,0 +1,75 @@
1
+ import { ChevronDown, Download, Github, Link } from "lucide-react"
2
+ import { Button } from "@/components/ui/button"
3
+ import {
4
+ DropdownMenu,
5
+ DropdownMenuContent,
6
+ DropdownMenuItem,
7
+ DropdownMenuTrigger,
8
+ } from "@/components/ui/dropdown-menu"
9
+ import { useParams } from "wouter"
10
+
11
+ export function PackageBuildHeader() {
12
+ const { author, packageName } = useParams()
13
+
14
+ return (
15
+ <div className="border-b border-gray-200 bg-white px-6 py-4">
16
+ <div className="flex items-center justify-between container mx-auto">
17
+ <h1 className="text-2xl font-semibold">
18
+ Package Build
19
+ <a
20
+ className="ml-2 bg-gray-100 px-2 py-1 rounded font-mono text-blue-600"
21
+ href={`/${author}/${packageName}`}
22
+ >
23
+ {author}/{packageName}
24
+ </a>
25
+ </h1>
26
+ <div className="flex items-center gap-3">
27
+ <Button
28
+ variant="outline"
29
+ size="sm"
30
+ className="border-gray-300 bg-white hover:bg-gray-50 text-xs sm:text-sm"
31
+ asChild
32
+ >
33
+ <a
34
+ href="https://github.com/tscircuit/tscircuit.com/issues/new"
35
+ target="_blank"
36
+ rel="noopener noreferrer"
37
+ >
38
+ <Github className="w-3 h-3 sm:w-4 sm:h-4 mr-1 sm:mr-2" />
39
+ Report Issue
40
+ </a>
41
+ </Button>
42
+ <DropdownMenu>
43
+ <DropdownMenuTrigger asChild>
44
+ <Button
45
+ size="sm"
46
+ className="bg-gray-900 text-white hover:bg-gray-800"
47
+ >
48
+ <Download className="w-4 h-4 mr-2" />
49
+ Download
50
+ <ChevronDown className="w-4 h-4 ml-2" />
51
+ </Button>
52
+ </DropdownMenuTrigger>
53
+ <DropdownMenuContent
54
+ align="end"
55
+ className="bg-white border-gray-200"
56
+ >
57
+ <DropdownMenuItem className="text-gray-900 hover:bg-gray-100">
58
+ Circuit JSON
59
+ </DropdownMenuItem>
60
+ <DropdownMenuItem className="text-gray-900 hover:bg-gray-100">
61
+ PCB SVG
62
+ </DropdownMenuItem>
63
+ <DropdownMenuItem className="text-gray-900 hover:bg-gray-100">
64
+ Schematic SVG
65
+ </DropdownMenuItem>
66
+ <DropdownMenuItem className="text-gray-900 hover:bg-gray-100">
67
+ 3D Model (stl)
68
+ </DropdownMenuItem>
69
+ </DropdownMenuContent>
70
+ </DropdownMenu>
71
+ </div>
72
+ </div>
73
+ </div>
74
+ )
75
+ }