@tscircuit/fake-snippets 0.0.44 → 0.0.46

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/.github/workflows/bun-pver-release.yml +1 -0
  2. package/bun.lock +43 -11
  3. package/dist/bundle.js +405 -335
  4. package/dist/schema.d.ts +1845 -0
  5. package/dist/schema.js +251 -0
  6. package/fake-snippets-api/routes/api/_fake/received_quotes.ts +66 -0
  7. package/fake-snippets-api/routes/api/package_releases/update.ts +25 -18
  8. package/package.json +8 -3
  9. package/src/components/CodeAndPreview.tsx +0 -1
  10. package/src/components/CodeEditor.tsx +0 -1
  11. package/src/components/CodeEditorHeader.tsx +0 -25
  12. package/src/components/EditorNav.tsx +10 -8
  13. package/src/components/ErrorOutline.tsx +35 -0
  14. package/src/components/FileSidebar.tsx +46 -16
  15. package/src/components/NotFound.tsx +37 -0
  16. package/src/components/PreviewContent.tsx +0 -6
  17. package/src/components/TrendingSnippetCarousel.tsx +1 -1
  18. package/src/components/ViewPackagePage/components/package-header.tsx +24 -3
  19. package/src/components/ViewPackagePage/utils/is-hidden-file.ts +0 -1
  20. package/src/components/dialogs/package-visibility-settings-dialog.tsx +10 -1
  21. package/src/components/dialogs/view-ts-files-dialog.tsx +0 -6
  22. package/src/components/package-port/CodeAndPreview.tsx +24 -9
  23. package/src/components/package-port/CodeEditor.tsx +26 -38
  24. package/src/components/package-port/CodeEditorHeader.tsx +117 -39
  25. package/src/components/package-port/EditorNav.tsx +10 -8
  26. package/src/components/ui/tree-view.tsx +5 -1
  27. package/src/{prettier.ts → lib/types.ts} +3 -1
  28. package/src/lib/utils/findTargetFile.ts +62 -0
  29. package/src/lib/utils/load-prettier.ts +3 -0
  30. package/src/pages/404.tsx +2 -33
  31. package/src/pages/package-editor.tsx +14 -3
  32. package/src/pages/user-profile.tsx +66 -27
  33. package/src/components/FootprintDialog.tsx +0 -339
  34. package/src/components/ParametersEditor.tsx +0 -140
  35. package/src/lib/utils/parseFootprintParams.ts +0 -52
