@tscircuit/fake-snippets 0.0.99 → 0.0.101

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.
package/dist/index.d.ts CHANGED
@@ -1073,7 +1073,7 @@ declare const createDatabase: ({ seed }?: {
1073
1073
  datasheet_pdf_urls: string[] | null;
1074
1074
  ai_description: string | null;
1075
1075
  }[];
1076
- }, "addOrder" | "getOrderById" | "getOrderFilesByOrderId" | "addOrderQuote" | "getOrderQuoteById" | "getJlcpcbOrderStatesByOrderId" | "getJlcpcbOrderStepRunsByJlcpcbOrderStateId" | "updateOrder" | "addJlcpcbOrderState" | "updateJlcpcbOrderState" | "addOrderFile" | "getOrderFileById" | "addAccount" | "addAccountPackage" | "getAccountPackageById" | "updateAccountPackage" | "deleteAccountPackage" | "addSnippet" | "getLatestSnippets" | "getTrendingSnippets" | "getPackagesByAuthor" | "getSnippetByAuthorAndName" | "updateSnippet" | "getSnippetById" | "searchSnippets" | "searchPackages" | "deleteSnippet" | "addSession" | "getSessions" | "createLoginPage" | "getLoginPage" | "updateLoginPage" | "getAccount" | "updateAccount" | "createSession" | "addStar" | "removeStar" | "hasStarred" | "addPackage" | "updatePackage" | "getPackageById" | "getPackageReleaseById" | "addPackageRelease" | "updatePackageRelease" | "deletePackageFile" | "addPackageFile" | "updatePackageFile" | "getStarCount" | "getPackageFilesByReleaseId" | "updatePackageReleaseFsSha" | "addAiReview" | "updateAiReview" | "getAiReviewById" | "listAiReviews" | "addDatasheet" | "getDatasheetById" | "getDatasheetByChipName" | "listDatasheets" | "updateDatasheet"> & {
1076
+ }, "addOrder" | "getOrderById" | "getOrderFilesByOrderId" | "addOrderQuote" | "getOrderQuoteById" | "getJlcpcbOrderStatesByOrderId" | "getJlcpcbOrderStepRunsByJlcpcbOrderStateId" | "updateOrder" | "addJlcpcbOrderState" | "updateJlcpcbOrderState" | "addOrderFile" | "getOrderFileById" | "addAccount" | "addAccountPackage" | "getAccountPackageById" | "updateAccountPackage" | "deleteAccountPackage" | "addSnippet" | "getLatestSnippets" | "getTrendingSnippets" | "getPackagesByAuthor" | "getSnippetByAuthorAndName" | "updateSnippet" | "getSnippetById" | "searchSnippets" | "searchPackages" | "searchAccounts" | "deleteSnippet" | "addSession" | "getSessions" | "createLoginPage" | "getLoginPage" | "updateLoginPage" | "getAccount" | "updateAccount" | "createSession" | "addStar" | "removeStar" | "hasStarred" | "addPackage" | "updatePackage" | "getPackageById" | "getPackageReleaseById" | "addPackageRelease" | "updatePackageRelease" | "deletePackageFile" | "addPackageFile" | "updatePackageFile" | "getStarCount" | "getPackageFilesByReleaseId" | "updatePackageReleaseFsSha" | "addAiReview" | "updateAiReview" | "getAiReviewById" | "listAiReviews" | "addDatasheet" | "getDatasheetById" | "getDatasheetByChipName" | "listDatasheets" | "updateDatasheet"> & {
1077
1077
  addOrder: (order: Omit<Order, "order_id">) => Order;
1078
1078
  getOrderById: (orderId: string) => Order | undefined;
1079
1079
  getOrderFilesByOrderId: (orderId: string) => OrderFile[];
@@ -1121,6 +1121,7 @@ declare const createDatabase: ({ seed }?: {
1121
1121
  }) => Snippet | undefined;
1122
1122
  searchSnippets: (query: string) => Snippet[];
1123
1123
  searchPackages: (query: string) => Package[];
1124
+ searchAccounts: (query: string, limit?: number) => Account[];
1124
1125
  deleteSnippet: (snippetId: string) => boolean;
1125
1126
  addSession: (session: Omit<Session, "session_id">) => Session;
1126
1127
  getSessions: ({ account_id, is_cli_session, }: {
@@ -1426,7 +1427,7 @@ declare const createDatabase: ({ seed }?: {
1426
1427
  datasheet_pdf_urls: string[] | null;
1427
1428
  ai_description: string | null;
1428
1429
  }[];
1429
- }, "addOrder" | "getOrderById" | "getOrderFilesByOrderId" | "addOrderQuote" | "getOrderQuoteById" | "getJlcpcbOrderStatesByOrderId" | "getJlcpcbOrderStepRunsByJlcpcbOrderStateId" | "updateOrder" | "addJlcpcbOrderState" | "updateJlcpcbOrderState" | "addOrderFile" | "getOrderFileById" | "addAccount" | "addAccountPackage" | "getAccountPackageById" | "updateAccountPackage" | "deleteAccountPackage" | "addSnippet" | "getLatestSnippets" | "getTrendingSnippets" | "getPackagesByAuthor" | "getSnippetByAuthorAndName" | "updateSnippet" | "getSnippetById" | "searchSnippets" | "searchPackages" | "deleteSnippet" | "addSession" | "getSessions" | "createLoginPage" | "getLoginPage" | "updateLoginPage" | "getAccount" | "updateAccount" | "createSession" | "addStar" | "removeStar" | "hasStarred" | "addPackage" | "updatePackage" | "getPackageById" | "getPackageReleaseById" | "addPackageRelease" | "updatePackageRelease" | "deletePackageFile" | "addPackageFile" | "updatePackageFile" | "getStarCount" | "getPackageFilesByReleaseId" | "updatePackageReleaseFsSha" | "addAiReview" | "updateAiReview" | "getAiReviewById" | "listAiReviews" | "addDatasheet" | "getDatasheetById" | "getDatasheetByChipName" | "listDatasheets" | "updateDatasheet"> & {
1430
+ }, "addOrder" | "getOrderById" | "getOrderFilesByOrderId" | "addOrderQuote" | "getOrderQuoteById" | "getJlcpcbOrderStatesByOrderId" | "getJlcpcbOrderStepRunsByJlcpcbOrderStateId" | "updateOrder" | "addJlcpcbOrderState" | "updateJlcpcbOrderState" | "addOrderFile" | "getOrderFileById" | "addAccount" | "addAccountPackage" | "getAccountPackageById" | "updateAccountPackage" | "deleteAccountPackage" | "addSnippet" | "getLatestSnippets" | "getTrendingSnippets" | "getPackagesByAuthor" | "getSnippetByAuthorAndName" | "updateSnippet" | "getSnippetById" | "searchSnippets" | "searchPackages" | "searchAccounts" | "deleteSnippet" | "addSession" | "getSessions" | "createLoginPage" | "getLoginPage" | "updateLoginPage" | "getAccount" | "updateAccount" | "createSession" | "addStar" | "removeStar" | "hasStarred" | "addPackage" | "updatePackage" | "getPackageById" | "getPackageReleaseById" | "addPackageRelease" | "updatePackageRelease" | "deletePackageFile" | "addPackageFile" | "updatePackageFile" | "getStarCount" | "getPackageFilesByReleaseId" | "updatePackageReleaseFsSha" | "addAiReview" | "updateAiReview" | "getAiReviewById" | "listAiReviews" | "addDatasheet" | "getDatasheetById" | "getDatasheetByChipName" | "listDatasheets" | "updateDatasheet"> & {
1430
1431
  addOrder: (order: Omit<Order, "order_id">) => Order;
1431
1432
  getOrderById: (orderId: string) => Order | undefined;
1432
1433
  getOrderFilesByOrderId: (orderId: string) => OrderFile[];
@@ -1474,6 +1475,7 @@ declare const createDatabase: ({ seed }?: {
1474
1475
  }) => Snippet | undefined;
1475
1476
  searchSnippets: (query: string) => Snippet[];
1476
1477
  searchPackages: (query: string) => Package[];
1478
+ searchAccounts: (query: string, limit?: number) => Account[];
1477
1479
  deleteSnippet: (snippetId: string) => boolean;
1478
1480
  addSession: (session: Omit<Session, "session_id">) => Session;
1479
1481
  getSessions: ({ account_id, is_cli_session, }: {
package/dist/index.js CHANGED
@@ -2798,6 +2798,31 @@ var initializer = combine(databaseSchema.parse({}), (set, get) => ({
2798
2798
  ];
2799
2799
  return matchingPackages;
2800
2800
  },
2801
+ searchAccounts: (query, limit) => {
2802
+ const state = get();
2803
+ const lowercaseQuery = query.toLowerCase();
2804
+ const accountsWithPublicPackages = /* @__PURE__ */ new Set();
2805
+ state.packages.filter((pkg) => pkg.is_public === true).forEach((pkg) => {
2806
+ if (pkg.creator_account_id) {
2807
+ accountsWithPublicPackages.add(pkg.creator_account_id);
2808
+ }
2809
+ if (pkg.owner_github_username) {
2810
+ const account = state.accounts.find(
2811
+ (acc) => acc.github_username === pkg.owner_github_username
2812
+ );
2813
+ if (account) {
2814
+ accountsWithPublicPackages.add(account.account_id);
2815
+ }
2816
+ }
2817
+ });
2818
+ const matchingAccounts = state.accounts.filter((account) => {
2819
+ if (!accountsWithPublicPackages.has(account.account_id)) {
2820
+ return false;
2821
+ }
2822
+ return account.github_username.toLowerCase().includes(lowercaseQuery);
2823
+ }).slice(0, limit || 50);
2824
+ return matchingAccounts;
2825
+ },
2801
2826
  deleteSnippet: (snippetId) => {
2802
2827
  let deleted = false;
2803
2828
  set((state) => {
@@ -1019,6 +1019,38 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({
1019
1019
 
1020
1020
  return matchingPackages
1021
1021
  },
1022
+ searchAccounts: (query: string, limit?: number): Account[] => {
1023
+ const state = get()
1024
+ const lowercaseQuery = query.toLowerCase()
1025
+
1026
+ const accountsWithPublicPackages = new Set<string>()
1027
+ state.packages
1028
+ .filter((pkg) => pkg.is_public === true)
1029
+ .forEach((pkg) => {
1030
+ if (pkg.creator_account_id) {
1031
+ accountsWithPublicPackages.add(pkg.creator_account_id)
1032
+ }
1033
+ if (pkg.owner_github_username) {
1034
+ const account = state.accounts.find(
1035
+ (acc) => acc.github_username === pkg.owner_github_username,
1036
+ )
1037
+ if (account) {
1038
+ accountsWithPublicPackages.add(account.account_id)
1039
+ }
1040
+ }
1041
+ })
1042
+
1043
+ const matchingAccounts = state.accounts
1044
+ .filter((account) => {
1045
+ if (!accountsWithPublicPackages.has(account.account_id)) {
1046
+ return false
1047
+ }
1048
+ return account.github_username.toLowerCase().includes(lowercaseQuery)
1049
+ })
1050
+ .slice(0, limit || 50)
1051
+
1052
+ return matchingAccounts
1053
+ },
1022
1054
  deleteSnippet: (snippetId: string): boolean => {
1023
1055
  let deleted = false
1024
1056
  set((state) => {
@@ -0,0 +1,20 @@
1
+ import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
2
+ import { z } from "zod"
3
+ import { accountSchema } from "fake-snippets-api/lib/db/schema"
4
+
5
+ export default withRouteSpec({
6
+ methods: ["POST"],
7
+ auth: "session",
8
+ jsonBody: z.object({
9
+ query: z.string(),
10
+ limit: z.number().optional().default(50),
11
+ }),
12
+ jsonResponse: z.object({
13
+ ok: z.boolean(),
14
+ accounts: z.array(accountSchema),
15
+ }),
16
+ })(async (req, ctx) => {
17
+ const { query, limit } = req.jsonBody
18
+ const accounts = ctx.db.searchAccounts(query, limit)
19
+ return ctx.json({ accounts, ok: true })
20
+ })
@@ -13,7 +13,8 @@ export default withRouteSpec({
13
13
  /^[@a-zA-Z0-9-_\/]+$/,
14
14
  "Package name can only contain letters, numbers, hyphens, underscores, and forward slashes",
15
15
  )
16
- .transform((name) => name.replace(/^@/, "")),
16
+ .transform((name) => name.replace(/^@/, ""))
17
+ .optional(),
17
18
  description: z.string().optional(),
18
19
  is_private: z.boolean().optional().default(false),
19
20
  is_unlisted: z.boolean().optional().default(false),
@@ -33,10 +34,20 @@ export default withRouteSpec({
33
34
  })
34
35
  }
35
36
 
36
- const unscoped_name = name.split("/")[1]
37
+ let unscoped_name = name?.includes("/") ? name?.split("/")[1] : name
38
+ if (!unscoped_name) {
39
+ const state = ctx.db.getState()
40
+ const count = state.packages.filter(
41
+ (pkg) => pkg.creator_account_id === ctx.auth.account_id,
42
+ ).length
43
+
44
+ unscoped_name = `untitled-package-${count}`
45
+ }
37
46
 
38
47
  const newPackage = ctx.db.addPackage({
39
- name,
48
+ name: name?.includes("/")
49
+ ? name
50
+ : `${ctx.auth.github_username}/${String(unscoped_name)}`,
40
51
  description: description ?? null,
41
52
  creator_account_id: ctx.auth.account_id,
42
53
  owner_org_id: ctx.auth.personal_org_id,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/fake-snippets",
3
- "version": "0.0.99",
3
+ "version": "0.0.101",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -83,7 +83,7 @@
83
83
  "@tscircuit/mm": "^0.0.8",
84
84
  "@tscircuit/pcb-viewer": "^1.11.194",
85
85
  "@tscircuit/prompt-benchmarks": "^0.0.28",
86
- "@tscircuit/runframe": "0.0.715",
86
+ "@tscircuit/runframe": "0.0.736",
87
87
  "@tscircuit/schematic-viewer": "^2.0.21",
88
88
  "@types/babel__standalone": "^7.1.7",
89
89
  "@types/bun": "^1.1.10",
@@ -105,9 +105,10 @@
105
105
  "autoprefixer": "^10.4.20",
106
106
  "change-case": "^5.4.4",
107
107
  "circuit-json-to-bom-csv": "^0.0.7",
108
- "circuit-json-to-gerber": "^0.0.25",
108
+ "circuit-json-to-gerber": "^0.0.29",
109
109
  "circuit-json-to-pnp-csv": "^0.0.7",
110
110
  "circuit-json-to-readable-netlist": "^0.0.13",
111
+ "circuit-json-to-spice": "^0.0.6",
111
112
  "circuit-json-to-tscircuit": "^0.0.4",
112
113
  "circuit-to-svg": "^0.0.167",
113
114
  "class-variance-authority": "^0.7.1",
@@ -138,6 +139,7 @@
138
139
  "ky": "^1.7.5",
139
140
  "lucide-react": "^0.488.0",
140
141
  "lz-string": "^1.5.0",
142
+ "marked": "^16.1.1",
141
143
  "md5": "^2.3.0",
142
144
  "ms": "^2.1.3",
143
145
  "next-themes": "^0.3.0",
@@ -150,6 +152,7 @@
150
152
  "react-cookie-consent": "^9.0.0",
151
153
  "react-day-picker": "8.10.1",
152
154
  "react-dom": "^18.3.1",
155
+ "react-error-boundary": "^6.0.0",
153
156
  "react-helmet": "^6.1.0",
154
157
  "react-helmet-async": "^2.0.5",
155
158
  "react-hook-form": "^7.53.0",
@@ -187,7 +190,6 @@
187
190
  "wouter": "^3.3.5",
188
191
  "zod": "^3.23.8",
189
192
  "zustand": "^4.5.5",
190
- "zustand-hoist": "^2.0.1",
191
- "circuit-json-to-spice": "^0.0.6"
193
+ "zustand-hoist": "^2.0.1"
192
194
  }
193
195
  }
package/src/App.tsx CHANGED
@@ -3,6 +3,8 @@ import { Route, Switch } from "wouter"
3
3
  import "./components/CmdKMenu"
4
4
  import { ContextProviders } from "./ContextProviders"
5
5
  import React from "react"
6
+ import { ReloadIcon } from "@radix-ui/react-icons"
7
+ import { Loader2 } from "lucide-react"
6
8
 
7
9
  const FullPageLoader = () => (
8
10
  <div className="fixed inset-0 flex items-center justify-center bg-white z-50">
@@ -163,10 +165,64 @@ class ErrorBoundary extends React.Component<
163
165
 
164
166
  render() {
165
167
  if (this.state.reloading) {
166
- return <div>There was a problem loading this page. Reloading…</div>
168
+ return (
169
+ <div className="min-h-screen flex items-center justify-center bg-gray-50 dark:bg-gray-900 px-4">
170
+ <div className="max-w-md w-full text-center">
171
+ <div className="mb-6">
172
+ <div className="inline-flex items-center justify-center w-16 h-16 bg-blue-100 dark:bg-blue-900 rounded-full mb-4">
173
+ <Loader2 className="w-8 h-8 text-blue-600 dark:text-blue-400 animate-spin" />
174
+ </div>
175
+ <h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-2">
176
+ Reloading Page
177
+ </h2>
178
+ <p className="text-gray-600 dark:text-gray-400">
179
+ We encountered an issue and are refreshing the page for you.
180
+ </p>
181
+ </div>
182
+ </div>
183
+ </div>
184
+ )
167
185
  }
168
186
  if (this.state.hasError) {
169
- return <div>Something went wrong loading the page.</div>
187
+ return (
188
+ <div className="min-h-screen flex items-center justify-center bg-gray-50 dark:bg-gray-900 px-4">
189
+ <div className="max-w-lg w-full text-center">
190
+ <div className="mb-8">
191
+ <div className="inline-flex items-center justify-center w-20 h-20 bg-red-100 dark:bg-red-900 rounded-full mb-6">
192
+ <svg
193
+ className="w-10 h-10 text-red-600 dark:text-red-400"
194
+ fill="none"
195
+ stroke="currentColor"
196
+ viewBox="0 0 24 24"
197
+ >
198
+ <path
199
+ strokeLinecap="round"
200
+ strokeLinejoin="round"
201
+ strokeWidth={2}
202
+ d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"
203
+ />
204
+ </svg>
205
+ </div>
206
+ <h1 className="text-2xl font-bold text-gray-900 dark:text-white mb-3">
207
+ Oops! Something went wrong
208
+ </h1>
209
+ <p className="text-gray-600 dark:text-gray-400 mb-6">
210
+ We're experiencing technical difficulties. The page will
211
+ automatically reload when you return to this tab.
212
+ </p>
213
+ </div>
214
+ <div className="space-y-3">
215
+ <button
216
+ onClick={this.performReload}
217
+ className="w-full sm:w-auto inline-flex items-center justify-center px-6 py-2 border border-transparent text-base font-medium rounded-lg text-white bg-red-600 hover:bg-red-700 dark:bg-red-500 dark:hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 transition-colors duration-200"
218
+ >
219
+ <ReloadIcon className="w-4 h-4 mr-2" />
220
+ Reload Now
221
+ </button>
222
+ </div>
223
+ </div>
224
+ </div>
225
+ )
170
226
  }
171
227
  return this.props.children
172
228
  }
@@ -15,7 +15,6 @@ import { useLocation } from "wouter"
15
15
  import { useGlobalStore } from "@/hooks/use-global-store"
16
16
  import { convertCircuitJsonToTscircuit } from "circuit-json-to-tscircuit"
17
17
  import { useCreatePackageMutation } from "@/hooks/use-create-package-mutation"
18
- import { generateRandomPackageName } from "@/lib/utils/package-utils"
19
18
  import { useCreatePackageReleaseMutation } from "@/hooks/use-create-package-release-mutation"
20
19
  import { useCreatePackageFilesMutation } from "@/hooks/use-create-package-files-mutation"
21
20
 
@@ -67,10 +66,17 @@ export function CircuitJsonImportDialog({
67
66
 
68
67
  const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
69
68
  const selectedFile = e.target.files?.[0]
70
- if (selectedFile && selectedFile.type === "application/json") {
71
- setFile(selectedFile)
69
+ if (selectedFile) {
70
+ try {
71
+ const fileText = await selectedFile.text()
72
+ JSON.parse(fileText)
73
+ setFile(selectedFile)
74
+ setError(null)
75
+ } catch (e) {
76
+ setError("Please select a valid JSON file that can be parsed.")
77
+ }
72
78
  } else {
73
- setError("Please select a valid JSON file.")
79
+ setError("Please select a file.")
74
80
  }
75
81
  }
76
82
 
@@ -124,7 +130,6 @@ export function CircuitJsonImportDialog({
124
130
 
125
131
  await createPackageMutation.mutateAsync(
126
132
  {
127
- name: `${loggedInUser?.github_username}/${generateRandomPackageName()}`,
128
133
  description: "Imported from Circuit JSON",
129
134
  },
130
135
  {
@@ -5,7 +5,7 @@ import { useHotkeyCombo } from "@/hooks/use-hotkey"
5
5
  import { useNotImplementedToast } from "@/hooks/use-toast"
6
6
  import { fuzzyMatch } from "@/components/ViewPackagePage/utils/fuzz-search"
7
7
  import { Command } from "cmdk"
8
- import { Package } from "fake-snippets-api/lib/db/schema"
8
+ import { Package, Account } from "fake-snippets-api/lib/db/schema"
9
9
  import React, { useCallback, useEffect, useMemo, useRef } from "react"
10
10
  import { useQuery } from "react-query"
11
11
  import {
@@ -16,6 +16,8 @@ import {
16
16
  Sparkles,
17
17
  Clock,
18
18
  ArrowRight,
19
+ Star,
20
+ User,
19
21
  } from "lucide-react"
20
22
  import { DialogTitle, DialogDescription } from "@/components/ui/dialog"
21
23
 
@@ -40,6 +42,11 @@ interface ScoredPackage extends Package {
40
42
  matches: number[]
41
43
  }
42
44
 
45
+ interface ScoredAccount extends Account {
46
+ score: number
47
+ matches: number[]
48
+ }
49
+
43
50
  const CmdKMenu = () => {
44
51
  const [open, setOpen] = React.useState(false)
45
52
  const [searchQuery, setSearchQuery] = React.useState("")
@@ -113,13 +120,42 @@ const CmdKMenu = () => {
113
120
  ["packageSearch", searchQuery],
114
121
  async () => {
115
122
  if (!searchQuery) return []
116
- const { data } = await axios.post("/packages/search", {
117
- query: searchQuery,
118
- })
119
- return data.packages || []
123
+ try {
124
+ const { data } = await axios.post("/packages/search", {
125
+ query: searchQuery,
126
+ })
127
+ return data.packages || []
128
+ } catch (error) {
129
+ console.warn("Failed to fetch packages:", error)
130
+ return []
131
+ }
120
132
  },
121
133
  {
122
134
  enabled: Boolean(searchQuery),
135
+ retry: false,
136
+ refetchOnWindowFocus: false,
137
+ },
138
+ )
139
+
140
+ const { data: allAccounts = [], isLoading: isSearchingAccounts } = useQuery(
141
+ ["accountSearch", searchQuery],
142
+ async () => {
143
+ if (!searchQuery) return []
144
+ try {
145
+ const { data } = await axios.post("/accounts/search", {
146
+ query: searchQuery,
147
+ limit: 5,
148
+ })
149
+ return data.accounts || []
150
+ } catch (error) {
151
+ console.warn("Failed to fetch accounts:", error)
152
+ return []
153
+ }
154
+ },
155
+ {
156
+ enabled: Boolean(searchQuery) && Boolean(currentUser),
157
+ retry: false,
158
+ refetchOnWindowFocus: false,
123
159
  },
124
160
  )
125
161
 
@@ -133,20 +169,43 @@ const CmdKMenu = () => {
133
169
  })
134
170
  .filter((pkg: ScoredPackage) => pkg.score >= 0)
135
171
  .sort((a: ScoredPackage, b: ScoredPackage) => b.score - a.score)
136
- .slice(0, 8)
172
+ .slice(0, 6)
137
173
  }, [allPackages, searchQuery])
138
174
 
175
+ const accountSearchResults = useMemo((): ScoredAccount[] => {
176
+ if (!searchQuery || !allAccounts.length) return []
177
+
178
+ return allAccounts
179
+ .map((account: Account) => {
180
+ const { score, matches } = fuzzyMatch(
181
+ searchQuery,
182
+ account.github_username,
183
+ )
184
+ return { ...account, score, matches }
185
+ })
186
+ .filter((account: ScoredAccount) => account.score >= 0)
187
+ .sort((a: ScoredAccount, b: ScoredAccount) => b.score - a.score)
188
+ .slice(0, 5)
189
+ }, [allAccounts, searchQuery])
190
+
139
191
  const { data: recentPackages = [] } = useQuery<Package[]>(
140
192
  ["userPackages", currentUser],
141
193
  async () => {
142
194
  if (!currentUser) return []
143
- const response = await axios.post(`/packages/list`, {
144
- owner_github_username: currentUser,
145
- })
146
- return response.data.packages || []
195
+ try {
196
+ const response = await axios.post(`/packages/list`, {
197
+ owner_github_username: currentUser,
198
+ })
199
+ return response.data.packages || []
200
+ } catch (error) {
201
+ console.warn("Failed to fetch recent packages:", error)
202
+ return []
203
+ }
147
204
  },
148
205
  {
149
206
  enabled: !!currentUser && !searchQuery,
207
+ retry: false,
208
+ refetchOnWindowFocus: false,
150
209
  },
151
210
  )
152
211
 
@@ -195,7 +254,7 @@ const CmdKMenu = () => {
195
254
 
196
255
  const allItems = useMemo(() => {
197
256
  const items: Array<{
198
- type: "package" | "recent" | "template" | "blank" | "import"
257
+ type: "package" | "account" | "recent" | "template" | "blank" | "import"
199
258
  item: any
200
259
  disabled?: boolean
201
260
  }> = []
@@ -206,6 +265,12 @@ const CmdKMenu = () => {
206
265
  })
207
266
  }
208
267
 
268
+ if (searchQuery && accountSearchResults.length > 0) {
269
+ accountSearchResults.forEach((account) => {
270
+ items.push({ type: "account", item: account })
271
+ })
272
+ }
273
+
209
274
  if (!searchQuery && recentPackages.length > 0) {
210
275
  recentPackages.slice(0, 6).forEach((pkg) => {
211
276
  items.push({ type: "recent", item: pkg })
@@ -225,7 +290,13 @@ const CmdKMenu = () => {
225
290
  })
226
291
 
227
292
  return items
228
- }, [searchQuery, searchResults, recentPackages, filteredStaticOptions])
293
+ }, [
294
+ searchQuery,
295
+ searchResults,
296
+ accountSearchResults,
297
+ recentPackages,
298
+ filteredStaticOptions,
299
+ ])
229
300
 
230
301
  useHotkeyCombo("cmd+k", () => {
231
302
  setOpen((prev) => !prev)
@@ -285,6 +356,10 @@ const CmdKMenu = () => {
285
356
  window.location.href = `/${item.owner_github_username}/${item.unscoped_name}`
286
357
  setOpen(false)
287
358
  break
359
+ case "account":
360
+ window.location.href = `/${item.github_username}`
361
+ setOpen(false)
362
+ break
288
363
  case "blank":
289
364
  case "template":
290
365
  if (!item.disabled) {
@@ -362,7 +437,11 @@ const CmdKMenu = () => {
362
437
  )}
363
438
  </div>
364
439
  </div>
365
- <div className="flex items-center gap-1 flex-shrink-0">
440
+ <div className="flex items-center gap-2 flex-shrink-0">
441
+ <div className="flex items-center gap-1 text-gray-500">
442
+ <Star className="w-3 h-3 fill-yellow-400 text-yellow-400" />
443
+ <span className="text-xs">{data.star_count ?? 0}</span>
444
+ </div>
366
445
  <span className="text-xs text-gray-500 bg-gray-100 px-1.5 py-0.5 rounded">
367
446
  package
368
447
  </span>
@@ -371,6 +450,41 @@ const CmdKMenu = () => {
371
450
  </div>
372
451
  )
373
452
 
453
+ case "account":
454
+ return (
455
+ <div
456
+ key={`account-${data.account_id}`}
457
+ ref={isSelected ? selectedItemRef : null}
458
+ className={baseClasses}
459
+ onClick={() => !disabled && handleItemSelect(item)}
460
+ >
461
+ <div className="flex items-center gap-2 min-w-0">
462
+ <img
463
+ src={`https://github.com/${data.github_username}.png`}
464
+ alt={`${data.github_username} avatar`}
465
+ className="w-6 h-6 rounded-full flex-shrink-0"
466
+ onError={(e) => {
467
+ const target = e.target as HTMLImageElement
468
+ target.style.display = "none"
469
+ target.nextElementSibling?.classList.remove("hidden")
470
+ }}
471
+ />
472
+ <User className="w-6 h-6 text-gray-400 flex-shrink-0 hidden" />
473
+ <div className="flex flex-col min-w-0">
474
+ <span className="font-medium text-gray-900 truncate">
475
+ {renderHighlighted(data, data.github_username)}
476
+ </span>
477
+ </div>
478
+ </div>
479
+ <div className="flex items-center gap-1 flex-shrink-0">
480
+ <span className="text-xs text-gray-500 bg-gray-100 px-1.5 py-0.5 rounded">
481
+ user
482
+ </span>
483
+ {isSelected && <ArrowRight className="w-3 h-3 text-gray-400" />}
484
+ </div>
485
+ </div>
486
+ )
487
+
374
488
  case "blank":
375
489
  case "template":
376
490
  return (
@@ -464,7 +578,7 @@ const CmdKMenu = () => {
464
578
  </div>
465
579
 
466
580
  <Command.List className="max-h-80 overflow-y-auto p-2 space-y-4">
467
- {isSearching ? (
581
+ {isSearching || isSearchingAccounts ? (
468
582
  <Command.Loading className="p-6 text-center text-gray-500">
469
583
  <div className="flex items-center justify-center gap-2">
470
584
  <div className="w-3 h-3 border-2 border-gray-300 border-t-gray-600 rounded-full animate-spin"></div>
@@ -476,7 +590,7 @@ const CmdKMenu = () => {
476
590
  {searchQuery && searchResults.length > 0 && (
477
591
  <div>
478
592
  <h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wide mb-2 px-2">
479
- Search Results
593
+ Packages
480
594
  </h3>
481
595
  <div className="space-y-1">
482
596
  {searchResults.slice(0, 8).map((pkg, localIndex) => {
@@ -490,6 +604,25 @@ const CmdKMenu = () => {
490
604
  </div>
491
605
  )}
492
606
 
607
+ {searchQuery && accountSearchResults.length > 0 && (
608
+ <div>
609
+ <h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wide mb-2 px-2">
610
+ Users
611
+ </h3>
612
+ <div className="space-y-1">
613
+ {accountSearchResults
614
+ .slice(0, 5)
615
+ .map((account, localIndex) => {
616
+ const globalIndex = searchResults.length + localIndex
617
+ return renderItem(
618
+ { type: "account", item: account },
619
+ globalIndex,
620
+ )
621
+ })}
622
+ </div>
623
+ </div>
624
+ )}
625
+
493
626
  {!searchQuery && recentPackages.length > 0 && (
494
627
  <div>
495
628
  <h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wide mb-2 px-2 flex items-center gap-1">
@@ -518,7 +651,7 @@ const CmdKMenu = () => {
518
651
  (template, localIndex) => {
519
652
  const globalIndex =
520
653
  (searchQuery
521
- ? searchResults.length
654
+ ? searchResults.length + accountSearchResults.length
522
655
  : recentPackages.length) + localIndex
523
656
  return renderItem(
524
657
  {
@@ -544,7 +677,7 @@ const CmdKMenu = () => {
544
677
  (template, localIndex) => {
545
678
  const globalIndex =
546
679
  (searchQuery
547
- ? searchResults.length
680
+ ? searchResults.length + accountSearchResults.length
548
681
  : recentPackages.length) +
549
682
  filteredStaticOptions.blankTemplates.length +
550
683
  localIndex
@@ -568,7 +701,7 @@ const CmdKMenu = () => {
568
701
  (option, localIndex) => {
569
702
  const globalIndex =
570
703
  (searchQuery
571
- ? searchResults.length
704
+ ? searchResults.length + accountSearchResults.length
572
705
  : recentPackages.length) +
573
706
  filteredStaticOptions.blankTemplates.length +
574
707
  filteredStaticOptions.templates.length +
@@ -585,10 +718,12 @@ const CmdKMenu = () => {
585
718
 
586
719
  {searchQuery &&
587
720
  !searchResults.length &&
721
+ !accountSearchResults.length &&
588
722
  !filteredStaticOptions.blankTemplates.length &&
589
723
  !filteredStaticOptions.templates.length &&
590
724
  !filteredStaticOptions.importOptions.length &&
591
- !isSearching && (
725
+ !isSearching &&
726
+ !isSearchingAccounts && (
592
727
  <Command.Empty className="py-8 text-center">
593
728
  <div className="text-gray-400 mb-1">No results found</div>
594
729
  <div className="text-gray-500 text-xs">