@tscircuit/fake-snippets 0.0.87 → 0.0.89
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/api/generated-index.js +96 -14
- package/bun-tests/fake-snippets-api/routes/proxy.test.ts +42 -0
- package/bun.lock +187 -206
- package/dist/bundle.js +207 -101
- package/fake-snippets-api/routes/api/package_releases/create.ts +1 -1
- package/fake-snippets-api/routes/api/proxy.ts +128 -0
- package/package.json +57 -50
- package/renovate.json +2 -1
- package/src/App.tsx +22 -3
- package/src/ContextProviders.tsx +2 -0
- package/src/build-watcher.ts +52 -0
- package/src/components/CmdKMenu.tsx +533 -197
- package/src/components/DownloadButtonAndMenu.tsx +104 -26
- package/src/components/FileSidebar.tsx +11 -1
- package/src/components/Header.tsx +5 -1
- package/src/components/Header2.tsx +7 -2
- package/src/components/HeaderLogin.tsx +1 -1
- package/src/components/PackageBuildsPage/LogContent.tsx +25 -22
- package/src/components/PackageBuildsPage/PackageBuildDetailsPage.tsx +6 -6
- package/src/components/PackageBuildsPage/build-preview-content.tsx +5 -5
- package/src/components/PackageBuildsPage/package-build-details-panel.tsx +15 -13
- package/src/components/PackageBuildsPage/package-build-header.tsx +17 -16
- package/src/components/PackageCard.tsx +66 -16
- package/src/components/PrefetchPageLink.tsx +66 -15
- package/src/components/SearchComponent.tsx +2 -2
- package/src/components/SuspenseRunFrame.tsx +14 -2
- package/src/components/ViewPackagePage/components/important-files-view.tsx +97 -22
- package/src/components/ViewPackagePage/components/main-content-header.tsx +27 -3
- package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +2 -2
- package/src/components/ViewPackagePage/components/repo-page-content.tsx +49 -34
- package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +2 -2
- package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +20 -12
- package/src/components/ViewPackagePage/components/tab-views/files-view.tsx +0 -7
- package/src/components/ViewPackagePage/utils/fuzz-search.ts +121 -0
- package/src/components/ViewPackagePage/utils/is-hidden-file.ts +4 -0
- package/src/components/ViewPackagePage/utils/is-package-file-important.ts +18 -5
- package/src/components/dialogs/confirm-delete-package-dialog.tsx +1 -1
- package/src/components/dialogs/confirm-discard-changes-dialog.tsx +73 -0
- package/src/components/dialogs/edit-package-details-dialog.tsx +2 -2
- package/src/components/dialogs/view-ts-files-dialog.tsx +478 -42
- package/src/components/package-port/CodeAndPreview.tsx +16 -0
- package/src/components/package-port/CodeEditor.tsx +113 -11
- package/src/components/package-port/CodeEditorHeader.tsx +39 -4
- package/src/components/package-port/EditorNav.tsx +41 -15
- package/src/components/package-port/GlobalFindReplace.tsx +681 -0
- package/src/components/package-port/QuickOpen.tsx +241 -0
- package/src/components/ui/dialog.tsx +1 -1
- package/src/components/ui/tree-view.tsx +1 -1
- package/src/global.d.ts +3 -0
- package/src/hooks/use-ai-review.ts +31 -0
- package/src/hooks/use-current-package-release.ts +5 -1
- package/src/hooks/use-download-zip.ts +50 -0
- package/src/hooks/use-hotkey.ts +116 -0
- package/src/hooks/use-package-by-package-id.ts +1 -0
- package/src/hooks/use-package-by-package-name.ts +1 -0
- package/src/hooks/use-package-files.ts +3 -0
- package/src/hooks/use-package-release.ts +5 -1
- package/src/hooks/use-package.ts +1 -0
- package/src/hooks/use-request-ai-review-mutation.ts +14 -6
- package/src/hooks/use-snippet.ts +1 -0
- package/src/hooks/useFileManagement.ts +26 -8
- package/src/hooks/usePackageFilesLoader.ts +3 -1
- package/src/index.css +11 -0
- package/src/lib/decodeUrlHashToFsMap.ts +17 -0
- package/src/lib/download-fns/download-circuit-png.ts +88 -0
- package/src/lib/download-fns/download-png-utils.ts +31 -0
- package/src/lib/encodeFsMapToUrlHash.ts +13 -0
- package/src/lib/populate-query-cache-with-ssr-data.ts +39 -38
- package/src/lib/ts-lib-cache.ts +47 -0
- package/src/lib/types.ts +2 -0
- package/src/main.tsx +7 -0
- package/src/pages/dashboard.tsx +8 -5
- package/src/pages/user-profile.tsx +1 -1
- package/src/pages/view-package.tsx +15 -7
- package/vite.config.ts +100 -1
|
@@ -28,7 +28,7 @@ export default withRouteSpec({
|
|
|
28
28
|
package_name_with_version,
|
|
29
29
|
} = req.jsonBody
|
|
30
30
|
|
|
31
|
-
if (package_name_with_version && !
|
|
31
|
+
if (package_name_with_version && !package_id) {
|
|
32
32
|
const [packageName, parsedVersion] = package_name_with_version.split("@")
|
|
33
33
|
const pkg = ctx.db.packages.find((p) => p.name === packageName)
|
|
34
34
|
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
|
|
2
|
+
import { z } from "zod"
|
|
3
|
+
|
|
4
|
+
const PROXY_HEADERS = [
|
|
5
|
+
"X-Target-Url",
|
|
6
|
+
"X-Sender-Origin",
|
|
7
|
+
"X-Sender-Host",
|
|
8
|
+
"X-Sender-Referer",
|
|
9
|
+
"X-Sender-User-Agent",
|
|
10
|
+
"X-Sender-Cookie",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
const ALLOWED_DOMAINS: Array<{ domain: string; routes?: string[] }> = [
|
|
14
|
+
{ domain: "easyeda.com" },
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
function isAllowedDomain(url: string) {
|
|
18
|
+
try {
|
|
19
|
+
const { hostname, pathname, port } = new URL(url)
|
|
20
|
+
const allowedDomain = ALLOWED_DOMAINS.find(
|
|
21
|
+
(domain) => domain.domain === hostname,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
if (allowedDomain) {
|
|
25
|
+
if (
|
|
26
|
+
!allowedDomain.routes ||
|
|
27
|
+
allowedDomain.routes.some((route) => pathname.startsWith(route))
|
|
28
|
+
) {
|
|
29
|
+
return true
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return hostname === "localhost" || (hostname === "127.0.0.1" && port)
|
|
34
|
+
} catch {
|
|
35
|
+
return false
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default withRouteSpec({
|
|
40
|
+
methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"],
|
|
41
|
+
jsonResponse: z.any(),
|
|
42
|
+
auth: "session",
|
|
43
|
+
})(async (req, ctx) => {
|
|
44
|
+
if (req.headers.get("X-proxied")) {
|
|
45
|
+
return ctx.json(
|
|
46
|
+
{ error: "Recursive proxy calls are not allowed" },
|
|
47
|
+
{ status: 403 },
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const targetUrl = req.headers.get("X-Target-Url")
|
|
52
|
+
|
|
53
|
+
if (!targetUrl) {
|
|
54
|
+
return ctx.json(
|
|
55
|
+
{ error: "X-Target-Url header is required" },
|
|
56
|
+
{ status: 400 },
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!isAllowedDomain(targetUrl)) {
|
|
61
|
+
return ctx.json({ error: "Domain not allowed" }, { status: 403 })
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let body: string | undefined
|
|
65
|
+
if (["POST", "PUT", "PATCH"].includes(req.method)) {
|
|
66
|
+
body = await req.clone().text()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const headers = new Headers(req.headers)
|
|
70
|
+
|
|
71
|
+
const senderOrigin = req.headers.get("X-Sender-Origin")
|
|
72
|
+
if (senderOrigin) {
|
|
73
|
+
headers.set("Origin", senderOrigin)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const senderHost = req.headers.get("X-Sender-Host")
|
|
77
|
+
if (senderHost) {
|
|
78
|
+
const hostValue = senderHost.replace(/^https?:\/\//, "")
|
|
79
|
+
headers.set("Host", hostValue)
|
|
80
|
+
headers.set("authority", hostValue)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const senderReferer = req.headers.get("X-Sender-Referer")
|
|
84
|
+
if (senderReferer) {
|
|
85
|
+
headers.set("Referer", senderReferer)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const senderUserAgent = req.headers.get("X-Sender-User-Agent")
|
|
89
|
+
if (senderUserAgent) {
|
|
90
|
+
headers.set("User-Agent", senderUserAgent)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const senderCookie = req.headers.get("X-Sender-Cookie")
|
|
94
|
+
if (senderCookie) {
|
|
95
|
+
headers.set("Cookie", senderCookie)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
for (const header of PROXY_HEADERS) {
|
|
99
|
+
headers.delete(header)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
headers.delete("content-encoding")
|
|
103
|
+
headers.delete("accept-encoding")
|
|
104
|
+
headers.set("X-proxied", "true")
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const response = await fetch(targetUrl, {
|
|
108
|
+
method: req.method,
|
|
109
|
+
headers: headers,
|
|
110
|
+
body: ["GET", "HEAD"].includes(req.method) ? undefined : body,
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
const responseHeaders = new Headers(response.headers)
|
|
114
|
+
responseHeaders.delete("content-encoding")
|
|
115
|
+
|
|
116
|
+
return new Response(response.body, {
|
|
117
|
+
status: response.status,
|
|
118
|
+
statusText: response.statusText,
|
|
119
|
+
headers: responseHeaders,
|
|
120
|
+
})
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.error("Proxy error:", error)
|
|
123
|
+
return ctx.json(
|
|
124
|
+
{ error: { message: "Failed to proxy request" } },
|
|
125
|
+
{ status: 502 },
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tscircuit/fake-snippets",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.89",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -32,9 +32,12 @@
|
|
|
32
32
|
"generate-images": "bun run scripts/generate-image-sizes.ts",
|
|
33
33
|
"generate-sitemap": "bun run scripts/generate-sitemap.ts"
|
|
34
34
|
},
|
|
35
|
-
"
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@anthropic-ai/sdk": "^0.27.3",
|
|
36
37
|
"@babel/preset-react": "^7.25.9",
|
|
37
38
|
"@babel/preset-typescript": "^7.26.0",
|
|
39
|
+
"@babel/standalone": "^7.26.2",
|
|
40
|
+
"@biomejs/biome": "^1.9.2",
|
|
38
41
|
"@codemirror/autocomplete": "^6.18.1",
|
|
39
42
|
"@codemirror/lang-javascript": "^6.2.2",
|
|
40
43
|
"@codemirror/lang-json": "^6.0.1",
|
|
@@ -42,6 +45,7 @@
|
|
|
42
45
|
"@codemirror/state": "^6.4.1",
|
|
43
46
|
"@codemirror/view": "^6.34.1",
|
|
44
47
|
"@hookform/resolvers": "^3.9.0",
|
|
48
|
+
"@playwright/test": "^1.48.0",
|
|
45
49
|
"@radix-ui/react-accordion": "^1.2.4",
|
|
46
50
|
"@radix-ui/react-alert-dialog": "^1.1.1",
|
|
47
51
|
"@radix-ui/react-aspect-ratio": "^1.1.0",
|
|
@@ -70,24 +74,48 @@
|
|
|
70
74
|
"@radix-ui/react-toggle": "^1.1.0",
|
|
71
75
|
"@radix-ui/react-toggle-group": "^1.1.0",
|
|
72
76
|
"@radix-ui/react-tooltip": "^1.1.2",
|
|
73
|
-
"@
|
|
77
|
+
"@tailwindcss/typography": "^0.5.16",
|
|
78
|
+
"@tscircuit/3d-viewer": "^0.0.279",
|
|
79
|
+
"@tscircuit/assembly-viewer": "^0.0.1",
|
|
80
|
+
"@tscircuit/core": "^0.0.536",
|
|
81
|
+
"@tscircuit/create-snippet-url": "^0.0.8",
|
|
82
|
+
"@tscircuit/eval": "^0.0.244",
|
|
83
|
+
"@tscircuit/footprinter": "^0.0.186",
|
|
74
84
|
"@tscircuit/layout": "^0.0.29",
|
|
75
85
|
"@tscircuit/math-utils": "^0.0.10",
|
|
76
86
|
"@tscircuit/mm": "^0.0.8",
|
|
77
|
-
"@tscircuit/
|
|
87
|
+
"@tscircuit/pcb-viewer": "^1.11.194",
|
|
88
|
+
"@tscircuit/prompt-benchmarks": "^0.0.28",
|
|
89
|
+
"@tscircuit/props": "^0.0.246",
|
|
90
|
+
"@tscircuit/runframe": "^0.0.653",
|
|
91
|
+
"@tscircuit/schematic-viewer": "^2.0.21",
|
|
92
|
+
"@types/babel__standalone": "^7.1.7",
|
|
93
|
+
"@types/bun": "^1.1.10",
|
|
94
|
+
"@types/country-list": "^2.1.4",
|
|
78
95
|
"@types/file-saver": "^2.0.7",
|
|
96
|
+
"@types/md5": "^2.3.5",
|
|
79
97
|
"@types/ms": "^0.7.34",
|
|
98
|
+
"@types/node": "^22.13.0",
|
|
99
|
+
"@types/prismjs": "^1.26.4",
|
|
100
|
+
"@types/react": "^18.3.9",
|
|
101
|
+
"@types/react-dom": "^18.3.0",
|
|
102
|
+
"@types/react-helmet": "^6.1.11",
|
|
103
|
+
"@types/sharp": "^0.32.0",
|
|
80
104
|
"@typescript/ata": "^0.9.7",
|
|
105
|
+
"@typescript/vfs": "^1.6.0",
|
|
81
106
|
"@valtown/codemirror-codeium": "^1.1.1",
|
|
82
107
|
"@valtown/codemirror-ts": "^2.2.0",
|
|
83
108
|
"@vercel/analytics": "^1.4.1",
|
|
109
|
+
"@vitejs/plugin-react": "^4.3.1",
|
|
110
|
+
"autoprefixer": "^10.4.20",
|
|
84
111
|
"change-case": "^5.4.4",
|
|
85
112
|
"circuit-json": "^0.0.190",
|
|
86
|
-
"circuit-json-to-bom-csv": "^0.0.
|
|
87
|
-
"circuit-json-to-gerber": "^0.0.
|
|
88
|
-
"circuit-json-to-pnp-csv": "^0.0.
|
|
113
|
+
"circuit-json-to-bom-csv": "^0.0.7",
|
|
114
|
+
"circuit-json-to-gerber": "^0.0.25",
|
|
115
|
+
"circuit-json-to-pnp-csv": "^0.0.7",
|
|
89
116
|
"circuit-json-to-readable-netlist": "^0.0.13",
|
|
90
117
|
"circuit-json-to-tscircuit": "^0.0.4",
|
|
118
|
+
"circuit-to-svg": "^0.0.163",
|
|
91
119
|
"class-variance-authority": "^0.7.1",
|
|
92
120
|
"clsx": "^2.1.1",
|
|
93
121
|
"cmdk": "^1.0.4",
|
|
@@ -95,11 +123,15 @@
|
|
|
95
123
|
"country-list": "^2.3.0",
|
|
96
124
|
"date-fns": "^4.1.0",
|
|
97
125
|
"dsn-converter": "^0.0.60",
|
|
98
|
-
"easyeda": "^0.0.
|
|
126
|
+
"easyeda": "^0.0.195",
|
|
99
127
|
"embla-carousel-react": "^8.3.0",
|
|
100
128
|
"extract-codefence": "^0.0.4",
|
|
101
129
|
"fflate": "^0.8.2",
|
|
102
130
|
"file-saver": "^2.0.5",
|
|
131
|
+
"get-port": "^7.1.0",
|
|
132
|
+
"globals": "^15.9.0",
|
|
133
|
+
"he": "^1.2.0",
|
|
134
|
+
"idb-keyval": "^6.2.2",
|
|
103
135
|
"immer": "^10.1.1",
|
|
104
136
|
"input-otp": "^1.2.4",
|
|
105
137
|
"javascript-time-ago": "^2.5.11",
|
|
@@ -107,11 +139,17 @@
|
|
|
107
139
|
"jscad-electronics": "^0.0.25",
|
|
108
140
|
"jszip": "^3.10.1",
|
|
109
141
|
"kicad-converter": "^0.0.16",
|
|
142
|
+
"ky": "^1.7.5",
|
|
110
143
|
"lucide-react": "^0.488.0",
|
|
144
|
+
"lz-string": "^1.5.0",
|
|
111
145
|
"md5": "^2.3.0",
|
|
112
146
|
"ms": "^2.1.3",
|
|
113
147
|
"next-themes": "^0.3.0",
|
|
148
|
+
"postcss": "^8.4.47",
|
|
114
149
|
"posthog-js": "^1.203.2",
|
|
150
|
+
"prismjs": "^1.29.0",
|
|
151
|
+
"prompts": "^2.4.2",
|
|
152
|
+
"react": "^18.3.1",
|
|
115
153
|
"react-cookie-consent": "^9.0.0",
|
|
116
154
|
"react-day-picker": "8.10.1",
|
|
117
155
|
"react-dom": "^18.3.1",
|
|
@@ -124,62 +162,31 @@
|
|
|
124
162
|
"react-query": "^3.39.3",
|
|
125
163
|
"react-resizable-panels": "^2.1.3",
|
|
126
164
|
"recharts": "^2.12.7",
|
|
165
|
+
"redaxios": "^0.5.1",
|
|
127
166
|
"remark-gfm": "^4.0.1",
|
|
128
167
|
"rollup-plugin-visualizer": "^5.12.0",
|
|
129
168
|
"schematic-symbols": "^0.0.155",
|
|
169
|
+
"sharp": "^0.33.5",
|
|
170
|
+
"shiki": "^3.2.1",
|
|
130
171
|
"sitemap": "^8.0.0",
|
|
131
172
|
"sonner": "^1.5.0",
|
|
132
173
|
"states-us": "^1.1.1",
|
|
133
174
|
"tailwind-merge": "^2.5.2",
|
|
134
|
-
"tailwindcss-animate": "^1.0.7",
|
|
135
|
-
"use-async-memo": "^1.2.5",
|
|
136
|
-
"use-mouse-matrix-transform": "^1.3.0",
|
|
137
|
-
"vaul": "^0.9.9",
|
|
138
|
-
"vite-plugin-vercel": "^9.0.4",
|
|
139
|
-
"wouter": "^3.3.5"
|
|
140
|
-
},
|
|
141
|
-
"devDependencies": {
|
|
142
|
-
"@anthropic-ai/sdk": "^0.27.3",
|
|
143
|
-
"@babel/standalone": "^7.26.2",
|
|
144
|
-
"@biomejs/biome": "^1.9.2",
|
|
145
|
-
"@playwright/test": "^1.48.0",
|
|
146
|
-
"@tailwindcss/typography": "^0.5.16",
|
|
147
|
-
"@tscircuit/core": "^0.0.433",
|
|
148
|
-
"@tscircuit/eval": "^0.0.227",
|
|
149
|
-
"@tscircuit/prompt-benchmarks": "^0.0.28",
|
|
150
|
-
"@tscircuit/runframe": "^0.0.578",
|
|
151
|
-
"@types/babel__standalone": "^7.1.7",
|
|
152
|
-
"@types/bun": "^1.1.10",
|
|
153
|
-
"@types/country-list": "^2.1.4",
|
|
154
|
-
"@types/md5": "^2.3.5",
|
|
155
|
-
"@types/node": "^22.13.0",
|
|
156
|
-
"@types/prismjs": "^1.26.4",
|
|
157
|
-
"@types/react": "^18.3.9",
|
|
158
|
-
"@types/react-dom": "^18.3.0",
|
|
159
|
-
"@types/react-helmet": "^6.1.11",
|
|
160
|
-
"@types/sharp": "^0.32.0",
|
|
161
|
-
"@typescript/vfs": "^1.6.0",
|
|
162
|
-
"@vitejs/plugin-react": "^4.3.1",
|
|
163
|
-
"autoprefixer": "^10.4.20",
|
|
164
|
-
"circuit-to-svg": "^0.0.152",
|
|
165
|
-
"get-port": "^7.1.0",
|
|
166
|
-
"globals": "^15.9.0",
|
|
167
|
-
"he": "^1.2.0",
|
|
168
|
-
"ky": "^1.7.5",
|
|
169
|
-
"postcss": "^8.4.47",
|
|
170
|
-
"prismjs": "^1.29.0",
|
|
171
|
-
"prompts": "^2.4.2",
|
|
172
|
-
"react": "^18.3.1",
|
|
173
|
-
"redaxios": "^0.5.1",
|
|
174
|
-
"sharp": "^0.33.5",
|
|
175
|
-
"shiki": "^3.2.1",
|
|
176
175
|
"tailwindcss": "^3.4.13",
|
|
176
|
+
"tailwindcss-animate": "^1.0.7",
|
|
177
177
|
"terser": "^5.27.0",
|
|
178
|
+
"three": "^0.177.0",
|
|
179
|
+
"three-stdlib": "^2.36.0",
|
|
178
180
|
"tsup": "^8.5.0",
|
|
179
181
|
"typescript": "^5.6.3",
|
|
182
|
+
"use-async-memo": "^1.2.5",
|
|
183
|
+
"use-mouse-matrix-transform": "^1.3.0",
|
|
184
|
+
"vaul": "^0.9.9",
|
|
180
185
|
"vite": "^6.3.4",
|
|
181
186
|
"vite-plugin-image-optimizer": "^1.1.8",
|
|
187
|
+
"vite-plugin-vercel": "^9.0.4",
|
|
182
188
|
"winterspec": "^0.0.107",
|
|
189
|
+
"wouter": "^3.3.5",
|
|
183
190
|
"zod": "^3.23.8",
|
|
184
191
|
"zustand": "^4.5.5",
|
|
185
192
|
"zustand-hoist": "^2.0.1"
|
package/renovate.json
CHANGED
package/src/App.tsx
CHANGED
|
@@ -78,18 +78,37 @@ const PackageEditorPage = lazyImport(async () => {
|
|
|
78
78
|
|
|
79
79
|
class ErrorBoundary extends React.Component<
|
|
80
80
|
{ children: React.ReactNode },
|
|
81
|
-
{ hasError: boolean }
|
|
81
|
+
{ hasError: boolean; reloading: boolean }
|
|
82
82
|
> {
|
|
83
83
|
constructor(props: { children: React.ReactNode }) {
|
|
84
84
|
super(props)
|
|
85
|
-
this.state = { hasError: false }
|
|
85
|
+
this.state = { hasError: false, reloading: false }
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
static getDerivedStateFromError() {
|
|
89
|
-
return { hasError: true }
|
|
89
|
+
return { hasError: true, reloading: false }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
componentDidCatch(error: Error) {
|
|
93
|
+
console.error("ErrorBoundary caught", error)
|
|
94
|
+
const message = error.message || ""
|
|
95
|
+
if (
|
|
96
|
+
/(Loading chunk|ChunkLoadError|dynamically imported module)/i.test(
|
|
97
|
+
message,
|
|
98
|
+
)
|
|
99
|
+
) {
|
|
100
|
+
const loadedAt = window.__APP_LOADED_AT || Date.now()
|
|
101
|
+
if (Date.now() - loadedAt >= 10_000) {
|
|
102
|
+
this.setState({ reloading: true })
|
|
103
|
+
window.location.reload()
|
|
104
|
+
}
|
|
105
|
+
}
|
|
90
106
|
}
|
|
91
107
|
|
|
92
108
|
render() {
|
|
109
|
+
if (this.state.reloading) {
|
|
110
|
+
return <div>There was a problem loading this page. Reloading…</div>
|
|
111
|
+
}
|
|
93
112
|
if (this.state.hasError) {
|
|
94
113
|
return <div>Something went wrong loading the page.</div>
|
|
95
114
|
}
|
package/src/ContextProviders.tsx
CHANGED
|
@@ -4,6 +4,7 @@ import { useEffect } from "react"
|
|
|
4
4
|
import { useGlobalStore } from "./hooks/use-global-store"
|
|
5
5
|
import { posthog } from "./lib/posthog"
|
|
6
6
|
import { Toaster } from "react-hot-toast"
|
|
7
|
+
import { Toaster as SonnerToaster } from "@/components/ui/sonner"
|
|
7
8
|
import { populateQueryCacheWithSSRData } from "./lib/populate-query-cache-with-ssr-data"
|
|
8
9
|
|
|
9
10
|
const staffGithubUsernames = [
|
|
@@ -65,6 +66,7 @@ export const ContextProviders = ({ children }: any) => {
|
|
|
65
66
|
<PostHogIdentifier />
|
|
66
67
|
{children}
|
|
67
68
|
<Toaster position="bottom-right" />
|
|
69
|
+
<SonnerToaster position="bottom-left" />
|
|
68
70
|
</HelmetProvider>
|
|
69
71
|
</QueryClientProvider>
|
|
70
72
|
)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { toast } from "sonner"
|
|
2
|
+
|
|
3
|
+
export function setupBuildWatcher() {
|
|
4
|
+
const meta = document.querySelector<HTMLMetaElement>(
|
|
5
|
+
'meta[name="tscircuit-build"]',
|
|
6
|
+
)
|
|
7
|
+
const currentId = meta?.content || ""
|
|
8
|
+
;(window as any).TSC_BUILD_ID = currentId
|
|
9
|
+
|
|
10
|
+
async function fetchBuildId(): Promise<string | null> {
|
|
11
|
+
try {
|
|
12
|
+
const res = await fetch("/api/generated-index", { cache: "no-store" })
|
|
13
|
+
const text = await res.text()
|
|
14
|
+
const match = text.match(
|
|
15
|
+
/<meta name="tscircuit-build" content="([^"]+)"/i,
|
|
16
|
+
)
|
|
17
|
+
return match ? match[1] : null
|
|
18
|
+
} catch (err) {
|
|
19
|
+
console.error("Failed to fetch build identifier", err)
|
|
20
|
+
return null
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let updateToastShown = false
|
|
25
|
+
|
|
26
|
+
async function checkForUpdate() {
|
|
27
|
+
const serverId = await fetchBuildId()
|
|
28
|
+
if (
|
|
29
|
+
serverId &&
|
|
30
|
+
serverId !== (window as any).TSC_BUILD_ID &&
|
|
31
|
+
!updateToastShown
|
|
32
|
+
) {
|
|
33
|
+
updateToastShown = true
|
|
34
|
+
toast("A new version of tscircuit.com is available.", {
|
|
35
|
+
action: {
|
|
36
|
+
label: "Reload",
|
|
37
|
+
onClick: () => window.location.reload(),
|
|
38
|
+
},
|
|
39
|
+
duration: Infinity,
|
|
40
|
+
position: "bottom-left",
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
document.addEventListener("visibilitychange", () => {
|
|
46
|
+
if (document.visibilityState === "visible") {
|
|
47
|
+
checkForUpdate()
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
setInterval(checkForUpdate, 5 * 60 * 1000)
|
|
52
|
+
}
|