package/dist/schema.js ADDED
@@ -0,0 +1,251 @@
1
+ // fake-snippets-api/lib/db/schema.ts
2
+ import { z } from "zod";
3
+ var errorSchema = z.object({
4
+ error_code: z.string(),
5
+ message: z.string()
6
+ }).passthrough();
7
+ var errorResponseSchema = z.object({
8
+ error: errorSchema
9
+ });
10
+ var snippetSchema = z.object({
11
+ snippet_id: z.string(),
12
+ package_release_id: z.string(),
13
+ name: z.string(),
14
+ unscoped_name: z.string(),
15
+ owner_name: z.string(),
16
+ is_starred: z.boolean().default(false),
17
+ code: z.string(),
18
+ dts: z.string().optional(),
19
+ compiled_js: z.string().optional().nullable(),
20
+ circuit_json: z.array(z.record(z.any())).optional().nullable(),
21
+ manual_edits_json_content: z.string().optional().nullable(),
22
+ created_at: z.string(),
23
+ updated_at: z.string(),
24
+ snippet_type: z.enum(["board", "package", "model", "footprint"]),
25
+ description: z.string().optional(),
26
+ version: z.string().default("0.0.1"),
27
+ star_count: z.number().default(0),
28
+ is_private: z.boolean().default(false),
29
+ is_public: z.boolean().default(true),
30
+ is_unlisted: z.boolean().default(false)
31
+ });
32
+ var sessionSchema = z.object({
33
+ session_id: z.string(),
34
+ account_id: z.string(),
35
+ expires_at: z.string(),
36
+ is_cli_session: z.boolean()
37
+ });
38
+ var loginPageSchema = z.object({
39
+ login_page_id: z.string(),
40
+ login_page_auth_token: z.string(),
41
+ was_login_successful: z.boolean(),
42
+ has_been_used_to_create_session: z.boolean(),
43
+ created_at: z.string(),
44
+ expires_at: z.string()
45
+ });
46
+ var shippingInfoSchema = z.object({
47
+ firstName: z.string(),
48
+ lastName: z.string(),
49
+ companyName: z.string().optional(),
50
+ address: z.string(),
51
+ apartment: z.string().optional(),
52
+ city: z.string(),
53
+ state: z.string(),
54
+ zipCode: z.string(),
55
+ country: z.string(),
56
+ phone: z.string()
57
+ });
58
+ var accountSchema = z.object({
59
+ account_id: z.string(),
60
+ github_username: z.string(),
61
+ shippingInfo: shippingInfoSchema.optional()
62
+ });
63
+ var orderSchema = z.object({
64
+ order_id: z.string(),
65
+ account_id: z.string().nullable(),
66
+ is_running: z.boolean(),
67
+ is_started: z.boolean(),
68
+ is_finished: z.boolean(),
69
+ error: errorSchema.nullable(),
70
+ has_error: z.boolean(),
71
+ created_at: z.string(),
72
+ started_at: z.string().nullable(),
73
+ completed_at: z.string().nullable(),
74
+ circuit_json: z.any()
75
+ });
76
+ var orderFileSchema = z.object({
77
+ order_file_id: z.string(),
78
+ order_id: z.string(),
79
+ is_gerbers_zip: z.boolean(),
80
+ content_type: z.string(),
81
+ for_provider: z.string().nullable(),
82
+ uploaded_at: z.string(),
83
+ content_text: z.string().nullable(),
84
+ content_bytes: z.instanceof(Uint8Array).nullable()
85
+ });
86
+ var shippingOptionSchema = z.object({
87
+ carrier: z.string(),
88
+ service: z.string(),
89
+ cost: z.number()
90
+ });
91
+ var quotedComponentSchema = z.object({
92
+ manufacturer_part_number: z.string().nullable(),
93
+ supplier_part_number: z.string().nullable(),
94
+ quantity: z.number().default(0),
95
+ unit_price: z.number().default(0),
96
+ total_price: z.number().default(0),
97
+ available: z.boolean().default(true)
98
+ });
99
+ var orderQuoteSchema = z.object({
100
+ order_quote_id: z.string(),
101
+ account_id: z.string().nullable(),
102
+ package_release_id: z.string().nullable(),
103
+ is_completed: z.boolean().default(false),
104
+ is_processing: z.boolean().default(true),
105
+ vendor_name: z.string(),
106
+ error: errorSchema.nullable(),
107
+ has_error: z.boolean().default(false),
108
+ created_at: z.string(),
109
+ updated_at: z.string(),
110
+ completed_at: z.string().nullable(),
111
+ quoted_components: z.array(quotedComponentSchema).nullable(),
112
+ bare_pcb_cost: z.number().default(0),
113
+ shipping_options: z.array(shippingOptionSchema),
114
+ total_cost: z.number().default(0)
115
+ });
116
+ var accountSnippetSchema = z.object({
117
+ account_id: z.string(),
118
+ snippet_id: z.string(),
119
+ has_starred: z.boolean(),
120
+ created_at: z.string(),
121
+ updated_at: z.string()
122
+ });
123
+ var accountPackageSchema = z.object({
124
+ account_package_id: z.string(),
125
+ account_id: z.string(),
126
+ package_id: z.string(),
127
+ is_starred: z.boolean(),
128
+ created_at: z.string(),
129
+ updated_at: z.string()
130
+ });
131
+ var packageReleaseSchema = z.object({
132
+ package_release_id: z.string(),
133
+ package_id: z.string(),
134
+ version: z.string().nullable(),
135
+ is_locked: z.boolean(),
136
+ is_latest: z.boolean(),
137
+ created_at: z.string().datetime(),
138
+ commit_sha: z.string().nullable().optional(),
139
+ license: z.string().nullable().optional()
140
+ });
141
+ var packageFileSchema = z.object({
142
+ package_file_id: z.string(),
143
+ package_release_id: z.string(),
144
+ file_path: z.string(),
145
+ content_text: z.string().nullable().optional(),
146
+ created_at: z.string().datetime(),
147
+ content_mimetype: z.string().nullable().optional(),
148
+ is_release_tarball: z.boolean().optional(),
149
+ npm_pack_output: z.any().nullable().optional()
150
+ });
151
+ var packageSchema = z.object({
152
+ package_id: z.string(),
153
+ creator_account_id: z.string(),
154
+ owner_org_id: z.string(),
155
+ owner_github_username: z.string().nullable(),
156
+ name: z.string(),
157
+ unscoped_name: z.string(),
158
+ description: z.string().nullable(),
159
+ created_at: z.string().datetime(),
160
+ updated_at: z.string().datetime(),
161
+ is_snippet: z.boolean().default(false),
162
+ is_board: z.boolean().default(false),
163
+ is_package: z.boolean().default(false),
164
+ is_model: z.boolean().default(false),
165
+ is_footprint: z.boolean().default(false),
166
+ is_private: z.boolean().nullable().default(false),
167
+ is_public: z.boolean().nullable().default(true),
168
+ is_unlisted: z.boolean().nullable().default(false),
169
+ is_source_from_github: z.boolean().default(false),
170
+ snippet_type: z.enum(["board", "package", "model", "footprint"]).optional(),
171
+ latest_package_release_id: z.string().nullable(),
172
+ latest_version: z.string().nullable(),
173
+ license: z.string().nullable(),
174
+ website: z.string().nullable().default(null),
175
+ star_count: z.number().default(0),
176
+ ai_description: z.string().nullable(),
177
+ latest_license: z.string().nullable().optional(),
178
+ ai_usage_instructions: z.string().nullable()
179
+ });
180
+ var jlcpcbOrderStateSchema = z.object({
181
+ jlcpcb_order_state_id: z.string(),
182
+ order_id: z.string(),
183
+ are_gerbers_uploaded: z.boolean().default(false),
184
+ is_gerber_analyzed: z.boolean().default(false),
185
+ are_initial_costs_calculated: z.boolean().default(false),
186
+ is_pcb_added_to_cart: z.boolean().default(false),
187
+ is_bom_uploaded: z.boolean().default(false),
188
+ is_pnp_uploaded: z.boolean().default(false),
189
+ is_bom_pnp_analyzed: z.boolean().default(false),
190
+ is_bom_parsing_complete: z.boolean().default(false),
191
+ are_components_available: z.boolean().default(false),
192
+ is_patch_map_generated: z.boolean().default(false),
193
+ is_json_merge_file_created: z.boolean().default(false),
194
+ is_dfm_result_generated: z.boolean().default(false),
195
+ are_files_downloaded: z.boolean().default(false),
196
+ are_product_categories_fetched: z.boolean().default(false),
197
+ are_final_costs_calculated: z.boolean().default(false),
198
+ is_json_merge_file_updated: z.boolean().default(false),
199
+ is_added_to_cart: z.boolean().default(false),
200
+ uploaded_gerber_metadata: z.any().nullable().default(null),
201
+ gerber_analysis: z.any().nullable().default(null),
202
+ created_at: z.string(),
203
+ are_gerbers_generated: z.boolean().default(false),
204
+ current_step: z.string().nullable().default(null)
205
+ });
206
+ var jlcpcbOrderStepRunSchema = z.object({
207
+ jlcpcb_order_step_run_id: z.string(),
208
+ is_running: z.boolean().nullable().default(null),
209
+ step_function_name: z.string().nullable().default(null),
210
+ jlcpcb_order_state_id: z.string().nullable().default(null),
211
+ error_message: z.string().nullable().default(null),
212
+ created_at: z.string()
213
+ });
214
+ var databaseSchema = z.object({
215
+ idCounter: z.number().default(0),
216
+ snippets: z.array(snippetSchema).default([]),
217
+ packageReleases: z.array(packageReleaseSchema).default([]),
218
+ packageFiles: z.array(packageFileSchema).default([]),
219
+ sessions: z.array(sessionSchema).default([]),
220
+ loginPages: z.array(loginPageSchema).default([]),
221
+ accounts: z.array(accountSchema).default([]),
222
+ packages: z.array(packageSchema).default([]),
223
+ orders: z.array(orderSchema).default([]),
224
+ orderFiles: z.array(orderFileSchema).default([]),
225
+ accountSnippets: z.array(accountSnippetSchema).default([]),
226
+ accountPackages: z.array(accountPackageSchema).default([]),
227
+ jlcpcbOrderState: z.array(jlcpcbOrderStateSchema).default([]),
228
+ jlcpcbOrderStepRuns: z.array(jlcpcbOrderStepRunSchema).default([]),
229
+ orderQuotes: z.array(orderQuoteSchema).default([])
230
+ });
231
+ export {
232
+ accountPackageSchema,
233
+ accountSchema,
234
+ accountSnippetSchema,
235
+ databaseSchema,
236
+ errorResponseSchema,
237
+ errorSchema,
238
+ jlcpcbOrderStateSchema,
239
+ jlcpcbOrderStepRunSchema,
240
+ loginPageSchema,
241
+ orderFileSchema,
242
+ orderQuoteSchema,
243
+ orderSchema,
244
+ packageFileSchema,
245
+ packageReleaseSchema,
246
+ packageSchema,
247
+ quotedComponentSchema,
248
+ sessionSchema,
249
+ shippingInfoSchema,
250
+ snippetSchema
251
+ };
@@ -0,0 +1,66 @@
1
+ import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
2
+ import { z } from "zod"
3
+ import { orderQuoteSchema } from "fake-snippets-api/lib/db/schema"
4
+
5
+ export default withRouteSpec({
6
+ methods: ["POST"],
7
+ auth: "session",
8
+ jsonBody: z.object({
9
+ order_quote_id: z.string(),
10
+ }),
11
+ jsonResponse: z.object({
12
+ order_quote: orderQuoteSchema.optional(),
13
+ error: z.string().optional(),
14
+ }),
15
+ })(async (req, ctx) => {
16
+ const { order_quote_id } = req.jsonBody
17
+
18
+ return ctx.json({
19
+ order_quote: {
20
+ order_quote_id,
21
+ account_id: "123",
22
+ package_release_id: "123",
23
+ is_completed: true,
24
+ is_processing: false,
25
+ vendor_name: "JLCPCB",
26
+ error: null,
27
+ has_error: false,
28
+ created_at: "2025-04-21",
29
+ updated_at: "2025-04-21",
30
+ completed_at: "2025-04-21",
31
+ quoted_components: [
32
+ {
33
+ manufacturer_part_number: "123",
34
+ supplier_part_number: "123",
35
+ quantity: 1,
36
+ unit_price: 100,
37
+ total_price: 100,
38
+ available: true,
39
+ },
40
+ {
41
+ manufacturer_part_number: "1234",
42
+ supplier_part_number: "1234",
43
+ quantity: 1,
44
+ unit_price: 200,
45
+ total_price: 200,
46
+ available: true,
47
+ },
48
+ ],
49
+ bare_pcb_cost: 300,
50
+ shipping_options: [
51
+ {
52
+ carrier: "DHL",
53
+ service: "Express",
54
+ cost: 100,
55
+ },
56
+ {
57
+ carrier: "FedEx",
58
+ service: "Express",
59
+ cost: 200,
60
+ },
61
+ ],
62
+ // TODO: shipping cost can vary so do the total cost calculation
63
+ total_cost: 100,
64
+ },
65
+ })
66
+ })
@@ -67,28 +67,35 @@ export default withRouteSpec({
67
67
  })
68
68
  }
69
69
 
70
- // Handle is_latest updates
71
- if (is_latest !== undefined && is_latest) {
72
- ctx.db.packageReleases
73
- .filter(
74
- (pr) =>
75
- pr.package_id === release.package_id &&
76
- pr.package_release_id !== releaseId &&
77
- pr.is_latest,
78
- )
79
- .forEach((pr) => {
80
- pr.is_latest = false
81
- })
82
- }
83
-
84
- // Update the release
85
- Object.assign(release, {
70
+ // Create updated release object
71
+ const updatedRelease = {
72
+ ...release,
86
73
  ...(is_locked !== undefined && { is_locked }),
87
74
  ...(is_latest !== undefined && { is_latest }),
88
75
  ...(license !== undefined && { license }),
89
- })
76
+ }
77
+
78
+ // Handle is_latest updates atomically
79
+ if (is_latest !== undefined && is_latest) {
80
+ // Get all releases for this package that are currently marked as latest
81
+ const otherLatestReleases = ctx.db.packageReleases.filter(
82
+ (pr) =>
83
+ pr.package_id === release.package_id &&
84
+ pr.package_release_id !== releaseId &&
85
+ pr.is_latest,
86
+ )
87
+
88
+ // Update all releases in a single operation
89
+ for (const latestRelease of otherLatestReleases) {
90
+ ctx.db.updatePackageRelease({
91
+ ...latestRelease,
92
+ is_latest: false,
93
+ })
94
+ }
95
+ }
90
96
 
91
- ctx.db.updatePackageRelease(release)
97
+ // Update the target release
98
+ ctx.db.updatePackageRelease(updatedRelease)
92
99
 
93
100
  return ctx.json({
94
101
  ok: true,
package/package.json CHANGED
@@ -1,12 +1,16 @@
1
1
  {
2
2
  "name": "@tscircuit/fake-snippets",
3
- "version": "0.0.44",
3
+ "version": "0.0.46",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/tscircuit/snippets"
8
8
  },
9
9
  "main": "dist/index.js",
10
+ "exports": {
11
+ ".": "./dist/index.js",
12
+ "./schema": "./dist/schema.js"
13
+ },
10
14
  "scripts": {
11
15
  "start": "bun run dev",
12
16
  "snapshot": "bun scripts/snapshot.ts",
@@ -21,7 +25,8 @@
21
25
  "lint": "biome format .",
22
26
  "build:fake-api:tsup": "tsup-node ./fake-snippets-api/lib/index.ts --format esm --dts",
23
27
  "build:fake-api:bundle": "winterspec bundle -o dist/bundle.js",
24
- "build:fake-api": "bun run build:fake-api:tsup && bun run build:fake-api:bundle",
28
+ "build:fake-api": "bun run build:fake-api:tsup && bun run build:fake-api:bundle && bun run build:fake-api:schema",
29
+ "build:fake-api:schema": "tsup-node ./fake-snippets-api/lib/db/schema.ts --format esm --dts",
25
30
  "generate-images": "bun run scripts/generate-image-sizes.ts",
26
31
  "generate-sitemap": "bun run scripts/generate-sitemap.ts"
27
32
  },
@@ -136,7 +141,7 @@
136
141
  "@playwright/test": "^1.48.0",
137
142
  "@tscircuit/core": "^0.0.370",
138
143
  "@tscircuit/prompt-benchmarks": "^0.0.28",
139
- "@tscircuit/runframe": "^0.0.330",
144
+ "@tscircuit/runframe": "^0.0.357",
140
145
  "@types/babel__standalone": "^7.1.7",
141
146
  "@types/bun": "^1.1.10",
142
147
  "@types/country-list": "^2.1.4",
@@ -10,7 +10,6 @@ import { decodeUrlHashToText } from "@/lib/decodeUrlHashToText"
10
10
  import { getSnippetTemplate } from "@/lib/get-snippet-template"
11
11
  import { cn } from "@/lib/utils"
12
12
  import { parseJsonOrNull } from "@/lib/utils/parseJsonOrNull"
13
- import "@/prettier"
14
13
  import type { Snippet } from "fake-snippets-api/lib/db/schema"
15
14
  import { Loader2 } from "lucide-react"
16
15
  import { useEffect, useMemo, useState } from "react"
@@ -456,7 +456,6 @@ export const CodeEditor = ({
456
456
  ...(args as Parameters<typeof updateFileContent>),
457
457
  )
458
458
  }}
459
- cursorPosition={cursorPosition}
460
459
  />
461
460
  )}
462
461
  <div ref={editorRef} className="flex-1 overflow-auto" />
@@ -8,8 +8,6 @@ import {
8
8
  } from "@/components/ui/select"
9
9
  import { useImportSnippetDialog } from "./dialogs/import-snippet-dialog"
10
10
  import { useToast } from "@/hooks/use-toast"
11
- import { FootprintDialog } from "./FootprintDialog"
12
- import { useState } from "react"
13
11
  import {
14
12
  DropdownMenu,
15
13
  DropdownMenuContent,
@@ -27,7 +25,6 @@ interface CodeEditorHeaderProps {
27
25
  files: Record<FileName, string>
28
26
  handleFileChange: (filename: FileName) => void
29
27
  updateFileContent: (filename: FileName, content: string) => void
30
- cursorPosition: number | null
31
28
  }
32
29
 
33
30
  export const CodeEditorHeader = ({
@@ -35,11 +32,9 @@ export const CodeEditorHeader = ({
35
32
  files,
36
33
  handleFileChange,
37
34
  updateFileContent,
38
- cursorPosition,
39
35
  }: CodeEditorHeaderProps) => {
40
36
  const { Dialog: ImportSnippetDialog, openDialog: openImportDialog } =
41
37
  useImportSnippetDialog()
42
- const [footprintDialogOpen, setFootprintDialogOpen] = useState(false)
43
38
  const { toast } = useToast()
44
39
 
45
40
  const formatCurrentFile = () => {
@@ -120,18 +115,6 @@ export const CodeEditorHeader = ({
120
115
  </DropdownMenuContent>
121
116
  </DropdownMenu>
122
117
  )}
123
- <DropdownMenu>
124
- <DropdownMenuTrigger asChild>
125
- <Button size="sm" variant="ghost">
126
- Insert
127
- </Button>
128
- </DropdownMenuTrigger>
129
- <DropdownMenuContent>
130
- <DropdownMenuItem onClick={() => setFootprintDialogOpen(true)}>
131
- Chip
132
- </DropdownMenuItem>
133
- </DropdownMenuContent>
134
- </DropdownMenu>
135
118
  <Button size="sm" variant="ghost" onClick={() => openImportDialog()}>
136
119
  Import
137
120
  </Button>
@@ -145,14 +128,6 @@ export const CodeEditorHeader = ({
145
128
  updateFileContent(currentFile, newContent)
146
129
  }}
147
130
  />
148
- <FootprintDialog
149
- currentFile={currentFile}
150
- open={footprintDialogOpen}
151
- onOpenChange={setFootprintDialogOpen}
152
- updateFileContent={updateFileContent}
153
- files={files}
154
- cursorPosition={cursorPosition}
155
- />
156
131
  </div>
157
132
  )
158
133
  }
@@ -195,14 +195,16 @@ export default function EditorNav({
195
195
  {snippet && (
196
196
  <>
197
197
  <SnippetLink snippet={snippet} />
198
- <Button
199
- variant="ghost"
200
- size="icon"
201
- className="h-6 w-6 ml-2"
202
- onClick={() => openRenameDialog()}
203
- >
204
- <Pencil className="h-3 w-3 text-gray-700" />
205
- </Button>
198
+ {snippet?.owner_name === session?.github_username && (
199
+ <Button
200
+ variant="ghost"
201
+ size="icon"
202
+ className="h-6 w-6 ml-2"
203
+ onClick={() => openRenameDialog()}
204
+ >
205
+ <Pencil className="h-3 w-3 text-gray-700" />
206
+ </Button>
207
+ )}
206
208
  {isPrivate && (
207
209
  <div className="relative group">
208
210
  <LockClosedIcon className="h-3 w-3 text-gray-700" />
@@ -0,0 +1,35 @@
1
+ import { AlertCircle } from "lucide-react"
2
+
3
+ export function ErrorOutline({
4
+ error,
5
+ description,
6
+ }: { error?: Error; description?: string; children?: React.ReactNode }) {
7
+ return (
8
+ <div className="flex items-center justify-center p-4">
9
+ <div className="max-w-lg w-full mx-auto p-6 sm:p-8 bg-red-100/80 rounded-xl shadow-lg border border-red-500">
10
+ <div className="flex flex-col items-center text-center">
11
+ <AlertCircle className="h-10 w-10 text-red-700 mb-4" />
12
+ <h2 className="text-xl sm:text-2xl font-bold text-red-700 mb-3">
13
+ Something strange happened
14
+ </h2>
15
+ <p className="text-red-600 font-medium mb-6 text-sm sm:text-base">
16
+ {description ||
17
+ "An unexpected error occurred while processing your request."}{" "}
18
+ <br />
19
+ Please try again or contact support if the problem persists.
20
+ </p>
21
+ {error?.message && (
22
+ <details className="w-full text-left bg-red-50 p-3 rounded-md border border-red-200">
23
+ <summary className="text-sm font-medium text-red-800 cursor-pointer hover:text-red-900">
24
+ Show Error Details
25
+ </summary>
26
+ <pre className="mt-2 text-xs text-red-700 bg-transparent p-0 rounded overflow-auto whitespace-pre-wrap break-words">
27
+ {error.message}
28
+ </pre>
29
+ </details>
30
+ )}
31
+ </div>
32
+ </div>
33
+ </div>
34
+ )
35
+ }
@@ -2,6 +2,7 @@ import React, { useState } from "react"
2
2
  import { cn } from "@/lib/utils"
3
3
  import { File, Folder, PanelRightOpen } from "lucide-react"
4
4
  import { TreeView, TreeDataItem } from "@/components/ui/tree-view"
5
+ import { isHiddenFile } from "./ViewPackagePage/utils/is-hidden-file"
5
6
 
6
7
  type FileName = string
7
8
 
@@ -24,35 +25,63 @@ const FileSidebar: React.FC<FileSidebarProps> = ({
24
25
  const transformFilesToTreeData = (
25
26
  files: Record<FileName, string>,
26
27
  ): TreeDataItem[] => {
27
- const root: { [key: string]: TreeDataItem } = {}
28
+ type TreeNode = Omit<TreeDataItem, "children"> & {
29
+ children?: Record<string, TreeNode>
30
+ }
31
+ const root: Record<string, TreeNode> = {}
28
32
 
29
33
  Object.keys(files).forEach((path) => {
30
- const parts = path.split("/")
34
+ const startsWithSlash = path.startsWith("/")
35
+ const parts = (startsWithSlash ? path.slice(1) : path).trim().split("/")
31
36
  let current = root
32
37
 
33
- let currentPath = ""
34
38
  parts.forEach((part, index) => {
35
- const id = currentPath ? `${currentPath}/${part}` : part
36
- currentPath = id
37
-
38
- if (!current[part]) {
39
- const isFile = index === parts.length - 1
39
+ const isFile = index === parts.length - 1
40
+ const parentPath = parts.slice(0, index).join("/")
41
+ const currentPath = parentPath ? `${parentPath}/${part}` : part
42
+ const evaluatedFilePath = startsWithSlash
43
+ ? `/${currentPath}`
44
+ : currentPath
45
+ if (
46
+ !current[part] &&
47
+ (!isHiddenFile(currentPath) ||
48
+ isHiddenFile(
49
+ currentFile.startsWith("/") ? currentFile.slice(1) : currentFile,
50
+ ))
51
+ ) {
40
52
  current[part] = {
41
- id: id,
42
- name: part,
53
+ id: currentPath,
54
+ name: isFile ? part : part,
43
55
  icon: isFile ? File : Folder,
44
- onClick: isFile ? () => onFileSelect(id) : undefined,
56
+ onClick: isFile ? () => onFileSelect(evaluatedFilePath) : undefined,
57
+ draggable: isFile,
58
+ droppable: !isFile,
59
+ children: isFile ? undefined : {},
45
60
  }
46
61
  }
47
- current = (current[part] as any).children || (current[part] as any)
62
+
63
+ if (!isFile && current[part].children) {
64
+ current = current[part].children
65
+ }
48
66
  })
49
67
  })
50
68
 
51
- return Object.values(root)
69
+ // Convert the nested object structure to array structure
70
+ const convertToArray = (
71
+ items: Record<string, TreeNode>,
72
+ ): TreeDataItem[] => {
73
+ return Object.values(items).map((item) => ({
74
+ ...item,
75
+ children: item.children ? convertToArray(item.children) : undefined,
76
+ }))
77
+ }
78
+ return convertToArray(root).filter((x) => {
79
+ if (x.children?.length === 0) return false
80
+ return true
81
+ })
52
82
  }
53
83
 
54
84
  const treeData = transformFilesToTreeData(files)
55
-
56
85
  return (
57
86
  <div
58
87
  className={cn(
@@ -63,11 +92,12 @@ const FileSidebar: React.FC<FileSidebarProps> = ({
63
92
  >
64
93
  <button
65
94
  onClick={() => setSidebarOpen(!sidebarOpen)}
66
- className={`z-[99] mt-2 ml-2 text-black/60 scale-90 transition-opacity duration-200 ${!sidebarOpen ? "opacity-0 pointer-events-none" : "opacity-100"}`}
95
+ className={`z-[99] mt-2 ml-2 text-gray-400 scale-90 transition-opacity duration-200 ${
96
+ !sidebarOpen ? "opacity-0 pointer-events-none" : "opacity-100"
97
+ }`}
67
98
  >
68
99
  <PanelRightOpen />
69
100
  </button>
70
-
71
101
  <TreeView
72
102
  data={treeData}
73
103
  initialSelectedItemId={currentFile}